2009-08-25 10 views
2

Wir extrahieren Buchungsinformationen für unsere Tennisplätze aus unserer SQL-Datenbank in eine einfache Ergebnistabelle, die uns hilft, ein Bild von der Gerichtsverwendung zu erstellen. Es ist ziemlich einfach, außer wenn es um Buchungen geht, die mehr als eine Stunde dauern.Einzelne oder mehrere INSERTs basierend auf Werten SELECTed

Gegenwärtig ergibt jede Buchung eine Zeile in unserer Ergebnistabelle. Jede Zeile enthält eine Startzeit, eine Dauer und eine Gerichtsnummer. Wir möchten diese Tabelle direkt in eine Tabellenkalkulation oder einen Pivot-Tisch kartieren, damit wir sehen können, wie viele Stunden unsere Plätze belegt sind und zu welchen Stunden des Tages.

Derzeit unsere SQL-Abfrage sieht ungefähr so ​​aus:

INSERT INTO Results (year, month, day, hour, duration, court) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime), 
     a.Duration, 
     a.Court 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 

Unser Problem ist, dass Buchungen für 2, 3 oder mehr Stunden Dauer nur eine Zeile in der Ergebnistabelle, dh. für die erste Stunde der Buchung. Dies liegt daran, dass die Länge der Buchung in den Dauerdaten erfasst wird. Wir könnten die Daten nachbearbeiten, um unsere Ziele zu erreichen, aber es wäre einfacher, wenn dies in unserer SQL-Abfrage möglich wäre.

Kann diese Abfrage in irgendeiner Weise geändert werden, so dass abhängig von der Dauer (die 1, 2, 3, ... Stunden sein kann) die entsprechende Anzahl von Zeilen in die Ergebnistabelle mit der Dauer 1 eingefügt wird Somit würde eine 3-Stunden-Buchung, die um 9 Uhr beginnt, zu drei Reihen in der Ergebnistabelle führen, eine um 9 Uhr, eine um 10 Uhr und eine um 11 Uhr, jede von einer Dauer von 1 Stunde.

Anstatt also die folgende Zeile in der Ergebnistabelle:

Year, Month, Day, Hour, Duration, Court 
2009, 08, 25, 09,  3,  1 

wir die folgenden Zeilen erhalten:

Year, Month, Day, Hour, Duration, Court 
2009, 08, 25, 09,  1,  1 
2009, 08, 25, 10,  1,  1 
2009, 08, 25, 11,  1,  1 

Dies würde die Ergebnistabelle in eine Tabelle machen Mapping viel einfacher.

UPDATE 2009-08-25: Natürlich, wie die ersten paar Antworten zeigen, muss es nicht eine einzige Abfrage sein. Ein Set ist in Ordnung.

UPDATE 2009-08-26: Seitwärts verfolgt und hatte noch keine Chance, die vorgeschlagenen Lösungen auszuprobieren. Ich hoffe, dass ich das bald tun werde und eine Antwort basierend auf den Ergebnissen auswählen werde.

UPDATE 2009-08-27: Endlich eine Chance, die Lösungen auszuprobieren. Die Tabelle der Ganzzahlen und der Verbindung, um eine Lösung zu erzeugen, war ein Augenöffner. Vor allem die Verwendung von Kreuzverbindungen, um eine solche Tabelle zu erstellen. Dies ist wahrscheinlich die sauberere, SQL-Art, Dinge zu tun.

Am Ende ging ich jedoch mit Aarons Lösung mit der Flagge und dem einfachen Algorithmus. Ich habe es verbessert, indem ich seinen Algorithmus in eine while-Schleife gepackt habe, um weiter zu iterieren, bis keine Dauer> 1 mehr übrig ist. Dies war schnell und einfach zu implementieren. Es zeigte auch, dass wir 10 Stunden gebucht hatten, also musste ich hier kein Limit programmieren.

Ich sollte beachten, dass ich Jeffs Idee der maximalen Dauer in den While-Loop-Zähler aufgenommen habe, anstatt meine ursprüngliche Idee, die Elemente mit Dauer> 1 zu zählen. Etwas weniger Code.

+0

Welche Version von SQL Server? –

