SQL Berechnung der Anzahl der Tage des Aufenthalts nach Monat, nach Benutzer, nach Ort
-
27-09-2019 - |
Frage
Ich arbeite an einer Abfrage für eine Reha-Organisation, in der Mieter (Client / Patienten) leben in einem Gebäude, wenn sie ankommen, wie sie in ihrer Behandlung Fortschritte sie in ein anderes Gebäude bewegen und wie sie am Ende der Behandlung, die sie in einem dritten Gebäude ist.
Für Finanzierungszwecke müssen wir wissen, wie viele Nächte ein Mieter in jedem Gebäude in jedem Monat ausgegeben. Ich kann DateDiff verwenden, um die Gesamtzahl der Nächte zu bekommen, aber, wie ich für jeden Kunden in jedem Monat in jedem Gebäude die Gesamt erhalten?
Zum Beispiel ist John Smith in Gebäude A 9 / 12-11 / 3; bewegt sich auf Gebäude B 11 / 3-15; bewegt sich in Gebäude C auf und ist immer noch da: 11/15 - heute
Was Abfrage ein Ergebnis zurückgibt, die die Anzahl der Nächte zeigen, dass er in verbracht: Gebäude A in Septmeber, Oktober und November. Buidling B im November Gebäude C im November
Zwei Tabellen halten Name des Kunden, Gebäudenamen und Einzugsdatum und Auszugsdatum
CREATE TABLE [dbo].[clients](
[ID] [nvarchar](50) NULL,
[First_Name] [nvarchar](100) NULL,
[Last_Name] [nvarchar](100) NULL
) ON [PRIMARY]
--populate w/ two records
insert into clients (ID,First_name, Last_name)
values ('A2938', 'John', 'Smith')
insert into clients (ID,First_name, Last_name)
values ('A1398', 'Mary', 'Jones')
CREATE TABLE [dbo].[Buildings](
[ID_U] [nvarchar](50) NULL,
[Move_in_Date_Building_A] [datetime] NULL,
[Move_out_Date_Building_A] [datetime] NULL,
[Move_in_Date_Building_B] [datetime] NULL,
[Move_out_Date_Building_B] [datetime] NULL,
[Move_in_Date_Building_C] [datetime] NULL,
[Move_out_Date_Building_C] [datetime] NULL,
[Building_A] [nvarchar](50) NULL,
[Building_B] [nvarchar](50) NULL,
[Building_C] [nvarchar](50) NULL
) ON [PRIMARY]
-- Populate the tables with two records
insert into buildings (ID_U,Move_in_Date_Building_A,Move_out_Date_Building_A, Move_in_Date_Building_B,
Move_out_Date_Building_B, Move_in_Date_Building_C, Building_A, Building_B, Building_C)
VALUES ('A2938','2010-9-12', '2010-11-3','2010-11-3','2010-11-15', '2010-11-15', 'Kalgan', 'Rufus','Waylon')
insert into buildings (ID_U,Move_in_Date_Building_A,Building_A)
VALUES ('A1398','2010-10-6', 'Kalgan')
Danke für Ihre Hilfe.
Lösung
würde ich ein richtig normalisierte Datenbankschema verwenden, Ihre Gebäude Tabelle wie folgt nicht sinnvoll ist. Nach Spaltung es bis ich glaube, dass Ihre Antwort immer ziemlich einfach sein wird.
Edit (und aktualisiert): Hier ist ein CTE, die diese seltsame Tabellenstruktur nehmen und es in eine normalisierte Form aufgeteilt, die Benutzer-ID angezeigt wird, Gebäudenamen, bewegen sich in und Daten ausrücken. Durch die Gruppierung auf die, die Sie wollen (und mit DATEPART()
etc.) sollten Sie die Daten in der Lage sein, das Sie mit, dass benötigen.
WITH User_Stays AS (
SELECT
ID_U,
Building_A Building,
Move_in_Date_Building_A Move_In,
COALESCE(Move_out_Date_Building_A, CASE WHEN ((Move_in_Date_Building_B IS NULL) OR (Move_in_Date_Building_C<Move_in_Date_Building_B)) AND (Move_in_Date_Building_C>Move_in_Date_Building_A) THEN Move_in_Date_Building_C WHEN Move_in_Date_Building_B>=Move_in_Date_Building_A THEN Move_in_Date_Building_B END, GETDATE()) Move_Out
FROM dbo.Buildings
WHERE Move_in_Date_Building_A IS NOT NULL
UNION ALL
SELECT
ID_U,
Building_B,
Move_in_Date_Building_B,
COALESCE(Move_out_Date_Building_B, CASE WHEN ((Move_in_Date_Building_A IS NULL) OR (Move_in_Date_Building_C<Move_in_Date_Building_A)) AND (Move_in_Date_Building_C>Move_in_Date_Building_B) THEN Move_in_Date_Building_C WHEN Move_in_Date_Building_A>=Move_in_Date_Building_B THEN Move_in_Date_Building_A END, GETDATE())
FROM dbo.Buildings
WHERE Move_in_Date_Building_B IS NOT NULL
UNION ALL
SELECT
ID_U,
Building_C,
Move_in_Date_Building_C,
COALESCE(Move_out_Date_Building_C, CASE WHEN ((Move_in_Date_Building_B IS NULL) OR (Move_in_Date_Building_A<Move_in_Date_Building_B)) AND (Move_in_Date_Building_A>Move_in_Date_Building_C) THEN Move_in_Date_Building_A WHEN Move_in_Date_Building_B>=Move_in_Date_Building_C THEN Move_in_Date_Building_B END, GETDATE())
FROM dbo.Buildings
WHERE Move_in_Date_Building_C IS NOT NULL
)
SELECT *
FROM User_Stays
ORDER BY ID_U, Move_In
Diese Abfrage Lauf bei Ihren Testdaten erzeugt er folgende Ausgabe:
ID_U Building Move_In Move_Out
-------- ----------- ----------------------- -----------------------
A1398 Kalgan 2010-10-06 00:00:00.000 2010-11-23 18:35:59.050
A2938 Kalgan 2010-09-12 00:00:00.000 2010-11-03 00:00:00.000
A2938 Rufus 2010-11-03 00:00:00.000 2010-11-15 00:00:00.000
A2938 Waylon 2010-11-15 00:00:00.000 2010-11-23 18:35:59.050
(4 row(s) affected)
Wie Sie sehen können, von hier es viel einfacher sein wird, die Tage pro Patient oder Gebäude, zu isolieren und auch die Datensätze für bestimmte Monate zu finden und die richtige Aufenthaltsdauer in diesem Fall zu berechnen. Beachten Sie, dass die CTE zeigt das aktuelle Datum für die Patienten, die noch in einem Gebäude befinden.
Edit (wieder): Um alle Monate einschließlich ihrer Start- und Enddaten für alle Jahre zu erhalten, können Sie einen CTE wie folgt verwendet werden:
WITH User_Stays AS (
[...see above...]
)
,
Months AS (
SELECT m.IX,
y.[Year], dateadd(month,(12*y.[Year])-22801+m.ix,0) StartDate, dateadd(second, -1, dateadd(month,(12*y.[Year])-22800+m.ix,0)) EndDate
FROM (
SELECT 1 IX UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9 UNION ALL
SELECT 10 UNION ALL
SELECT 11 UNION ALL
SELECT 12
)
m
CROSS JOIN (
SELECT Datepart(YEAR, us.Move_In) [Year]
FROM User_Stays us UNION
SELECT Datepart(YEAR, us.Move_Out)
FROM User_Stays us
)
y
)
SELECT *
FROM months;
So, da wir nun eine tabellarische Darstellung aller Datenbereiche haben, die von Interesse sein kann, wir dies einfach miteinander verbinden:
WITH User_Stays AS ([...]),
Months AS ([...])
SELECT m.[Year],
DATENAME(MONTH, m.StartDate) [Month],
us.ID_U,
us.Building,
DATEDIFF(DAY, CASE WHEN us.Move_In>m.StartDate THEN us.Move_In ELSE m.StartDate END, CASE WHEN us.Move_Out<m.EndDate THEN us.Move_Out ELSE DATEADD(DAY, -1, m.EndDate) END) Days
FROM Months m
JOIN User_Stays us ON (us.Move_In < m.EndDate) AND (us.Move_Out >= m.StartDate)
ORDER BY m.[Year],
us.ID_U,
m.Ix,
us.Move_In
Welche endlich diese Ausgabe erzeugt:
Year Month ID_U Building Days
----------- ------------ -------- ---------- -----------
2010 October A1398 Kalgan 25
2010 November A1398 Kalgan 22
2010 September A2938 Kalgan 18
2010 October A2938 Kalgan 30
2010 November A2938 Kalgan 2
2010 November A2938 Rufus 12
2010 November A2938 Waylon 8
Andere Tipps
- stellen Sie die Termine für den Monat Sie
wollenDeclare @startDate datetime
declare @endDate datetime
set @StartDate = '09/01/2010'
set @EndDate = '09/30/2010'
select
-- determine if the stay occurred during this month
Case When @StartDate <= Move_out_Date_Building_A and @EndDate >= Move_in_Date_Building_A
Then
(DateDiff(d, @StartDate , @enddate+1)
)
-- drop the days off the front
- (Case When @StartDate < Move_in_Date_Building_A
Then datediff(d, @StartDate, Move_in_Date_Building_A)
Else 0
End)
--drop the days of the end
- (Case When @EndDate > Move_out_Date_Building_A
Then datediff(d, @EndDate, Move_out_Date_Building_A)
Else 0
End)
Else 0
End AS Building_A_Days_Stayed
from Clients c
inner join Buildings b
on c.id = b.id_u
Versuchen Sie, ein Datum Tabelle. Zum Beispiel könnte man eine wie so erstellen:
CREATE TABLE Dates
(
[date] datetime,
[year] smallint,
[month] tinyint,
[day] tinyint
)
INSERT INTO Dates(date)
SELECT dateadd(yy, 100, cast(row_number() over(order by s1.object_id) as datetime))
FROM sys.objects s1
CROSS JOIN sys.objects s2
UPDATE Dates
SET [year] = year(date),
[month] = month(date),
[day] = day(date)
ändern Sie einfach die Anfangspopulation Termine Ihre Bedürfnisse zu erfüllen (auf meiner Testinstanz, die oben ergab stammt aus 2000.01.02 bis 2015.10.26). Mit einem Daten Tabelle ist die Abfrage ziemlich geradlinig, so etwas wie folgt aus:
select c.First_name, c.Last_name,
b.Building_A BuildingName, dA.year, dA.month, count(distinct dA.day) daysInBuilding
from clients c
join Buildings b on c.ID = b.ID_U
left join Dates dA on dA.date between b.Move_in_Date_Building_A and isnull(b.Move_out_Date_Building_A, getDate())
group by c.First_name, c.Last_name,
b.Building_A, dA.year, dA.month
UNION
select c.First_name, c.Last_name,
b.Building_B, dB.year, dB.month, count(distinct dB.day)
from clients c
join Buildings b on c.ID = b.ID_U
left join Dates dB on dB.date between b.Move_in_Date_Building_B and isnull(b.Move_out_Date_Building_B, getDate())
group by c.First_name, c.Last_name,
b.Building_B, dB.year, dB.month
UNION
select c.First_name, c.Last_name,
b.Building_C, dC.year, dC.month, count(distinct dC.day)
from clients c
join Buildings b on c.ID = b.ID_U
left join Dates dC on dC.date between b.Move_in_Date_Building_C and isnull(b.Move_out_Date_Building_C, getDate())
group by c.First_name, c.Last_name,
b.Building_C, dC.year, dC.month
Wenn Sie die Gebäude Tabelle nicht umstrukturieren können Sie eine Abfrage erstellen können, die es für Sie normalisieren und für eine einfachere Berechnungen erlauben:
SELECT "A" as Building, BuidlingA as Name, Move_in_Date_Building_A as MoveInDate,
Move_out_Date_Building_A As MoveOutDate
UNION
SELECT "B", BuidlingB, Move_in_Date_Building_B, Move_out_Date_Building_B
UNION
SELECT "C", BuidlingC, Move_in_Date_Building_C, Move_out_Date_Building_C