Ранняя остановка пользовательского агрегата SQLCLR

Допустим, я хочу, чтобы у меня была таблица со следующими столбцами и значениями:

| BucketID   | Value       |
|:-----------|------------:|
| 1          |    3        |
| 1          |    2        |
| 1          |    1        |
| 2          |    0        |
| 2          |    1        |
| 2          |    5        |

Давайте представим, что я хочу разделить BucketId и умножить значения в разделе:

SELECT DISTINCT BucketId, MULT(Value) OVER (PARTITION BY BucketId)

Теперь нет встроенной функции Aggregate MULT, поэтому я пишу свою собственную, используя SQL CLR:

using System;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;

[Serializable]
[SqlUserDefinedAggregate(Format.Native, Name = "MULT")]
public struct MULT
{
    private int runningSum;

    public void Init()
    {
        runningSum = 1;
    }

    public void Accumulate(SqlInt32 value)
    {
        runnningSum *= (int)value;
    }

    public void Merge(OverallStatus other)
    {
        runnningSum *= other.runningSum;
    }

    public SqlInt32 Terminate()
    {
        return new SqlInt32(runningSum);
    }
}

Мой вопрос сводится к следующему: предположим, я набрал 0 в Accumulate или Merge, нет смысла продолжать. Если это так, как я могу вернуть 0, как только я нажму 0?


person Matt    schedule 10.08.2017    source источник


Ответы (1)


Вы не можете принудительно завершить работу, поскольку нет возможности контролировать рабочий процесс. SQL Server вызовет метод Terminate(), когда каждая группа завершит обработку своего набора.

Однако, поскольку состояние UDA сохраняется для каждой строки, обрабатываемой в группе, вы можете просто проверить runningSum, чтобы увидеть, не является ли оно уже 0, и если да, то пропустить любые вычисления. Это сэкономит некоторое количество времени на обработку.

В методе Accumulate() первым шагом будет проверка, является ли runningSum 0, а если верно, то просто return;. Вы также должны проверить, является ли value NULL (то, что вы в настоящее время не проверяете). После этого проверьте входящий value, чтобы убедиться, что он равен 0, и если он истинен, то установите runningSum в 0 и return;.

public void Accumulate(SqlInt32 value)
{

  if (runningSum == 0 || value.IsNull)
  {
    return;
  }

  if (value.Value == 0)
  {
    runningSum = 0;
    return;
  }

  runningSum *= value.Value;
}

Примечание: не переводите value в int. Все типы Sql* имеют свойство Value, которое возвращает ожидаемый собственный тип .NET.

Наконец, в методе Merge проверьте runnningSum, чтобы убедиться, что это 0, и если оно истинно, просто return;. Затем проверьте other.runningSum, чтобы убедиться, что это 0, и если это правда, то установите runnningSum на 0.

person Solomon Rutzky    schedule 10.08.2017
comment
Потребуется тщательное тестирование, чтобы увидеть, есть ли в этом какой-то смысл. В частности, вставка условных операторов будет включать дополнительные инструкции и (возможно) переходы, которые вполне могут оказаться медленнее, чем простое безусловное умножение (в зависимости от того, что делает JIT-компилятор). - person Jeroen Mostert; 10.08.2017
comment
@JeroenMostert Достаточно справедливо, что касается этого конкретного сценария, который довольно прост. Но для более сложных сценариев этот прием весьма полезен :-). - person Solomon Rutzky; 10.08.2017