+0

SQL Server 2000. Wird das einen Unterschied machen? – dave

+0

Es gibt einige Funktionen in SQL 2005 und höher, die diese Art der Abfrage erleichtern. –

Antwort

0

Es ist nicht trivial.Zunächst müssen Sie eine weitere Spalte „Flag“, die 0:

INSERT INTO Results (year, month, day, hour, duration, court, Flag) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime), 
     a.Duration, 
     a.Court, 
     0 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 

Sie müssen diese Fragen mehrmals auszuführen:

-- Copy all rows with duration > 1 and set the flag to 1 
insert into results(year, month, day, hour, duration, court, Flag) 
select year, month, day, hour+1, duration-1, court, 1 
from result 
where duration > 1 
; 
-- Set the duration of all copied rows to 1 
update result 
set duration = 1 
where flag = 0 and duration > 1 
; 
-- Prepare the copies for the next round 
update result 
set flag = 0 
where flag = 1 

Dies erzeugt einen zusätzlichen Eintrag für jeden duration > 1. Meine Vermutung ist, dass Sie ein Gericht für mehr als 8 Stunden nicht zuordnen können, also müssen Sie nur diese drei 8 Mal ausführen, um alle von ihnen zu beheben.

+0

Ich denke, ich folge diesem. Aber muss Ihre zweite Abfrage nicht die Dauer um 1 reduzieren, nicht genau auf 1 setzen? – dave

+0

@dave: Nein, es werden alle Prozessabfragen aus dem Bild genommen, indem die Dauer auf 1 gesetzt wird. Alle Ergebnisse mit "flag = 1" enthalten die neue "duration-1". Deshalb brauche ich die Flagge (um zu wissen, welche Dauer zu "löschen") –

+0

@Aaron, ich bekomme es jetzt. Sobald ich ein Beispiel in meinem Kopf begann, war es offensichtlich. Ich hatte angenommen, dass die "Iteration" auf der "Eltern" -Reihe basieren würde, dh. die ursprüngliche Zeile mit der Dauer> 1. Ihre Lösung erstellt jedoch eine untergeordnete Zeile zum Iterieren. Was ziemlich schlau ist. Vielleicht werde ich die ganze Sache in eine while-Schleife einpacken, die prüft, ob es Zeilen mit der Dauer> 1 gibt. Auf diese Weise muss ich mir keine Gedanken über die maximale Anzahl von Stunden machen, die für Iterationen gebucht werden können. – dave

1

redigierte die fehlende Stunde Berechnung

Erstellen Sie eine einzelne Spalte temporäre Tabelle mit n Zeilen für integer korrigieren n - (Ich habe angenommen, dass die maximale Buchungszeit 8 Stunden).

create table #t 
(id int 
,addHour int 
) 

insert #t 
select 1,0 
union all select 2,0 
union all select 2,1 
union all select 3,0 
union all select 3,1 
union all select 3,2 
union all select 4,0 
union all select 4,1 
union all select 4,2 
union all select 4,3 
union all select 5,0 
union all select 5,1 
union all select 5,2 
union all select 5,3 
union all select 5,4 
union all select 6,0 
union all select 6,1 
union all select 6,2 
union all select 6,3 
union all select 6,4 
union all select 6,5 
union all select 7,0 
union all select 7,1 
union all select 7,2 
union all select 7,3 
union all select 7,4 
union all select 7,5 
union all select 7,6 
union all select 8,0 
union all select 8,1 
union all select 8,2 
union all select 8,3 
union all select 8,4 
union all select 8,5 
union all select 8,6 
union all select 8,7 

Sie bestätigen können, dass die temporäre Tabelle, um die richtige Anzahl von Zeilen mit der folgenden Abfrage hat:

select id, count(1) 
from #t 
group by id 
order by id 

Ändern Sie Ihre Abfrage ein, um die temporäre Tabelle beitreten umfassen:

INSERT INTO Results (year, month, day, hour, duration, court) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime) + addHour, 
     1 AS Duration, 
     a.Court 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 
INNER JOIN #t AS t 
ON t.id = a.Duration 

EDIT - eine Erläuterung, wie dies funktioniert

