Выбор суммы значений TOP 2 в таблице с несколькими GROUP в SQL - PullRequest
9 голосов
/ 29 сентября 2010

Я играл с наборами в SQL Server 2000 и имею следующую структуру таблицы для одной из моих временных таблиц (#Periods):

    RestCTR     HoursCTR    Duration    Rest
    ----------------------------------------
    1           337         2           0
    2           337         46          1
    3           337         2           0
    4           337         46          1
    5           338         1           0
    6           338         46          1
    7           338         2           0
    8           338         46          1
    9           338         1           0
    10          339         46          1
    ...

Я хотел бы рассчитать сумму 2-х самых длинных периодов отдыха для каждого HoursCTR, предпочтительно используя наборы и временные таблицы (а не курсоры или вложенные подзапросы).

Вот запрос мечты, который просто не будет работать в SQL (независимо от того, сколько раз я его выполняю):

Select HoursCTR, SUM ( TOP 2 Duration ) as LongestBreaks
FROM #Periods
WHERE Rest = 1
Group By HoursCTR    

HoursCTR может иметь любое количество периодов отдыха (в том числе ни одного).

Мое текущее решение не очень элегантно и в основном включает следующие шаги:

  1. Получите максимальную продолжительность отдыха, сгруппированные по часамCTR
  2. Выберите первую (минимальную) строку RestCTR, которая возвращает эту максимальную продолжительность для каждого HoursCTR
  3. Повторите шаг 1 (за исключением строк, уже собранных на шаге 2)
  4. Повторите шаг 2 (снова, за исключением строк, собранных на шаге 2)
  5. Объединить строки RestCTR (из шагов 2 и 4) в одну таблицу
  6. Получить сумму Длительности, на которую указывают строки в шаге 5, сгруппированные по HoursCTR

Если есть какие-либо установленные функции, которые сокращают этот процесс, они будут очень приветствоваться.

Ответы [ 3 ]

7 голосов
/ 29 сентября 2010

Лучший способ сделать это в SQL Server - использовать общее табличное выражение , нумерация строк в каждой группе с помощью функции окон ROW_NUMBER():

WITH NumberedPeriods AS (
  SELECT HoursCTR, Duration, ROW_NUMBER() 
    OVER (PARTITION BY HoursCTR ORDER BY Duration DESC) AS RN
  FROM #Periods
  WHERE Rest = 1
)
SELECT HoursCTR, SUM(Duration) AS LongestBreaks
FROM NumberedPeriods
WHERE RN <= 2
GROUP BY HoursCTR

edit: Я добавил в раздел предложение ORDER BY, чтобы получить два самых длинных остатка.


Извините, я не заметил, что вам это нужно для работы в Microsoft SQL Server 2000. Эта версия не поддерживает функции CTE или оконные функции. Я оставлю ответ выше, если он кому-нибудь поможет.

В SQL Server 2000 обычно рекомендуется использовать коррелированный подзапрос:

SELECT p1.HoursCTR, (SELECT SUM(t.Duration) FROM 
    (SELECT TOP 2 p2.Duration FROM #Periods AS p2
     WHERE p2.HoursCTR = p1.HoursCTR 
     ORDER BY p2.Duration DESC) AS t) AS LongestBreaks
FROM #Periods AS p1
2 голосов
/ 07 октября 2010

В SQL 2000 нет ни CTE, ни ROW_NUMBER().
Коррелированные подзапросы могут потребовать дополнительного шага при использовании group by.

Это должно работать для вас:

SELECT 
    F.HoursCTR,
    MAX (F.LongestBreaks) AS LongestBreaks -- Dummy max() so that groupby can be used.
FROM
    (
        SELECT 
            Pm.HoursCTR, 
            (
                SELECT 
                    COALESCE (SUM (S.Duration), 0)    
                FROM 
                    (
                        SELECT TOP 2    T.Duration
                        FROM            #Periods    AS T
                        WHERE           T.HoursCTR  = Pm.HoursCTR 
                        AND             T.Rest      = 1
                        ORDER BY        T.Duration  DESC
                    ) AS S
             ) AS LongestBreaks
        FROM
            #Periods AS Pm
    ) AS F
GROUP BY
    F.HoursCTR
1 голос
/ 30 сентября 2010

К сожалению для вас, Алекс, у вас есть правильное решение: коррелированные подзапросы, в зависимости от их структуры, будут запускаться несколько раз, что потенциально даст вам сотни отдельных запросов.

Поместите текущее решение в Query Analyzer, включите «Показать план выполнения» (Ctrl + K) и запустите его. Внизу у вас будет дополнительная вкладка, которая покажет вам, как движок собирал ваши результаты. Если вы сделаете то же самое с коррелированным подзапросом, вы увидите, что делает эта опция.

Я полагаю, что таблица #Periods может забить столько раз, сколько отдельных строк в этой таблице.

Кроме того, что-то не так в коррелированном подзапросе, мне кажется. Поскольку я избегаю их как чумы, зная, что они злые, я не знаю, как их исправить.

...