2012-06-26 14 views
16

Necesito ayuda para producir una consulta de MS SQL 2012 que coincida con la salida de paso de escalera deseada. Las filas resumen los datos por un rango de fechas (mes de fecha de envío de cuenta) y las columnas lo resumen por otro rango de fechas (mes de fecha de pago)SQL Stairstep Query Help necesaria

Tabla 1: Cuentas rastrea las cuentas colocadas para colecciones.

CREATE TABLE [dbo].[Accounts](
    [AccountID] [nchar](10) NOT NULL, 
    [SubmissionDate] [date] NOT NULL, 
    [Amount] [money] NOT NULL, 
CONSTRAINT [PK_Accounts] PRIMARY KEY CLUSTERED (AccountID ASC)) 

INSERT INTO [dbo].[Accounts] VALUES ('1000', '2012-01-01', 1999.00) 
INSERT INTO [dbo].[Accounts] VALUES ('1001', '2012-01-02', 100.00) 
INSERT INTO [dbo].[Accounts] VALUES ('1002', '2012-02-05', 350.00) 
INSERT INTO [dbo].[Accounts] VALUES ('1003', '2012-03-01', 625.00) 
INSERT INTO [dbo].[Accounts] VALUES ('1004', '2012-03-10', 50.00) 
INSERT INTO [dbo].[Accounts] VALUES ('1005', '2012-03-10', 10.00) 

Tabla 2: Trans pistas pagos efectuados

CREATE TABLE [dbo].[Trans](
    [TranID] [int] IDENTITY(1,1) NOT NULL, 
    [AccountID] [nchar](10) NOT NULL, 
    [TranDate] [date] NOT NULL, 
    [TranAmount] [money] NOT NULL, 
CONSTRAINT [PK_Trans] PRIMARY KEY CLUSTERED (TranID ASC)) 

INSERT INTO [dbo].[Trans] VALUES (1000, '2012-01-15', 300.00) 
INSERT INTO [dbo].[Trans] VALUES (1000, '2012-02-15', 300.00) 
INSERT INTO [dbo].[Trans] VALUES (1000, '2012-03-15', 300.00) 
INSERT INTO [dbo].[Trans] VALUES (1002, '2012-02-20', 325.00) 
INSERT INTO [dbo].[Trans] VALUES (1002, '2012-04-20', 25.00) 
INSERT INTO [dbo].[Trans] VALUES (1003, '2012-03-24', 625.00) 
INSERT INTO [dbo].[Trans] VALUES (1004, '2012-03-28', 31.00) 
INSERT INTO [dbo].[Trans] VALUES (1004, '2012-04-12', 5.00) 
INSERT INTO [dbo].[Trans] VALUES (1005, '2012-04-08', 7.00) 
INSERT INTO [dbo].[Trans] VALUES (1005, '2012-04-28', 3.00) 

Esto es lo que la salida deseada debe ser similar

        *Total Payments in Each Month* 
SubmissionYearMonth TotalAmount | 2012-01 2012-02 2012-03 2012-04 
-------------------------------------------------------------------- 
2012-01    2099.00  | 300.00 300.00 300.00  0.00 
2012-02    350.00  |   325.00  0.00 25.00 
2012-03    685.00  |     656.00 15.00 

Las dos primeras columnas suman Account.Amount agrupación por mes.

Las últimas 4 columnas suman Tran.TranAmount, por mes, para las cuentas colocadas en el mes dado de la fila actual.

La consulta con la que he estado trabajando se siente cerca. Simplemente no tengo el retraso correcto. Aquí es la consulta que estoy trabajando hasta el momento:

Select SubmissionYearMonth, 
     TotalAmount, 
     pt.[0] AS MonthOld0, 
     pt.[1] AS MonthOld1, 
     pt.[2] AS MonthOld2, 
     pt.[3] AS MonthOld3, 
     pt.[4] AS MonthOld4, 
     pt.[5] AS MonthOld5, 
     pt.[6] AS MonthOld6, 
     pt.[7] AS MonthOld7, 
     pt.[8] AS MonthOld8, 
     pt.[9] AS MonthOld9, 
     pt.[10] AS MonthOld10, 
     pt.[11] AS MonthOld11, 
     pt.[12] AS MonthOld12, 
     pt.[13] AS MonthOld13 

From (
     SELECT Convert(Char(4),Year(SubmissionDate)) + '-' + Right('00' + Convert(VarChar(2), DatePart(Month, SubmissionDate)),2) AS SubmissionYearMonth, 
     SUM(Amount) AS TotalAmount 
     FROM Accounts 
     GROUP BY Convert(Char(4),Year(SubmissionDate)) + '-' + Right('00' + Convert(VarChar(2), DatePart(Month, SubmissionDate)),2) 
    ) 
AS AccountSummary 
OUTER APPLY 
(
SELECT * 
FROM (
     SELECT CASE WHEN DATEDIFF(Month, SubmissionDate, TranDate) < 13 
        THEN DATEDIFF(Month, SubmissionDate, TranDate) 
        ELSE 13 
       END AS PaymentMonthAge, 
       TranAmount 
     FROM Trans INNER JOIN Accounts ON Trans.AccountID = Accounts.AccountID 
     Where Convert(Char(4),Year(TranDate)) + '-' + Right('00' + Convert(VarChar(2), DatePart(Month, TranDate)),2) 
      = AccountSummary.SubmissionYearMonth 
     ) as TransTemp 
     PIVOT (SUM(TranAmount) 
       FOR PaymentMonthAge IN ([0], 
             [1], 
             [2], 
             [3], 
             [4], 
             [5], 
             [6], 
             [7], 
             [8], 
             [9], 
             [10], 
             [11], 
             [12], 
             [13])) as TransPivot 
) as pt 

Se produce el siguiente resultado:

SubmissionYearMonth TotalAmount MonthOld0 MonthOld1 MonthOld2 MonthOld3 ... 
2012-01    2099.00  300.00 NULL  NULL  NULL  ... 
2012-02    350.00  325.00 300.00 NULL  NULL  ... 
2012-03    685.00  656.00 NULL  300.00 NULL  ... 

En cuanto a las cabeceras de fecha columna. No estoy seguro de cuál es la mejor opción aquí. Podría agregar un conjunto adicional de columnas y crear un valor calculado que pueda usar en el informe resultante.

SQL violín: http://www.sqlfiddle.com/#!6/272e5/1/0

Gracias por cualquier ayuda.

+9

** Excelente ** trabajo que incluye datos de muestra y consultas. ¡Así es como deben escribirse todas las preguntas! – mellamokb

+3

