Řekněme, že mám tabulku A: BookingsPerPerson
Person_Id ArrivalDate DepartureDate
123456 2012-01-01 2012-01-04
213415 2012-01-02 2012-01-07
Co musím dosáhnout, je následující:
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
Systém je určen pro akce, takže každá rezervace hotelu může trvat něco mezi 1 až 15 dny, ale ne víc než to. Jakékoli nápady by byly velmi oceněny.
K provedení tohoto dotazu můžete použít funkci PIVOT
. Moje odpověď bude zahrnovat statickou i dynamickou verzi, protože někdy je snazší ji pochopit pomocí statické verze.
Statický pivot je, když pevně zakódujete všechny hodnoty, které chcete převést do sloupců.
-- 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;
Výsledky (viz SQL Fiddle S ukázko ):
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
Dynamická verze vygeneruje seznam hodnot, které se převedou do sloupců:
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)
Výsledky jsou stejné (viz SQL Fiddle S ukázko ):
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
Jsem stará škola a zjistím, že CASE
snadněji pracuje v mé hlavě než PIVOT
. Jsem si jistý, že se bluefeet brzy objeví a zahanbí mě, ale mezitím si můžete hrát s tímto dynamickým dotazem SQL. Předpokládejme, že vaše tabulky ukládají DATE
a ne DATETIME
(nebo ještě horší, 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;
Jeden z mála případů, BTW, kde bych mohl ospravedlnit pomocí dotazů BETWEEN
pro dotazy na časové období.
Co takhle vygenerovat seznam dat
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