У меня есть таблица, которая включает в себя столбец десятичных значений, таких как это:
id value size
-- ----- ----
1 100 .02
2 99 .38
3 98 .13
4 97 .35
5 96 .15
6 95 .57
7 94 .25
8 93 .15
То, что мне нужно сделать, немного сложно описать, поэтому, пожалуйста, потерпите меня. То, что я пытаюсь сделать, это создать совокупное значение size
столбца, который увеличивается на 1 каждый раз, когда предыдущие строки суммируют до 1, когда в порядке убывания в соответствии с value
. Результат будет выглядеть примерно так:
id value size bucket
-- ----- ---- ------
1 100 .02 1
2 99 .38 1
3 98 .13 1
4 97 .35 1
5 96 .15 2
6 95 .57 2
7 94 .25 2
8 93 .15 3
Моя наивная первая попытка состояла в том, чтобы сохранить работоспособность, SUM
а затем и CEILING
это значение, однако это не обрабатывает случай, когда некоторые записи в size
конечном итоге вносят вклад в общее количество двух отдельных сегментов. Пример ниже может прояснить это:
id value size crude_sum crude_bucket distinct_sum bucket
-- ----- ---- --------- ------------ ------------ ------
1 100 .02 .02 1 .02 1
2 99 .38 .40 1 .40 1
3 98 .13 .53 1 .53 1
4 97 .35 .88 1 .88 1
5 96 .15 1.03 2 .15 2
6 95 .57 1.60 2 .72 2
7 94 .25 1.85 2 .97 2
8 93 .15 2.00 2 .15 3
Как вы можете видеть, если бы я просто использовал CEILING
для crude_sum
записи № 8 был бы назначен сегмент 2. Это вызвано тем, что size
записи № 5 и № 8 разделены на два сегмента. Вместо этого идеальным решением является сброс суммы каждый раз, когда она достигает 1, которая затем увеличивает bucket
столбец и начинает новую SUM
операцию, начиная со size
значения текущей записи. Поскольку порядок записей важен для этой операции, я включил value
столбец, который предназначен для сортировки в порядке убывания.
Мои первоначальные попытки включали многократное прохождение данных, один раз для выполнения SUM
операции, еще раз для CEILING
этого и т. Д. Вот пример того, что я сделал для создания crude_sum
столбца:
SELECT
id,
value,
size,
(SELECT TOP 1 SUM(size) FROM table t2 WHERE t2.value<=t1.value) as crude_sum
FROM
table t1
Который использовался в UPDATE
операции для вставки значения в таблицу для дальнейшей работы.
Изменить: Я хотел бы сделать еще один удар в объяснение этого, так что здесь. Представьте, что каждая запись является физическим элементом. Этот элемент имеет значение, связанное с ним, и физический размер меньше единицы. У меня есть серия сегментов с объемной емкостью ровно 1, и мне нужно определить, сколько из этих сегментов мне понадобится и в какой блок входит каждый элемент, в соответствии со стоимостью элемента, отсортированного от наивысшего к наименьшему.
Физический предмет не может существовать в двух местах одновременно, поэтому он должен находиться в одном ведре или другом. Вот почему я не могу выполнить CEILING
решение по промежуточному итогу + , потому что это позволило бы записям вносить свой размер в два сегмента.
distinct_count
усложнять ситуацию. Аарон Бертран (Aaron Bertrand) имеет отличную сводку ваших опций на SQL Server для такой работы с окнами. Я использовал метод «причудливого обновления» для расчетаdistinct_sum
, который вы можете увидеть здесь на SQL Fiddle , но это ненадежно.Ответы:
Я не уверен, какой тип производительности вы ищете, но если CLR или внешнее приложение не вариант, курсор это все, что осталось. На моем старом ноутбуке я прохожу 1000000 строк примерно за 100 секунд, используя следующее решение. Приятно то, что он линейно масштабируется, так что я бы посмотрел примерно на 20 минут, чтобы просмотреть все это. С приличным сервером вы будете быстрее, но не на порядок, так что это займет несколько минут. Если это один из процессов, вы, вероятно, можете позволить себе медлительность. Если вам нужно регулярно запускать этот отчет или подобное, вы можете сохранить значения в одной и той же таблице и не обновлять их по мере добавления новых строк, например, в триггере.
Во всяком случае, вот код:
Он удаляет и воссоздает таблицу MyTable, заполняет ее 1000000 строками и затем начинает работать.
Курсор копирует каждую строку во временную таблицу во время выполнения вычислений. В конце выбор возвращает вычисленные результаты. Вы можете быть немного быстрее, если вы не копируете данные, а вместо этого делаете обновление на месте.
Если у вас есть возможность перейти на SQL 2012, вы можете взглянуть на новые движущиеся агрегаты окон, поддерживаемые с помощью спулинга окон, которые должны повысить производительность.
Кстати, если у вас есть сборка, установленная с параметром allow_set = safe, вы можете сделать больше вредных вещей для сервера со стандартным T-SQL, чем со сборкой, так что я бы продолжал работать над устранением этого барьера. случай, когда CLR действительно поможет вам.
источник
В отсутствие новых оконных функций в SQL Server 2012 сложные оконные функции могут быть выполнены с использованием рекурсивных CTE. Интересно, насколько хорошо это будет работать против миллионов строк.
Следующее решение охватывает все описанные вами случаи. Вы можете увидеть это в действии здесь на SQL Fiddle .
Теперь сделайте глубокий вдох. Здесь есть два ключевых CTE, каждому из которых предшествует краткий комментарий. Остальные - просто «зачистка» CTE, например, чтобы вытянуть правильные строки после того, как мы их оценили.
Это решение предполагает, что
id
это последовательность без промежутков. Если нет, вам нужно будет создать свою собственную последовательность без промежутков, добавив в начале дополнительный CTE, который нумерует строки вROW_NUMBER()
соответствии с желаемым порядком (например,ROW_NUMBER() OVER (ORDER BY value DESC)
).На самом деле, это довольно многословно.
источник
crude_sum
сdistinct_sum
соответствующимиbucket
столбцами, чтобы понять, что я имею в виду.Это похоже на глупое решение, и оно, вероятно, не будет хорошо масштабироваться, поэтому проверяйте внимательно, если вы его используете. Поскольку основная проблема связана с «пробелом», оставленным в корзине, мне сначала нужно было создать запись-заполнитель для объединения в данные.
http://sqlfiddle.com/#!3/72ad4/14/0
источник
Ниже приведено еще одно рекурсивное решение CTE, хотя я бы сказал, что оно более простое, чем предложение @ Nick . Это на самом деле ближе к курсору @ Себастьяна , только я использовал текущие различия вместо промежуточных итогов. (Сначала я даже подумал, что ответ @ Ника будет соответствовать тому, что я предлагаю здесь, и именно после того, как я узнал, что это на самом деле совершенно другой запрос, я решил предложить мой.)
Примечание: этот запрос предполагает, что
value
столбец состоит из уникальных значений без пробелов. Если это не так, вам нужно будет ввести вычисляемый столбец ранжирования, основанный на нисходящем порядке,value
и использовать его в рекурсивном CTE, а неvalue
соединять рекурсивную часть с якорем.Демоверсию SQL Fiddle для этого запроса можно найти здесь .
источник
size
сroom_left
), а не сравнивать одно значение с выражением (1
сrunning_size
+size
).is_new_bucket
Сначала я использовал не флаг, а несколькоCASE WHEN t.size > r.room_left ...
вместо («несколько», потому что я также вычислял (и возвращал) общий размер, но потом подумал против него ради простоты), поэтому я подумал, что это будет более элегантно туда.