@mellamokb: Además de un enlace a [SQLFiddle] (http://sqlfiddle.com) y sería perfecto :) –

+2

@juergend: De hecho. Aquí está la consulta del OP: http://www.sqlfiddle.com/#!3/56223/1 – mellamokb

Respuesta

1

Thomas, que utilizan su respuesta como inspiración para la siguiente solución Terminé usando.

Primero creo una fecha de envío, una matriz de fecha de esquemas cruzados de TranDate, que luego uso para unirme a los datos de AccountSummary y TranSummary.

El resultado de la consulta resultante no está formateado en columnas, por mes de TranDate. Más bien, estoy usando el resultado en una matriz de SQL Server Reporting Services, y usando una agrupación de columnas, basada en la columna TranSummaryMonthNum, para obtener el resultado formateado deseado.

SQL Fiddle version

; 
WITH 
    --Generate a list of Dates, from the first SubmissionDate, through today. 
    --Note: Requires the use of: 'OPTION (MAXRECURSION 0)' to generate a list with more than 100 dates. 
    CTE_AutoDates AS 
    (Select Min(SubmissionDate) as FiscalDate 
     From Accounts 
     UNION ALL 
     SELECT DATEADD(Day, 1, FiscalDate) 
     FROM CTE_AutoDates 
     WHERE DATEADD(Day, 1, FiscalDate) <= GetDate() 
    ), 

    FiscalDates As 
    (SELECT FiscalDate, 
      DATEFROMPARTS(Year(FiscalDate), Month(FiscalDate), 1) as FiscalMonthStartDate 
     FROM CTE_AutoDates 
     --Optionaly filter Fiscal Dates by the last known Math.Max(SubmissionDate, TranDate) 
     Where FiscalDate <= (Select Max(MaxDate) 
          From (Select Max(SubmissionDate) as MaxDate From Accounts 
           Union All 
           Select Max(TranDate) as MaxDate From Trans 
           ) as MaxDates 
         ) 
    ), 

    FiscalMonths as 
    (SELECT Distinct FiscalMonthStartDate 
     FROM FiscalDates 
    ), 

    --Matrix to store the reporting date groupings for the Account submission and payment periods. 
    SubmissionAndTranMonths AS 
    (Select AM.FiscalMonthStartDate as SubmissionMonthStartDate, 
      TM.FiscalMonthStartDate as TransMonthStartDate, 
      DateDiff(Month, (Select Min(FiscalMonthStartDate) From FiscalMonths), TM.FiscalMonthStartDate) as TranSummaryMonthNum 
     From FiscalMonths AS AM 
      Join FiscalMonths AS TM 
      ON TM.FiscalMonthStartDate >= AM.FiscalMonthStartDate 
    ), 

    AccountData as 
    (Select A.AccountID, 
      A.Amount, 
      FD.FiscalMonthStartDate as SubmissionMonthStartDate 
     From Accounts as A 
      Inner Join FiscalDates as FD 
      ON A.SubmissionDate = FD.FiscalDate 
    ), 


    TranData as 
    (Select T.AccountID, 
      T.TranAmount, 
      AD.SubmissionMonthStartDate, 
      FD.FiscalMonthStartDate as TranMonthStartDate 
     From Trans as T 
      Inner Join AccountData as AD 
      ON T.AccountID = AD.AccountID 
      Inner Join FiscalDates AS FD 
      ON T.TranDate = FD.FiscalDate 
    ), 

    AccountSummaryByMonth As 
    (Select ASM.FiscalMonthStartDate, 
      Sum(AD.Amount) as TotalSubmissionAmount 
     From FiscalMonths as ASM 
      Inner Join AccountData as AD 
      ON ASM.FiscalMonthStartDate = AD.SubmissionMonthStartDate 
     Group By 
      ASM.FiscalMonthStartDate 
    ), 

    TranSummaryByMonth As 
    (Select STM.SubmissionMonthStartDate, 
      STM.TransMonthStartDate, 
      STM.TranSummaryMonthNum, 
      Sum(TD.TranAmount) as TotalTranAmount 
     From SubmissionAndTranMonths as STM 
      Inner Join TranData as TD 
      ON STM.SubmissionMonthStartDate = TD.SubmissionMonthStartDate 
       AND STM.TransMonthStartDate = TD.TranMonthStartDate 
     Group By 
      STM.SubmissionMonthStartDate, 
      STM.TransMonthStartDate, 
      STM.TranSummaryMonthNum 
    ) 

--#Inspect 1 
--Select * From SubmissionAndTranMonths 
--OPTION (MAXRECURSION 0) 

--#Inspect 1 Results 
--SubmissionMonthStartDate TransMonthStartDate TranSummaryMonthNum 
--2012-01-01    2012-01-01   0 
--2012-01-01    2012-02-01   1 
--2012-01-01    2012-03-01   2 
--2012-01-01    2012-04-01   3 
--2012-02-01    2012-02-01   1 
--2012-02-01    2012-03-01   2 
--2012-02-01    2012-04-01   3 
--2012-03-01    2012-03-01   2 
--2012-03-01    2012-04-01   3 
--2012-04-01    2012-04-01   3 

--#Inspect 2 
--Select * From AccountSummaryByMonth 
--OPTION (MAXRECURSION 0) 

--#Inspect 2 Results 
--FiscalMonthStartDate TotalSubmissionAmount 
--2012-01-01   2099.00 
--2012-02-01   350.00 
--2012-03-01   685.00 

--#Inspect 3 
--Select * From TranSummaryByMonth 
--OPTION (MAXRECURSION 0) 

--#Inspect 3 Results 
--SubmissionMonthStartDate TransMonthStartDate TranSummaryMonthNum TotalTranAmount 
--2012-01-01    2012-01-01   0     300.00 
--2012-01-01    2012-02-01   1     300.00 
--2012-01-01    2012-03-01   2     300.00 
--2012-02-01    2012-02-01   1     325.00 
--2012-02-01    2012-04-01   3     25.00 
--2012-03-01    2012-03-01   2     656.00 
--2012-03-01    2012-04-01   3     15.00 

Select STM.SubmissionMonthStartDate, 
     ASM.TotalSubmissionAmount, 
     STM.TransMonthStartDate, 
     STM.TranSummaryMonthNum, 
     TSM.TotalTranAmount 
From SubmissionAndTranMonths as STM 
    Inner Join AccountSummaryByMonth as ASM 
     ON STM.SubmissionMonthStartDate = ASM.FiscalMonthStartDate 
    Left Join TranSummaryByMonth AS TSM 
     ON STM.SubmissionMonthStartDate = TSM.SubmissionMonthStartDate 
      AND STM.TransMonthStartDate = TSM.TransMonthStartDate 
Order By STM.SubmissionMonthStartDate, STM.TranSummaryMonthNum 
OPTION (MAXRECURSION 0) 

--#Results 
--SubmissionMonthStartDate TotalSubmissionAmount TransMonthStartDate TranSummaryMonthNum TotalTranAmount 
--2012-01-01    2099.00    2012-01-01   0     300.00 
--2012-01-01    2099.00    2012-02-01   1     300.00 
--2012-01-01    2099.00    2012-03-01   2     300.00 
--2012-01-01    2099.00    2012-04-01   3     NULL 
--2012-02-01    350.00    2012-02-01   1     325.00 
--2012-02-01    350.00    2012-03-01   2     NULL 
--2012-02-01    350.00    2012-04-01   3     25.00 
--2012-03-01    685.00    2012-03-01   2     656.00 
--2012-03-01    685.00    2012-04-01   3     15.00 
+3

Estimado Nathan, si alguien le ayudó sustancialmente con su consulta, entonces, en mi opinión, debe seleccionar la respuesta de esa persona en lugar de proporcionar la suya y seleccionarla. – ErikE

1

La siguiente consulta prácticamente devuelve lo que desea. Debe realizar las operaciones por separado. Acabo de unir los resultados:

select a.yyyymm, a.Amount, 
     t201201, t201202, t201203, t201204 
from (select LEFT(convert(varchar(255), a.submissiondate, 121), 7) as yyyymm, 
       SUM(a.Amount) as amount 
     from Accounts a 
     group by LEFT(convert(varchar(255), a.submissiondate, 121), 7) 
    ) a left outer join 
     (select LEFT(convert(varchar(255), a.submissiondate, 121), 7) as yyyymm, 
       sum(case when trans_yyyymm = '2012-01' then tranamount end) as t201201, 
       sum(case when trans_yyyymm = '2012-02' then tranamount end) as t201202, 
       sum(case when trans_yyyymm = '2012-03' then tranamount end) as t201203, 
       sum(case when trans_yyyymm = '2012-04' then tranamount end) as t201204 
     from Accounts a join 
      (select t.*, LEFT(convert(varchar(255), t.trandate, 121), 7) as trans_yyyymm 
      from trans t 
      ) t 
      on a.accountid = t.accountid 
     group by LEFT(convert(varchar(255), a.submissiondate, 121), 7) 
    ) t 
     on a.yyyymm = t.yyyymm 
order by 1 

Obtengo un NULL donde tiene un 0.00 en dos celdas.

+0

Los puntos finales del rango de fechas no se conocen en el momento de la consulta. Cada vez que se ejecuta la consulta, se generará una lista de filas para todos los meses conocidos de Envío de cuenta, junto con una cantidad fija o potencialmente variable de columnas de resumen de mes de pago. Algunos nulos adicionales no son un gran problema. – Nathan

5

Dado que está utilizando SQL Server 2012, podemos usar la función de Formato para que la fecha sea bonita. No hay necesidad de agrupar por cadenas. En su lugar, me resulta útil utilizar el tipo de datos adecuado durante el tiempo que puedo y solo uso Formato o Convertir en pantalla (o no lo hago en absoluto y dejo que el nivel intermedio maneje la pantalla).

En esta solución, asumí arbitrariamente la TransDate más antigua y la extraje de ella, el primer día de ese mes. Sin embargo, uno podría reemplazar fácilmente esa expresión con un valor estático de la fecha de inicio deseada y esta solución tomaría eso y los próximos 12 meses.

With SubmissionMonths As 
    (
    Select DateAdd(d, -Day(A.SubmissionDate) + 1, A.SubmissionDate) As SubmissionMonth 
    , A.Amount 
    From dbo.Accounts As A 
) 
    , TranMonths As 
    (
    Select DateAdd(d, -Day(Min(T.TranDate)) + 1, Min(T.TranDate)) As TranMonth 
     , 1 As MonthNum 
    From dbo.Accounts As A 
    Join dbo.Trans As T 
     On T.AccountId = A.AccountId 
    Join SubmissionMonths As M 
     On A.SubmissionDate >= M.SubmissionMonth 
     And A.SubmissionDate < DateAdd(m,1,SubmissionMonth) 
    Union All 
    Select DateAdd(m, 1, TranMonth), MonthNum + 1 
    From TranMonths 
    Where MonthNum < 12 
) 
    , TotalBySubmissionMonth As 
    (
    Select M.SubmissionMonth, Sum(M.Amount) As Total 
    From SubmissionMonths As M 
    Group By M.SubmissionMonth 
) 
Select Format(SMT.SubmissionMonth,'yyyy-MM') As SubmissionMonth, SMT.Total 
    , Sum(Case When TM.MonthNum = 1 Then T.TranAmount End) As Month1 
    , Sum(Case When TM.MonthNum = 2 Then T.TranAmount End) As Month2 
    , Sum(Case When TM.MonthNum = 3 Then T.TranAmount End) As Month3 
    , Sum(Case When TM.MonthNum = 4 Then T.TranAmount End) As Month4 
    , Sum(Case When TM.MonthNum = 5 Then T.TranAmount End) As Month5 
    , Sum(Case When TM.MonthNum = 6 Then T.TranAmount End) As Month6 
    , Sum(Case When TM.MonthNum = 7 Then T.TranAmount End) As Month7 
    , Sum(Case When TM.MonthNum = 8 Then T.TranAmount End) As Month8 
    , Sum(Case When TM.MonthNum = 9 Then T.TranAmount End) As Month9 
    , Sum(Case When TM.MonthNum = 10 Then T.TranAmount End) As Month10 
    , Sum(Case When TM.MonthNum = 11 Then T.TranAmount End) As Month11 
    , Sum(Case When TM.MonthNum = 12 Then T.TranAmount End) As Month12 
From TotalBySubmissionMonth As SMT 
    Join dbo.Accounts As A 
    On A.SubmissionDate >= SMT.SubmissionMonth 
     And A.SubmissionDate < DateAdd(m,1,SMT.SubmissionMonth) 
    Join dbo.Trans As T 
    On T.AccountId = A.AccountId 
    Join TranMonths As TM 
    On T.TranDate >= TM.TranMonth 
     And T.TranDate < DateAdd(m,1,TM.TranMonth) 
Group By SMT.SubmissionMonth, SMT.Total 

SQL Fiddle version

+0

Esto se ve muy prometedor. Voy a jugar un poco con esto y ver si puedo usar esta implementación. Gracias. – Nathan

+0

+1 para una buena respuesta. Además, el OP debería haber marcado su respuesta como seleccionada porque fue instrumental para obtener una solución. – ErikE

1

La siguiente consulta duplica exactamente los resultados de your final query in your own answer pero no toma más de 1/30 de la CPU (o mejor), además es mucho más simple.

Si tuviera el tiempo & energía, estoy seguro de que podría encontrar aún más mejoras ... mi instinto me dice que tal vez no tenga que golpear la mesa Accounts tantas veces. Pero en cualquier caso, es una gran mejora y debería funcionar muy bien incluso para conjuntos de resultados muy grandes.

Ver the SqlFiddle for it.

WITH L0 AS (SELECT 1 N UNION ALL SELECT 1), 
L1 AS (SELECT 1 N FROM L0, L0 B), 
L2 AS (SELECT 1 N FROM L1, L1 B), 
L3 AS (SELECT 1 N FROM L2, L2 B), 
L4 AS (SELECT 1 N FROM L3, L2 B), 
Nums AS (SELECT N = Row_Number() OVER (ORDER BY (SELECT 1)) FROM L4), 
Anchor AS (
    SELECT MinDate = DateAdd(month, DateDiff(month, '20000101', Min(SubmissionDate)), '20000101') 
    FROM dbo.Accounts 
), 
MNums AS (
    SELECT N 
    FROM Nums 
    WHERE 
     N <= DateDiff(month, 
     (SELECT MinDate FROM Anchor), 
     (SELECT Max(TranDate) FROM dbo.Trans) 
    ) + 1 
), 
A AS (
    SELECT 
     AM.AccountMo, 
     Amount = Sum(A.Amount) 
    FROM 
     dbo.Accounts A 
     CROSS APPLY (
     SELECT DateAdd(month, DateDiff(month, '20000101', A.SubmissionDate), '20000101') 
    ) AM (AccountMo) 
    GROUP BY 
     AM.AccountMo 
), T AS (
    SELECT 
     AM.AccountMo, 
     TM.TranMo, 
     TotalTranAmount = Sum(T.TranAmount) 
    FROM 
     dbo.Accounts A 
     CROSS APPLY (
     SELECT DateAdd(month, DateDiff(month, '20000101', A.SubmissionDate), '20000101') 
    ) AM (AccountMo) 
     INNER JOIN dbo.Trans T 
     ON A.AccountID = T.AccountID 
     CROSS APPLY (
     SELECT DateAdd(month, DateDiff(month, '20000101', T.TranDate), '20000101') 
    ) TM (TranMo) 
    GROUP BY 
     AM.AccountMo, 
     TM.TranMo 
) 
SELECT 
    SubmissionStartMonth = A.AccountMo, 
    TotalSubmissionAmount = A.Amount, 
    M.TransMonth, 
    TransMonthNum = N.N - 1, 
    T.TotalTranAmount 
FROM 
    A 
    INNER JOIN MNums N 
     ON N.N >= DateDiff(month, (SELECT MinDate FROM Anchor), A.AccountMo) + 1 
    CROSS APPLY (
     SELECT TransMonth = DateAdd(month, N.N - 1, (SELECT MinDate FROM Anchor)) 
    ) M 
    LEFT JOIN T 
     ON A.AccountMo = T.AccountMo 
     AND M.TransMonth = T.TranMo 
ORDER BY 
    A.AccountMo, 
    M.TransMonth;