Это своего рода тривиальная задача в моем домашнем мире C #, но я еще не сделал это в SQL и предпочел бы решать ее на основе множеств (без курсоров). Набор результатов должен исходить из запроса, подобного этому.
SELECT SomeId, MyDate,
dbo.udfLastHitRecursive(param1, param2, MyDate) as 'Qualifying'
FROM T
Как это должно работать
Я отправляю эти три параметра в UDF.
UDF внутренне использует параметры для извлечения связанных строк <= 90 дней из представления.
UDF пересекает «MyDate» и возвращает 1, если оно должно быть включено в общий расчет.
Если это не так, то возвращается 0. Именуется здесь как «квалификация».
Что сделает udf
Перечислите строки в порядке дат. Подсчитайте дни между строками. Первая строка в наборе результатов по умолчанию имеет значение Hit = 1. Если разница составляет до 90, - затем переходите к следующей строке, пока сумма разрывов не станет равной 90 дням (90-й день должен пройти). По достижении установите Hit на 1 и сбросьте разрыв на 0 Было бы также работать, чтобы вместо этого пропустить строку из результата.
|(column by udf, which not work yet)
Date Calc_date MaxDiff | Qualifying
2014-01-01 11:00 2014-01-01 0 | 1
2014-01-03 10:00 2014-01-01 2 | 0
2014-01-04 09:30 2014-01-03 1 | 0
2014-04-01 10:00 2014-01-04 87 | 0
2014-05-01 11:00 2014-04-01 30 | 1
В приведенной выше таблице столбец MaxDiff - это разрыв с датой в предыдущей строке. Проблема с моими попытками до сих пор состоит в том, что я не могу игнорировать вторую последнюю строку в приведенном выше примере.
[РЕДАКТИРОВАТЬ]
В соответствии с комментарием я добавляю тег, а также вставляю только что скомпилированный udf. Тем не менее, это просто заполнитель и не даст полезного результата.
;WITH cte (someid, otherkey, mydate, cost) AS
(
SELECT someid, otherkey, mydate, cost
FROM dbo.vGetVisits
WHERE someid = @someid AND VisitCode = 3 AND otherkey = @otherkey
AND CONVERT(Date,mydate) = @VisitDate
UNION ALL
SELECT top 1 e.someid, e.otherkey, e.mydate, e.cost
FROM dbo.vGetVisits AS E
WHERE CONVERT(date, e.mydate)
BETWEEN DateAdd(dd,-90,CONVERT(Date,@VisitDate)) AND CONVERT(Date,@VisitDate)
AND e.someid = @someid AND e.VisitCode = 3 AND e.otherkey = @otherkey
AND CONVERT(Date,e.mydate) = @VisitDate
order by e.mydate
)
У меня есть другой запрос, который я определяю отдельно, который ближе к тому, что мне нужно, но заблокирован из-за того, что я не могу рассчитать на оконных столбцах. Я также попробовал один аналог, который дает более или менее такой же вывод только с LAG () над MyDate, окруженный датой.
SELECT
t.Mydate, t.VisitCode, t.Cost, t.SomeId, t.otherkey, t.MaxDiff, t.DateDiff
FROM
(
SELECT *,
MaxDiff = LAST_VALUE(Diff.Diff) OVER (
ORDER BY Diff.Mydate ASC
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM
(
SELECT *,
Diff = ISNULL(DATEDIFF(DAY, LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate),0),
DateDiff = ISNULL(LAST_VALUE(r.Mydate) OVER (
ORDER BY r.Mydate ASC
ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING),
r.Mydate)
FROM dbo.vGetVisits AS r
WHERE r.VisitCode = 3 AND r.SomeId = @SomeID AND r.otherkey = @otherkey
) AS Diff
) AS t
WHERE t.VisitCode = 3 AND t.SomeId = @SomeId AND t.otherkey = @otherkey
AND t.Diff <= 90
ORDER BY
t.Mydate ASC;
источник
Ответы:
Когда я читаю вопрос, требуется базовый рекурсивный алгоритм:
Это относительно легко реализовать с помощью рекурсивного общего табличного выражения.
Например, используя следующий пример данных (на основе вопроса):
Рекурсивный код:
Результаты:
С индексом, имеющим
TheDate
в качестве ведущего ключа, план выполнения очень эффективен:Вы могли бы обернуть это в функцию и выполнить ее непосредственно против представления, упомянутого в вопросе, но мои инстинкты против этого. Обычно производительность повышается, когда вы выбираете строки из представления во временной таблице, предоставляете соответствующий индекс для временной таблицы, а затем применяете логику выше. Детали зависят от деталей представления, но это мой общий опыт.
Для полноты (и подсказки ответа ypercube) я должен упомянуть, что мое другое промежуточное решение для этого типа проблемы (до тех пор, пока T-SQL не получит правильные функции упорядоченного множества) - это курсор SQLCLR ( см. Мой ответ здесь для примера техники ). Он работает намного лучше, чем курсор T-SQL, и удобен для тех, кто владеет языками .NET и умеет запускать SQLCLR в своей производственной среде. В этом сценарии он может не предложить много, по сравнению с рекурсивным решением, потому что большая часть затрат - это сорт, но это стоит упомянуть.
источник
Так как это является 2014 вопрос SQL Server я мог бы также добавить изначально скомпилированные версии хранимой процедуры в виде «движка».
Исходная таблица с некоторыми данными:
Тип таблицы, который является параметром хранимой процедуры. Отрегулируйте
bucket_count
соответственно .И хранимая процедура, которая перебирает табличный параметр и собирает строки в
@R
.Код для заполнения табличной переменной, оптимизированной для памяти, которая используется в качестве параметра для изначально скомпилированной хранимой процедуры и вызова процедуры.
Результат:
Обновить:
Если вам по какой-то причине не нужно посещать каждую строку в таблице, вы можете сделать эквивалент версии «перейти к следующей дате», которая реализована в рекурсивном CTE Полом Уайтом.
Тип данных не нуждается в столбце идентификатора, и вы не должны использовать хеш-индекс.
И хранимая процедура использует,
select top(1) ..
чтобы найти следующее значение.источник
T.TheDate >= dateadd(day, 91, @CurDate)
все, то все будет хорошо, верно?TheDate
вTType
наDate
.Решение, которое использует курсор.
(сначала несколько необходимых таблиц и переменных) :
Фактический курсор:
И получаю результаты:
Протестировано в SQLFiddle
источник
INSERT @cd
только когда@Qualify=1
(и, следовательно, не вставляя 13M строк, если вам не нужны все из них в выводе). И решение зависит от поиска индексаTheDate
. Если его нет, он не будет эффективным.Результат
Также взгляните на Как рассчитать промежуточную сумму в SQL Server
обновление: смотрите ниже результаты тестирования производительности.
Из-за различий в логике, используемой при поиске «разрыва в 90 дней», решения ypercube и мои решения, если их оставить нетронутыми, могут вернуть разные результаты для решения Пола Уайта. Это связано с использованием функций DATEDIFF и DATEADD соответственно.
Например:
возвращает «2014-04-01 00: 00: 00.000», что означает, что «2014-04-01 01: 00: 00.000» превышает 90 дней
но
Возвращает «90», означая, что он все еще находится в промежутке.
Рассмотрим пример магазина. В этом случае продажа скоропортящихся продуктов с продажей по датам «2014-01-01» на «2014-01-01 23: 59: 59: 999» - это нормально. Так что значение DATEDIFF (DAY, ...) в этом случае в порядке.
Другой пример - пациент, ожидающий увидеть. Для тех, кто приходит в «2014-01-01 00: 00: 00: 000» и уходит в «2014-01-01 23: 59: 59: 999», это 0 (ноль) дней, если используется DATEDIFF, даже если фактическое ожидание было почти 24 часа. Снова пациент, который приходит в '2014-01-01 23:59:59' и уходит в '2014-01-02 00:00:01', ждал день, если используется DATEDIFF.
Но я отвлекся.
Я оставил решения DATEDIFF и даже протестировал их, но они действительно должны быть в своей собственной лиге.
Также было отмечено, что для больших наборов данных невозможно избежать значений одного дня. Таким образом, если мы скажем 13 миллионов записей, охватывающих данные за 2 года, у нас будет несколько записей за несколько дней. Эти записи будут отфильтрованы при первой же возможности в решениях DATEDIFF от my и ypercube. Надеюсь, что ypercube не возражает против этого.
Решения были проверены на следующей таблице
с двумя разными кластерными индексами (в данном случае mydate):
Таблица была заполнена следующим образом
Для случая с многомиллионной строкой INSERT был изменен таким образом, что записи по 0-20 минут добавлялись случайным образом.
Все решения были тщательно обернуты в следующем коде
Фактические проверенные коды (в произвольном порядке):
Решение Ypercube DATEDIFF ( YPC, DATEDIFF )
Решение Ypercube для DATEADD ( YPC, DATEADD )
Решение Пола Уайта ( PW )
Мое решение DATEADD ( PN, DATEADD )
Мое решение DATEDIFF ( PN, DATEDIFF )
Я использую SQL Server 2012, поэтому извиняюсь перед Микаэлем Эрикссоном, но его код здесь не будет тестироваться. Я все еще ожидал бы, что его решения с DATADIFF и DATEADD будут возвращать разные значения в некоторых наборах данных.
И фактические результаты:
источник
Хорошо, я что-то пропустил или почему бы вам не пропустить рекурсию и снова присоединиться к себе? Если дата является первичным ключом, она должна быть уникальной и в хронологическом порядке, если вы планируете рассчитать смещение для следующей строки
Урожайность
Если я полностью не пропустил что-то важное ....
источник
WHERE [TheDate] > [T1].[TheDate]
чтобы учесть порог разницы в 90 дней. Но, тем не менее, ваш вывод не является желаемым.