2013-05-31 2 views
2
CREATE TABLE [T] 
(
    CreatedOn DATETIME NOT NULL 
    ,Name NVARCHAR(20) NOT NULL 
    ,Code NVARCHAR(20) NOT NULL 
    ,R FLOAT NULL 
    ,C1 FLOAT NULL 
    ,C2 FLOAT NULL 
    ,C3 FLOAT NULL 
); 

INSERT INTO [T] VALUES 
('2013-01-01', N'A', N'', 13, NULL, NULL, NULL) 
,('2013-01-07', N'A', N'1', NULL, 5, NULL, NULL) 
,('2013-01-31', N'A', N'2', NULL, 4, NULL, NULL) 
,('2013-02-01', N'A', N'1', NULL, NULL,  6, NULL) 
,('2013-02-15', N'A', N'2', NULL, NULL, NULL, 3) 
,('2013-03-01', N'A', N'1', NULL, 1, NULL, NULL) 
,('2013-03-05', N'A', N'',  8, NULL, NULL, NULL) 
,('2013-03-22', N'A', N'2', NULL, NULL, NULL, 1) 
,('2013-05-01', N'A', N'1', NULL, 2, NULL, NULL); 

In [T] 
1. One and only one non-null value per row for [R], [C1], [C2] and [C3] 
2. [Code] contains a non-empty value if [C1], [C2] or [C3] contains a non-null value 
3. There is an index on [Name] 
4. Contains millions of rows 
5. Few unique values of [Code], typically less than 100 
6. Few unique values of [Name], typically less than 10000 
7. Is actually a complex view containing several inner joins 

Как выбрать из [T] ([DateMonth], [P]) где [CreatedOn]> = @Start И [CreatedOn] < = @End AND [Name] = @Name AND [P] = Сумма ([R]) - (Сумма (MaxOf (Сумма ([C1]), Сумма ([C2]), Сумма ([C3]), за уникальный [Код])))? (См. Ожидаемый результат ниже для более точного «объяснения».) В каждом наборе результатов в каждый месяц должна быть строка @Start - @End, независимо от того, существуют ли строки в этом месяце в [T]. Использование временной таблицы допустимо.Как выбрать максимальную сумму столбцов за другой столбец за диапазон datetime?

Expected Output 
@Name = N'A' 
@Start = '2012-12-01' 
@End = '2013-07-01' 

DateMonth P 
'2012-12-01' 0 
'2013-01-01' 4 -- 4 = SUM([R])=13 -  (MaxForCode'1'(SUM(C1)=5,  SUM(C2)=0,  SUM(C3)=0)=5 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=0)=4) 
'2013-02-01' 3 -- 3 = SUM([R])=13 -  (MaxForCode'1'(SUM(C1)=5,  SUM(C2)=6,  SUM(C3)=0)=6 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=3)=4) 
'2013-03-01' 11 -- 11 = SUM([R])=13+8=21 - (MaxForCode'1'(SUM(C1)=5+1=6, SUM(C2)=6,  SUM(C3)=0)=6 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=3+1=4)=4) 
'2013-04-01' 11 
'2013-05-01' 9 -- 9 = SUM([R])=13+8=21 - (MaxForCode'1'(SUM(C1)=5+1=6, SUM(C2)=6+2=8, SUM(C3)=0)=8 + MaxForCode'2'(SUM(C1)=4, SUM(C2)=0, SUM(C3)=3+1=4)=4) 
'2013-06-01' 9 
'2013-07-01' 9 

ответ

1

Это одно решение. Конечно, некоторые улучшения производительности могут быть сделаны, но я оставлю это до вас и вашей конкретной ситуации. Обратите внимание, что использование CTE, безусловно, не требуется, и добавление CreateOn в индекс будет очень полезным. Таблица temp также может быть лучше, чем переменная таблицы, но вам нужно ее оценить.

Поскольку я думаю, что то, что вы ищете, рассчитано на общее количество, this article может помочь в улучшении производительности моего предлагаемого решения.

Лично я бы сначала не рассматривал возможность использования представления, так как работа непосредственно с sql, который создает представление, может быть более результативным.

Вот SQL и SQLFiddle link.

DECLARE @Name NVARCHAR(1) = N'A', 
@Start DATETIME = '2012-12-01', 
@End DATETIME = '2013-07-01' 


--get the date for the first of the start and end months 
DECLARE @StartMonth DATETIME = DATEADD(month, DATEDIFF(month, 0, @Start), 0) 
DECLARE @EndMonth DATETIME = DATEADD(month, DATEDIFF(month, 0, @End), 0) 


DECLARE @tt TABLE 
(
    DateMonth DATETIME, 
    sum_r FLOAT, 
    code NVARCHAR(20), 
    max_c FLOAT 
) 

--CTE to create a simple table with an entry for each month (and nxt month) 
;WITH Months 
as 
(
    SELECT @StartMonth as 'dt', DATEADD(month, 1, @StartMonth) as 'nxt' 
    UNION ALL 
    SELECT DATEADD(month, 1, dt) as 'dt', DATEADD(month, 2, dt) as 'nxt' 
    FROM Months 
    WHERE dt < @EndMonth 
) 
--SELECT * FROM Months OPTION(MAXRECURSION 9965) --for the CTE, you could also select dates into a temp table/table var first 


INSERT INTO @tt (DateMonth, sum_r, code, max_c) 
SELECT M.dt DateMonth, 
     ISNULL(t.sum_r,0) sum_r, 
     ISNULL(t.code,'') code, 
     ISNULL(t.max_c,0) max_c 
     --sum_c1, sum_c2, sum_c3, cnt 
FROM Months M 
    OUTER APPLY (
        SELECT sum_r, 
          code, 
          CASE WHEN sum_c1 >= sum_c2 AND sum_c1 >= sum_c3 THEN sum_c1 
           WHEN sum_c2 >= sum_c1 AND sum_c2 >= sum_c3 THEN sum_c2 
           ELSE sum_c3 
          END max_c 
          --sum_c1, sum_c2, sum_c3, cnt 
        FROM ( --use a sub select here to improve case statement performance getting max_c 
          SELECT SUM(ISNULL(r,0)) sum_r, 
            code, 
            sum(ISNULL(c1,0)) sum_c1, 
            sum(ISNULL(c2,0)) sum_c2, 
            SUM(ISNULL(c3,0)) sum_c3 
          FROM T 
          WHERE CreatedOn >= @Start AND CreatedOn < M.nxt 
            AND CreatedOn <= @End 
            AND Name = @Name 
          GROUP BY code 
         ) subselect 
       ) t 
OPTION (MAXRECURSION 999) 



SELECT DateMonth, SUM(sum_r) - SUM(max_c) p 
FROM @tt 
GROUP BY DateMonth 
Смежные вопросы