2010-11-23 5 views
3

Ich arbeite an einer Abfrage für eine Reha-Organisation, wo Mieter (Client/Patienten) in einem Gebäude leben, wenn sie zuerst ankommen, wie sie in ihrem Fortschritt Behandlung sie bewegen sich zu einem anderen Gebäude und wie sie gegen Ende der Behandlung sind sie in einem dritten Gebäude.SQL berechnen Anzahl der Tage des Aufenthalts nach Monat, nach Benutzer, nach Standort

Für Finanzierungszwecke müssen wir wissen, wie viele Nächte ein Mieter in jedem Gebäude in jedem Monat verbrachte. Ich kann DateDiff verwenden, um die Gesamtzahl der Nächte zu erhalten, aber wie bekomme ich die Summe für jeden Kunden in jedem Monat in jedem Gebäude?

Zum Beispiel ist John Smith in Gebäude A 9/12-11/3; Umzug in Gebäude B 11/3-15; bewegt sich zu Gebäude C auf und ist immer noch da: 11/15 - heute

Welche Abfrage gibt ein Ergebnis zurück, das die Anzahl der Nächte anzeigt, die er verbrachte: Gebäude A im September, Oktober und November. Buidling B im November Gebäude C in

November halten

Zwei Tabellen des Namen des Clients, den Namen Aufbau und datum einziehen und Datum

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 bewegen-out.

Antwort

2

Ich würde ein ordnungsgemäß normalisiertes Datenbankschema verwenden, Ihre Buildings-Tabelle ist nicht so nützlich. Nach dem Aufteilen glaube ich, dass es ziemlich einfach ist, deine Antwort zu bekommen.


Edit (und aktualisiert): Hier ist ein CTE, die diese seltsame Tabellenstruktur nehmen und teilen Sie es in eine normalisierte Form, die Benutzer-ID angezeigt wird, den Namen erstellen, bewegen und Daten ausrücken. Durch Gruppierung auf die gewünschten (und mit DATEPART() usw.) sollten Sie in der Lage sein, die Daten, die Sie benötigen, damit zu bekommen.

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 läuft auf Ihren Beispieldaten 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 hier sehen können, auf es wird viel einfacher sein, die Tage pro Patient oder Gebäude, zu isolieren und auch um die Aufzeichnungen für bestimmte Monate zu finden und die korrekte Aufenthaltsdauer in diesem Fall zu berechnen. Beachten Sie, dass der CTE das aktuelle Datum für Patienten anzeigt, die sich 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 verwenden:

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 jetzt eine haben tabellarische Darstellung aller Datenbereiche, die von Interesse sein können, kommen wir einfach diese zusammen:

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 

die schließlich diese Ausgabe erzeugt:

012.351.
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 
+0

Die einzige Herausforderung, die ich mit der Normalisierung der Datenbank habe, ist, dass es nicht meine Datenbank ist. Ich werde dafür drängen. Wenn es andere Ideen gibt, wie ich mit dem fortfahren soll, was ich habe, würde ich sie gerne hören. – Stan

+0

Ich habe eine Abfrage mit einem CTE hinzugefügt, die Ihnen den Einstieg erleichtern soll. – Lucero

+1

Das Schöne an dieser Lösung ist, dass keine Änderung an der Datenbank erforderlich ist. Sie führen eine Abfrage aus, die diese für Sie normalisiert und dann diese Abfrage als Quelle verwendet. – Leslie

0

- setzten die Termine für den Monat Sie

wollen
Declare @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 
0

Versuchen Sie ein Datum Tabelle.

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, ergab die oben genannten Daten von 2000-01-02 bis 2015.10.26)

: Zum Beispiel, Sie ein wie so schaffen könnten . Mit einem Daten Tabelle ist die Abfrage ziemlich geradlinig, etwa wie folgt:

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 
0

Wenn Sie nicht die Gebäude Tabelle restrukturieren können, können Sie eine Abfrage erstellen, 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