Вы должны стараться избегать петель в целом. Обычно они менее эффективны, чем решения на основе множеств, а также менее читабельны.
Ниже должно быть довольно эффективным.
Тем более, если столбцы name и weight могут быть INCLUDE-
добавлены в индекс, чтобы избежать поиска ключей.
Он может сканировать уникальный индекс в порядке turn
и вычислять промежуточную сумму Weight
столбца, а затем использовать LEAD
с теми же критериями упорядочения, чтобы увидеть, какой будет промежуточная сумма в следующей строке.
Как только он находит первую строку, где она превышает 1000 или равна NULL
(указывает на отсутствие следующей строки), он может остановить сканирование.
WITH T1
AS (SELECT *,
SUM(Weight) OVER (ORDER BY turn ROWS UNBOUNDED PRECEDING) AS cume_weight
FROM [dbo].[line]),
T2
AS (SELECT LEAD(cume_weight) OVER (ORDER BY turn) AS next_cume_weight,
*
FROM T1)
SELECT TOP 1 name
FROM T2
WHERE next_cume_weight > 1000
OR next_cume_weight IS NULL
ORDER BY turn
План выполнения
На практике кажется, что он читает на несколько строк впереди, где это строго необходимо, - похоже, что каждая пара катушек окон / агрегатов потока вызывает чтение двух дополнительных строк.
Для образцов данных в этом вопросе в идеале нужно было бы только прочитать две строки из сканирования индекса, но в действительности он читает 6, но это не является существенной проблемой эффективности, и это не ухудшается, так как в таблицу добавляется больше строк (как в это демо )
Для тех, кто интересуется этой проблемой, изображение со строками, выводимыми каждым оператором (как показано query_trace_column_values
расширенным событием), расположено ниже, строки выводятся по row_id
порядку (начиная 47
с первой строки, прочитанной при сканировании индекса, и заканчивая 113
для TOP
)
Нажмите на изображение ниже, чтобы увеличить его или, в качестве альтернативы, увидеть анимированную версию, чтобы упростить процесс .
Приостановка анимации в точке, где агрегат правого потока выпустил свой первый ряд (для gary - turn = 1). Кажется очевидным, что он ожидал получить свою первую строку с другим WindowCount (для Jo - turn = 2). И оконная шпуля не освобождает первую строку «Джо», пока не прочитает следующую строку с другим turn
(для Томаса - turn = 3)
Таким образом, спул окна и агрегат потока вызывают чтение дополнительной строки, и в плане их четыре, а значит, и 4 дополнительные строки.
Ниже приводится объяснение столбцов, показанных выше (на основе информации здесь ).
- NodeName: индексное сканирование, NodeId: 15, ColumnName: идентификатор базовой таблицы, охватываемый индексом
- NodeName: индексное сканирование, NodeId: 15, ColumnName: поворот столбца базовой таблицы, охватываемого индексом
- NodeName: поиск кластеризованного индекса, NodeId: 17, ColumnName: столбец таблицы весов, полученный из поиска
- NodeName: поиск кластеризованного индекса, NodeId: 17, ColumnName: столбец базовой таблицы имен, полученный из поиска
- NodeName: Segment, NodeId: 13, ColumnName: Segment1010 Возвращает 1 в начале новой группы или ноль в противном случае. Поскольку ни один не
Partition By
в SUM
единственной первой строке получает 1
- NodeName: Sequence Project, NodeId: 12, ColumnName: RowNumber1009
row_number()
в группе, указанной флагом Segment1010. Поскольку все строки находятся в одной группе, это целые числа от 1 до 6 по возрастанию. Используется для фильтрации строк в правом кадре в подобных случаях rows between 5 preceding and 2 following
. (или как LEAD
позже)
- NodeName: Segment, NodeId: 11, ColumnName: Segment1011 Возвращает 1 в начале новой группы или ноль в противном случае. Поскольку ни один не
Partition By
в SUM
единственной первой строке получает 1 (То же, что Segment1010)
- NodeName: Window Spool, NodeId: 10, ColumnName: WindowCount1012 Атрибут, который группирует строки, принадлежащие рамке окна. В этой оконной катушке используется чехол "ускоренного режима"
UNBOUNDED PRECEDING
. Где это испускает две строки на исходную строку. Один с совокупными значениями и один с подробными значениями. Хотя нет видимой разницы в строках, представленных, query_trace_column_values
я предполагаю, что кумулятивные столбцы существуют в действительности.
- NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1004,
Count(*)
сгруппированный по WindowCount1012 в соответствии с планом, но на самом деле текущий счет
- NodeName: Stream Aggregate, NodeId: 9, ColumnName: Expr1005,
SUM(weight)
сгруппированный по WindowCount1012 в соответствии с планом, но на самом деле текущая сумма весов (т.е. cume_weight
)
- NodeName: Segment, NodeId: 7, ColumnName: Expr1002
CASE WHEN [Expr1004]=(0) THEN NULL ELSE [Expr1005] END
- не вижу, как COUNT(*)
может быть 0, поэтому всегда будет работать sum ( cume_weight
)
- NodeName: Сегмент, NodeId: 7, ColumnName: Segment1013 Нет
partition by
на LEAD
так первый ряд получает 1. Все остальные прибудете нулевой
- NodeName: Sequence Project, NodeId: 6, ColumnName: RowNumber1006
row_number()
в группе, указанной флагом Segment1013. Поскольку все строки находятся в одной группе, это целые числа от 1 до 4 по возрастанию
- NodeName: Segment, NodeId: 4, ColumnName: BottomRowNumber1008 RowNumber1006 + 1, поскольку
LEAD
требуется одна следующая строка
- NodeName: Segment, NodeId: 4, ColumnName: TopRowNumber1007 RowNumber1006 + 1, поскольку
LEAD
требуется одна следующая строка
- NodeName: Сегмент, NodeId: 4, ColumnName: Segment1014 Нет
partition by
на LEAD
так первый ряд получает 1. Все остальные прибудете нулевой
- NodeName: Window Spool, NodeId: 3, ColumnName: WindowCount1015 Атрибут, который группирует строки, принадлежащие рамке окна, с использованием предыдущих номеров строк. Фрейм окна
LEAD
имеет максимум 2 строки (текущая и следующая)
- Имя узла: агрегат потока, идентификатор узла: 2, имя столбца: Expr1003
LAST_VALUE([Expr1002])
дляLEAD(cume_weight)
Client statistics --> Total Execution Time
, а не то,Actual execution plan
что, пожалуй, самое интересное здесь. НаClient Statistics
ваше решение является чуть - чуть медленнее , чем Мартин. Спасибо за дополнительную информацию. Какой метод можно использовать для измерения различий в производительности между различными подходами?ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
ввелSequence Project (Compute Scalar)
оператор. Само собой разумеется, я понятия не имею, что это значит :-)Вы можете сделать соединение против себя:
Подобные вещи не очень эффективны, так как вызывают выбор в строке. Но, по крайней мере, это выражается как одно утверждение.
Если вам не нужно делать это полностью в SQL, вы можете просто выбрать все строки и пройтись по ним, добавляя их по мере продвижения.
Вы можете сделать то же самое в хранимой процедуре без временной таблицы. Просто держите сумму и имя последней строки в переменной.
источник
self-join
Если бы вы могли сделать небольшой воспроизводимый пример, я добавил определение таблицы в свой вопрос. Мой sql плохо .... Мне нужно имя человека, ближайшего к <= 1000 фунтов.COALESCE()
илиISNULL()
функцию илиCASE
выражение, чтобы сделать его 0.