Search code examples
sql-serverpivot-tabledynamic-tables

Get Total on SQL Dynamic (Calendar) Pivot Tables


I am trying to get an SQL Server Dynamic Pivot table to work that allows me to count and then sum the number of columns. The purpose of the pivot table is to create a report of all the days individuals were staying in a city and the total number of days(in a month). So, for example, Person A was staying everyday in June - the total will be 30.Person B only started staying on the 3rd of June - the total will be 27 etc. The data table only consists of Name, ArriveDate, DepartDate...the days of the month are created through an SQL query.

+------+------------+------------+-------+-------+-------+-----+-------+-------+-------+
| Name | ArriveDate | DepartDate | 06-01 | 06-02 | 06-03 | ... | 06-29 | 06-30 | Total |
+------+------------+------------+-------+-------+-------+-----+-------+-------+-------+
| A    | 2014-06-01 | 2014-06-23 |     1 |     1 |     1 | ... |     1 |     1 |    30 |
| B    | 2014-06-02 | 2014-06-23 |     0 |     1 |     1 | ... |     1 |     1 |    27 |
| C    | 2014-06-02 | 2014-06-23 |     0 |     0 |     0 | ... |     1 |     1 |    16 |
+------+------------+------------+-------+-------+-------+-----+-------+-------+-------+

Here is the query I have so far:

DROP TABLE #tempDates

DECLARE @cols AS NVARCHAR(MAX),
        @query  AS NVARCHAR(MAX)

;WITH cte (datelist, maxdate) AS
(
    SELECT MIN(ArriveDate) datelist,
           EOMONTH(GETDATE()) AS maxdate
    FROM Reservation
    UNION ALL
    SELECT dateadd(dd, 1, datelist), maxdate
    FROM cte
    WHERE datelist < maxdate
) 
SELECT c.datelist
INTO #tempDates
FROM cte c

SELECT @cols = STUFF(( SELECT distinct ',' + QUOTENAME(convert(CHAR(10), datelist, 120)) 
                       FROM #tempDates
                       FOR XML PATH(''), TYPE
                      ).value('.', 'NVARCHAR(MAX)') 
                     ,1,1,'')



SET @query = 'SELECT ID,
                     ArriveDate,
                     DepartDate,                     
                     ' + @cols + '
              FROM 
              (         
                SELECT r.ID,
                       r.ArriveDate,
                       r.DepartDate,
                       d.datelist, 
                       convert(CHAR(10), datelist, 120) PivotDate
                FROM 
                Reservation r       
                LEFT JOIN
                #tempDates d
                    ON d.datelist BETWEEN rg.ArriveDate AND GETDATE()
            ) x
            pivot 
            (
                COUNT(datelist)
                FOR PivotDate IN (' + @cols + ')
            ) p '

EXECUTE (@query)

Solution

  • Here is my attempt using a dynamic crosstab:

    SQL Fiddle

    Sample Data:

    Name ArriveDate DepartDate
    ---- ---------- ----------
    A    2015-07-01 2015-07-23
    B    2015-07-02 2015-07-04
    C    2015-07-03 2015-07-31
    

    Dynamic Crosstab Solution:

    DECLARE @minDate AS DATE,
            @maxDate AS DATE
    
    SELECT  @minDate = DATEADD(DAY, 1, EOMONTH(GETDATE(), -1)),
            @maxDate = EOMONTH(GETDATE())
    
    CREATE TABLE #dates(dt DATE)
    
    DECLARE @sql1 VARCHAR(MAX) = '',
            @sql2 VARCHAR(MAX) = '',
            @sql3 VARCHAR(MAX) = '';
    
    WITH E1(N) AS(
        SELECT 1 FROM(VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))t(N)
    ),
    E2(N) AS(SELECT 1 FROM E1 a CROSS JOIN E1 b),
    E4(N) AS(SELECT 1 FROM E2 a CROSS JOIN E2 b),
    E8(N) AS(SELECT 1 FROM E4 a CROSS JOIN E4 b),
    Tally(N) AS(
        SELECT TOP(DATEDIFF(DAY, @minDate, @maxDate) + 1)
            ROW_NUMBER() OVER(ORDER BY (SELECT NULL))
        FROM E8
    )
    INSERT INTO #dates
        SELECT DATEADD(DAY, N - 1, @minDate)
        FROM Tally
    
    SELECT @sql1 =
    'SELECT
        r.Name
        , r.ArriveDate
        , r.DepartDate' + CHAR(10)
    
    SELECT @sql2 = @sql2 +
    '   , SUM(CASE WHEN d.dt = CAST(''' + CONVERT(VARCHAR(8), dt, 112) + ''' AS DATE) THEN 1 ELSE 0 END) AS ' 
        + QUOTENAME(CONVERT(VARCHAR(10), dt, 120)) + CHAR(10)
    FROM #dates
    ORDER BY dt
    
    SELECT @sql2 = @sql2 +
    '   , COUNT(d.dt) AS [total]' + CHAR(10)
    
    SELECT @sql3 =
    'FROM Reservation r
    LEFT JOIN #dates d
        ON d.dt BETWEEN r.ArriveDate AND r.DepartDate
    GROUP BY
        r.Name, r.ArriveDate, r.DepartDate'
    
    PRINT (@sql1 + @sql2 + @sql3)
    EXEC (@sql1 + @sql2 + @sql3)
    
    DROP TABLE #dates
    

    Result:

    Name ArriveDate DepartDate 2015-07-01  2015-07-02  2015-07-03  ..... 2015-07-29  2015-07-30  2015-07-31  total
    ---- ---------- ---------- ----------- ----------- ----------- ..... ----------- ----------- ----------- -----------
    A    2015-07-01 2015-07-23 1           1           1           ..... 0           0           0           23
    B    2015-07-02 2015-07-04 0           1           1           ..... 0           0           0           3
    C    2015-07-03 2015-07-31 0           0           1           ..... 1           1           1           29