Beim Verknüpfen von Tabellen wird in der Ausgabe für jede Kombination von verknüpften Zeilen in den Quelltabellen, die die Verknüpfungskriterien erfüllen, eine Zeile erzeugt.

Ich benutze die temporäre Tabelle, um die ursprüngliche Ergebnismenge aus Buchungen und Aktivitäten um die Anzahl der Stunden zu multiplizieren, die die Buchung dauert, indem Sie sich der Dauer anschließen. Dies funktioniert nur, wenn Buchungen in ganzen Stunden vorgenommen werden.

Wenn Sie dies deutlicher sehen mögen, fügen Sie eine zweite Spalte #T dem jede Zeile eindeutig identifiziert und schließen sie in der Ausgabeergebnismenge:

create table #t 
(id int 
,unique_id int identity 
) 

INSERT #t (id) 
select 1 
union all select 2 
... etc 

SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime) + addHour, 
     1 AS Duration, 
     a.Court, 
     t.unique_id 
FROM Bookings b 
INNER JOIN Activities a 
ON b.ActivityID = a.ID 
INNER JOIN #t AS t 
ON t.id = a.Duration 

Dies sollte klarstellen, dass jede Zeile in dem Ergebnis Set wird aus einer einzigen gültigen Kombination von Buchungen, Aktivitäten und #t erstellt.

+0

Können Sie ein wenig erklären, wie das funktioniert? Ich verstehe die Erstellung der temporären Tabelle, aber nicht, wie die Verknüpfung von #t das gewünschte Ergebnis erzeugt. – dave

+0

Dies gibt nicht die richtige Stunde in den Ergebnissen. –

+0

@Aaron - guter Punkt. Ich habe die Abfrage geändert, um dies zu behandeln. –

0

Sie könnten in Erwägung ziehen, einen INSTEAD OF INSERT-Trigger in die Tabelle "Results" zu setzen, die mehrere Zeilen für jede mehr als eine Stunde umfassende Buchung einfügt. Dies erhöht zwar die Komplexität, aber es könnte ein vernünftiger Ansatz sein, da dies nicht wie ein OLTP-System mit hohem Volumen klingt.

1

Eine leichte Modifikation zu Ihrem ursprünglichen genügt, wenn Sie eine integers Tabelle (oder VIEW) vorstellen als eine Reihe Generator zu dienen:

INSERT INTO Results (year, month, day, hour, duration, court) 
SELECT DATEPART (yy, b.StartDateTime), 
     DATEPART (mm, b.StartDateTime), 
     DATEPART (dd, b.StartDateTime), 
     DATEPART (hh, b.StartDateTime) + (a.Duration - i.iii - 1) 
     1, 
     a.Court 
FROM Bookings b 
INNER JOIN Activities a 
    ON b.ActivityID = a.ID 
INNER JOIN Integers999 i  -- N.B.: Integers999 (iii INT), all numbers 0 .. 999 
    ON a.Duration > i.iii;  -- So, a true Duration of 1 appears once, of 2 twice ... 
+0

Sie haben vergessen, die Startstunde zu korrigieren. –

+0

@Aaron, also tat ich, danke. Fest. – pilcrow

0

Ich habe keine Chance zu debuggen dies, aber etwas hatte wie Dies sollte es für Sie tun:

DECLARE @maxDuration INTEGER 
DECLARE @curDuration INTEGER 

SELECT @MaxDuration = SELECT MAX(Duration) FROM Activities 
SET @curDuration = 1 

WHILE @curDuration <= @MaxDuration 
BEGIN 
    INSERT INTO Results (year, month, day, hour, duration, court) 
    SELECT DATEPART (yy, b.StartDateTime), 
      DATEPART (mm, b.StartDateTime), 
      DATEPART (dd, b.StartDateTime), 
      DATEPART (hh, b.StartDateTime) + @curDuration - 1, 
      a.Duration, 
      a.Court 
    FROM Bookings b 
    INNER JOIN Activities a 
    ON b.ActivityID = a.ID 
    WHERE a.Duration <= @MaxDuration 

    SET @curDuration = @curDuration + 1 
END 
Verwandte Themen