web-dev-qa-db-fra.com

Renvoyer une colonne par date dans une plage

Disons que j'ai le tableau A: BookingsPerPerson

Person_Id    ArrivalDate    DepartureDate
123456       2012-01-01     2012-01-04
213415       2012-01-02     2012-01-07

Ce que je dois réaliser avec une vue est le suivant:

Person_Id    ArrivalDate    DepartureDate    Jan-01    Jan-02    Jan-03    Jan-04    Jan-05    Jan-06    Jan-07
123456       2012-01-01     2012-01-04       1         1         1         1
213415       2012-01-02     2012-01-07                 1         1         1         1         1         1

Le système est pour les événements, donc chaque réservation d'hôtel peut prendre entre 1 et 15 jours, mais pas plus. Toutes les idées seraient très appréciées.

15
Federico Giust

Vous pouvez utiliser la fonction PIVOT pour effectuer cette requête. Ma réponse inclura à la fois une version statique et une version dynamique car il est parfois plus facile de la comprendre en utilisant une version statique.

Un pivot statique est lorsque vous codez en dur toutes les valeurs que vous souhaitez transformer en colonnes.

-- first into into a #temp table the list of dates that you want to turn to columns
;with cte (datelist, maxdate) as
(
    select min(arrivaldate) datelist, max(departuredate) maxdate
    from BookingsPerPerson
    union all
    select dateadd(dd, 1, datelist), maxdate
    from cte
    where datelist < maxdate
) 
select c.datelist
into #tempDates
from cte c

select *
from
(
    select b.person_id, b.arrivaldate, b.departuredate,
        d.datelist,
        convert(CHAR(10), datelist, 120) PivotDate
    from #tempDates d
    left join BookingsPerPerson b
        on d.datelist between b.arrivaldate and b.departuredate
) x
pivot
(
    count(datelist)
    for PivotDate in ([2012-01-01], [2012-01-02], [2012-01-03],
              [2012-01-04], [2012-01-05], [2012-01-06] , [2012-01-07])
) p;

Résultats (Voir SQL Fiddle With Demo ):

PERSON_ID | ARRIVALDATE | DEPARTUREDATE | 2012-01-01 | 2012-01-02 | 2012-01-03 | 2012-01-04 | 2012-01-05 | 2012-01-06 | 2012-01-07
=====================================================================================================================================
123456    | 2012-01-01  | 2012-01-04    | 1          | 1          | 1          | 1          | 0          | 0          | 0
213415    | 2012-01-02  | 2012-01-07    | 0          | 1          | 1          | 1          | 1          | 1          | 1

La version dynamique générera la liste des valeurs à transformer en colonnes:

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

;with cte (datelist, maxdate) as
(
    select min(arrivaldate) datelist, max(departuredate) maxdate
    from BookingsPerPerson
    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 person_id, arrivaldate, departuredate, ' + @cols + ' from 
             (
                select b.person_id, b.arrivaldate, b.departuredate,
                    d.datelist,
                    convert(CHAR(10), datelist, 120) PivotDate
                from #tempDates d
                left join BookingsPerPerson b
                    on d.datelist between b.arrivaldate and b.departuredate
            ) x
            pivot 
            (
                count(datelist)
                for PivotDate in (' + @cols + ')
            ) p '

execute(@query)

Les résultats sont les mêmes (voir SQL Fiddle With Demo ):

PERSON_ID | ARRIVALDATE | DEPARTUREDATE | 2012-01-01 | 2012-01-02 | 2012-01-03 | 2012-01-04 | 2012-01-05 | 2012-01-06 | 2012-01-07
=====================================================================================================================================
123456    | 2012-01-01  | 2012-01-04    | 1          | 1          | 1          | 1          | 0          | 0          | 0
213415    | 2012-01-02  | 2012-01-07    | 0          | 1          | 1          | 1          | 1          | 1          | 1
27
Taryn

Je suis de la vieille école et je trouve CASE plus facile à travailler dans ma tête que PIVOT. Je suis sûr que Bluefeet apparaîtra sous peu et me fera honte, mais en attendant, vous pouvez jouer avec cette requête SQL dynamique. En supposant que votre table stocke DATE et non DATETIME (ou pire encore, VARCHAR):

USE tempdb;
GO

CREATE TABLE dbo.a
(
   Person_Id INT, 
   ArrivalDate DATE, 
   DepartureDate DATE
);

INSERT dbo.a SELECT 123456, '2012-01-01', '2012-01-04'
UNION ALL    SELECT 213415, '2012-01-02', '2012-01-07';

DECLARE @sql NVARCHAR(MAX) = N'SELECT Person_Id';

;WITH dr AS
(
  SELECT MinDate = MIN(ArrivalDate),
         MaxDate = MAX(DepartureDate)
  FROM dbo.a
),
n AS
(
  SELECT TOP (DATEDIFF(DAY, (SELECT MinDate FROM dr), (SELECT MaxDate FROM dr)) + 1)
   d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY [object_id])-1, 
     (SELECT MinDate FROM dr))
 FROM sys.all_objects
)
SELECT @sql += ',
  ' + QUOTENAME(d) + ' = CASE WHEN ''' + CONVERT(CHAR(10), d, 120) 
  + ''' BETWEEN ArrivalDate AND DepartureDate THEN ''1'' ELSE '''' END' FROM n;

SELECT @sql += ' FROM dbo.a;'

EXEC sp_executesql @sql;
GO

DROP TABLE dbo.a;

Un des très rares cas, BTW, où je pouvais justifier l'utilisation de BETWEEN pour les requêtes de plage de dates.

8
Aaron Bertrand

Que diriez-vous de cela pour générer une liste de dates

declare @Date01 as smalldatetime
declare @Date02 as smalldatetime
select @Date01 = min(periodstart), @Date02 = max(periodend)
    from BookingTable

declare @DateDiff as int
select @DateDiff = (select DATEDIFF(DAY, @Date01, @Date02))
;

WITH Tally (N) AS
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM sys.all_columns a CROSS JOIN sys.all_columns b
)
SELECT DATEADD(day, N, @Date01)
FROM Tally
where N <= @DateDiff
0
mko