SQL Запуск итоговой группы со сбросом порога суммы - PullRequest
0 голосов
/ 24 мая 2018

У меня есть таблица:

PersonID    FirstName   PersonAge
1           Pras        2
2           Deep        3
3           Test        4
4           Prash       2
5           ser         1
6           df          8
7           ddf         5
8           vvv         4
9           ddd         1
10          eww         6
11          vvv         3
12          vbbb        7
13          Prabbbbs    6

Я хочу сгруппировать их так, чтобы общий возраст группы не превышал 10.

Я могу сделать это с помощью рекурсии, но этонеэффективен для больших таблиц.

  ;WITH cte AS
(
    SELECT PersonID, PersonAge, 1 AS [Group], PersonAge AS RunningTotal FROM POP where PersonId=1
    UNION ALL
    SELECT data.PersonId, data.PersonAge, 
        CASE WHEN cte.RunningTotal + data.PersonAge > 10 THEN cte.[Group] + 1 ELSE cte.[Group] END, 
        -- Reset the running total for each new group
        data.PersonAge + CASE WHEN cte.RunningTotal + data.PersonAge > 10 THEN 0 ELSE cte.RunningTotal END
    FROM POP data INNER JOIN cte ON data.PersonId = cte.PersonID + 1
)
SELECT * FROM cte

Итак, мне нужен вывод:

PersonID    PersonAge   Group   RunningTotal
1           2           1       2
2           3           1       5
3           4           1       9
4           2           2       2
5           1           2       3
6           8           3       8
7           5           4       5
8           4           4       9
9           1           4       10
10          6           5       6
11          3           5       9
12          7           6       7
13          6           7       6

Есть ли хорошее нерекурсивное решение?

РЕДАКТИРОВАТЬ: Пробная версия #1. Думая по ходу промежуточных итогов, я получил таблицу с CurrRunningTotal и RunningTotal до предыдущей строки.

WITH TE
AS (SELECT
  PersonId,
  FirstName,
  PersonAge,
  SUM(PersonAge) OVER (ORDER BY PersonId
  ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
  AS PrevRunningTotal,
  SUM(PersonAge) OVER (ORDER BY PersonId
  ROWS UNBOUNDED PRECEDING)
  AS RunningTotal
FROM POP),
MergedGroup
AS (SELECT
  *,
  SUM(CASE
    WHEN RunningTotal > @total THEN RunningTotal - @total
    ELSE PersonAge
  END) OVER (ORDER BY PersonId) AS Total
FROM TE)
SELECT
  *
FROM MergedGroup

Я чувствую, что используя PreviousRunningTotal, я могу творить чудеса, когда мой порог достигнут, чтобы получить итогс заполнением, т. е. при превышении порога, добавьте 10 к текущей строке, чтобы компенсировать итоговое значение. До сих пор близко, но без сигары.

1 Ответ

0 голосов
/ 24 мая 2018

Это слишком долго для комментария.

Да, вы можете выполнить ваши условия тривиально, но не оптимально.Просто сгруппируйте по personid.

Предположительно, вы намереваетесь что-то вроде «начать с начала и использовать соседние записи» или «минимизировать количество групп».Последняя представляет собой проблему упаковки бина, и не существует ни одного известного алгоритма с эффективной производительностью.

Первый - к сожалению - требует циклического перебора данных с самого начала, и именно это делают рекурсивные CTE.Мне не известен какой-либо другой общий подход к решению проблемы.Там могут быть конкретные подходы, в зависимости от конкретных ограничений вашей проблемы.

...