Aggregate benachbarte nur Datensätze mit T-SQL
-
04-07-2019 - |
Frage
Ich habe (vereinfacht für das Beispiel) eine Tabelle mit den folgenden Daten
Row Start Finish ID Amount
--- --------- ---------- -- ------
1 2008-10-01 2008-10-02 01 10
2 2008-10-02 2008-10-03 02 20
3 2008-10-03 2008-10-04 01 38
4 2008-10-04 2008-10-05 01 23
5 2008-10-05 2008-10-06 03 14
6 2008-10-06 2008-10-07 02 3
7 2008-10-07 2008-10-08 02 8
8 2008-10-08 2008-11-08 03 19
Die Daten über einen Zeitraum in der Zeit darstellen, die ID ist der Staat ein System während dieser Zeit war und die Menge ist ein Wert in diesem Zustand zusammen.
Was ich tun möchte, ist es, die Beträge, die für neben Zeilen mit der gleichen ID-Nummer aggregieren, aber die gleiche Gesamtsequenz zu halten, so dass zusammenhängende Läufe kombiniert werden können. So mag ich mit Daten, um am Ende wie:
Row Start Finish ID Amount
--- --------- ---------- -- ------
1 2008-10-01 2008-10-02 01 10
2 2008-10-02 2008-10-03 02 20
3 2008-10-03 2008-10-05 01 61
4 2008-10-05 2008-10-06 03 14
5 2008-10-06 2008-10-08 02 11
6 2008-10-08 2008-11-08 03 19
Ich bin nach einer T-SQL-Lösung, die in eine SP gesetzt werden kann, aber ich kann nicht sehen, wie mit einfachen Abfragen zu tun. Ich vermute, dass es Iteration irgendeine Art erfordern kann, aber ich will nicht auf diesem Weg gehen.
Der Grund, warum ich diese Aggregation tun möchte, ist, dass der nächste Schritt in dem Prozess ist durch die eindeutige ID des, die innerhalb der Sequenz auftreten, eine SUM () und Count () gruppiert zu tun, damit meine Enddaten etwas aussehen wird :
ID Counts Total
-- ------ -----
01 2 71
02 2 31
03 2 33
Allerdings, wenn ich eine einfache tun
SELECT COUNT(ID), SUM(Amount) FROM data GROUP BY ID
Auf der ursprünglichen Tabelle bekomme ich so etwas wie
ID Counts Total
-- ------ -----
01 3 71
02 3 31
03 2 33
Was ist nicht das, was ich will.
Keine korrekte Lösung
Andere Tipps
Wenn Sie das Buch lesen „Die Entwicklung von zeitorientierte Datenbankanwendungen in SQL“ von RT Snodgrass (die pdf von denen aus seiner Website unter Publikationen zur Verfügung steht), und so weit wie Abbildung 6.25 auf p165-166 erhalten, werden Sie die nicht-triviale SQL finden, die die verschiedenen in dem aktuellen Beispiel zu einer Gruppe verwendet werden können, Zeilen mit dem gleichen ID-Wert und kontinuierlichen Zeitintervallen.
Die Abfrage Entwicklung unten ist in der Nähe zu korrigieren, aber es ist ein Problem, direkt am Ende entdeckt, die ihre Quelle in der ersten SELECT-Anweisung hat. Ich habe noch nicht aufgespürt, warum die falsche Antwort gegeben wird. [Wenn jemand die SQL auf dem DBMS testen und mir sagen, ob die erste Abfrage richtig es funktioniert, wäre es eine große Hilfe sein!]
Es sieht so etwas wie:
-- Derived from Figure 6.25 from Snodgrass "Developing Time-Oriented
-- Database Applications in SQL"
CREATE TABLE Data
(
Start DATE,
Finish DATE,
ID CHAR(2),
Amount INT
);
INSERT INTO Data VALUES('2008-10-01', '2008-10-02', '01', 10);
INSERT INTO Data VALUES('2008-10-02', '2008-10-03', '02', 20);
INSERT INTO Data VALUES('2008-10-03', '2008-10-04', '01', 38);
INSERT INTO Data VALUES('2008-10-04', '2008-10-05', '01', 23);
INSERT INTO Data VALUES('2008-10-05', '2008-10-06', '03', 14);
INSERT INTO Data VALUES('2008-10-06', '2008-10-07', '02', 3);
INSERT INTO Data VALUES('2008-10-07', '2008-10-08', '02', 8);
INSERT INTO Data VALUES('2008-10-08', '2008-11-08', '03', 19);
SELECT DISTINCT F.ID, F.Start, L.Finish
FROM Data AS F, Data AS L
WHERE F.Start < L.Finish
AND F.ID = L.ID
-- There are no gaps between F.Finish and L.Start
AND NOT EXISTS (SELECT *
FROM Data AS M
WHERE M.ID = F.ID
AND F.Finish < M.Start
AND M.Start < L.Start
AND NOT EXISTS (SELECT *
FROM Data AS T1
WHERE T1.ID = F.ID
AND T1.Start < M.Start
AND M.Start <= T1.Finish))
-- Cannot be extended further
AND NOT EXISTS (SELECT *
FROM Data AS T2
WHERE T2.ID = F.ID
AND ((T2.Start < F.Start AND F.Start <= T2.Finish)
OR (T2.Start <= L.Finish AND L.Finish < T2.Finish)));
Die Ausgabe von dieser Abfrage lautet:
01 2008-10-01 2008-10-02
01 2008-10-03 2008-10-05
02 2008-10-02 2008-10-03
02 2008-10-06 2008-10-08
03 2008-10-05 2008-10-06
03 2008-10-05 2008-11-08
03 2008-10-08 2008-11-08
Edited : Es gibt ein Problem mit der vorletzten Reihe - es sollte nicht da sein. Und ich bin nicht klar, (noch) nicht, wo es herkommt.
Nun müssen wir diese komplexen Ausdruck als Abfrageausdruck in der FROM-Klausel einer anderen SELECT-Anweisung behandeln, die die Menge Werte für eine bestimmte ID über die Einträge summieren werden, die mit den maximalen Bereiche überlappen oben gezeigt.
SELECT M.ID, M.Start, M.Finish, SUM(D.Amount)
FROM Data AS D,
(SELECT DISTINCT F.ID, F.Start, L.Finish
FROM Data AS F, Data AS L
WHERE F.Start < L.Finish
AND F.ID = L.ID
-- There are no gaps between F.Finish and L.Start
AND NOT EXISTS (SELECT *
FROM Data AS M
WHERE M.ID = F.ID
AND F.Finish < M.Start
AND M.Start < L.Start
AND NOT EXISTS (SELECT *
FROM Data AS T1
WHERE T1.ID = F.ID
AND T1.Start < M.Start
AND M.Start <= T1.Finish))
-- Cannot be extended further
AND NOT EXISTS (SELECT *
FROM Data AS T2
WHERE T2.ID = F.ID
AND ((T2.Start < F.Start AND F.Start <= T2.Finish)
OR (T2.Start <= L.Finish AND L.Finish < T2.Finish)))) AS M
WHERE D.ID = M.ID
AND M.Start <= D.Start
AND M.Finish >= D.Finish
GROUP BY M.ID, M.Start, M.Finish
ORDER BY M.ID, M.Start;
Dies ergibt:
ID Start Finish Amount
01 2008-10-01 2008-10-02 10
01 2008-10-03 2008-10-05 61
02 2008-10-02 2008-10-03 20
02 2008-10-06 2008-10-08 11
03 2008-10-05 2008-10-06 14
03 2008-10-05 2008-11-08 33 -- Here be trouble!
03 2008-10-08 2008-11-08 19
Edited : Dies ist fast die korrekten Daten fest, auf das die COUNT und SUM-Aggregation durch die ursprüngliche Frage gebeten zu tun, so dass die endgültige Antwort ist:
SELECT I.ID, COUNT(*) AS Number, SUM(I.Amount) AS Amount
FROM (SELECT M.ID, M.Start, M.Finish, SUM(D.Amount) AS Amount
FROM Data AS D,
(SELECT DISTINCT F.ID, F.Start, L.Finish
FROM Data AS F, Data AS L
WHERE F.Start < L.Finish
AND F.ID = L.ID
-- There are no gaps between F.Finish and L.Start
AND NOT EXISTS
(SELECT *
FROM Data AS M
WHERE M.ID = F.ID
AND F.Finish < M.Start
AND M.Start < L.Start
AND NOT EXISTS
(SELECT *
FROM Data AS T1
WHERE T1.ID = F.ID
AND T1.Start < M.Start
AND M.Start <= T1.Finish))
-- Cannot be extended further
AND NOT EXISTS
(SELECT *
FROM Data AS T2
WHERE T2.ID = F.ID
AND ((T2.Start < F.Start AND F.Start <= T2.Finish) OR
(T2.Start <= L.Finish AND L.Finish < T2.Finish)))
) AS M
WHERE D.ID = M.ID
AND M.Start <= D.Start
AND M.Finish >= D.Finish
GROUP BY M.ID, M.Start, M.Finish
) AS I
GROUP BY I.ID
ORDER BY I.ID;
id number amount
01 2 71
02 2 31
03 3 66
Überprüfen : Oh! Verflixt ... der Eintrag für 3 hat zweimal die ‚Menge‘, die es haben sollte. Zurück ‚bearbeitet‘ Teile angeben, wo die Dinge begannen schief zu gehen. Es sieht aus, als ob entweder die erste Abfrage auf subtile Weise falsch ist (vielleicht ist es für eine andere Frage soll) oder der Optimierer ich bin arbeitet misbehaving. Dennoch sollte es eine Antwort seines engen Zusammenhang damit, dass die richtigen Werte geben.
Für das Protokoll:. Getestet auf IBM Informix Dynamic Server 11.50 auf Solaris 10 sollte jedoch auf andere mäßig Standard-konformen SQL-DBMS funktioniert
müssen wahrscheinlich einen Cursor und eine Schleife durch die Ergebnisse erstellen, zu verfolgen, welche id Sie arbeiten und die Daten auf dem Weg zu akkumulieren. Wenn die ID ändert können Sie die gesammelten Daten in eine temporäre Tabelle einfügen und in der Tabelle am Ende des Verfahrens zurückzukehren (wählen Sie alle von ihm). Eine tabellenbasierte Funktion könnte besser sein, als Sie, dann können nur in die Rück Tabelle einfügen, wie Sie gehen.
Ich vermute, dass es Iteration irgendeine Art erfordern kann, aber ich will nicht, diesen Weg zu gehen.
Ich glaube, dass der Weg ist Sie nehmen müssen, verwenden Sie einen Cursor eine Tabelle Variable zu füllen. Wenn Sie eine große Anzahl von Datensätzen haben könnten Sie eine permanente Tabelle verwenden, um die Ergebnisse zu speichern, dann, wenn Sie die Daten abzurufen, müssen Sie nur die neuen Daten nicht verarbeiten konnte.
würde ich ein Bit-Feld mit einem Standardwert von 0 auf die Quelltabelle hinzufügen, um zu verfolgen, welche Datensätze verarbeitet wurden. Unter der Annahme, niemanden ist mit select * auf dem Tisch, eine Spalte mit einem Standardwert hinzugefügt wird nicht den Rest Ihrer Anwendung beeinflussen.
Fügen Sie einen Kommentar zu diesem Beitrag, wenn Sie helfen wollen, die Lösung Codierung.
Nun habe ich beschlossen, die Iteration Weg zu gehen mit einer Mischung aus Joins und Cursor. Durch das Verbinden der Datentabelle gegen sich selbst kann ich eine Linkliste der nur die Datensätze erstellen, die aufeinanderfolgend sind.
INSERT INTO #CONSEC
SELECT a.ID, a.Start, b.Finish, b.Amount
FROM Data a JOIN Data b
ON (a.Finish = b.Start) AND (a.ID = b.ID)
Dann kann ich die Liste entspannen Sie mit einem Cursor über sie iterieren und Updates zurück auf die Datentabelle zu tun anzupassen (und die jetzt Fremd Datensätze aus der Datentabelle löschen)
DECLARE CCursor CURSOR FOR
SELECT ID, Start, Finish, Amount FROM #CONSEC ORDER BY Start DESC
@Total = 0
OPEN CCursor
FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
WHILE @FETCH_STATUS = 0
BEGIN
@Total = @Total + @Amount
@Start_Last = @Start
@Finish_Last = @Finish
@ID_Last = @ID
DELETE FROM Data WHERE Start = @Finish
FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
IF (@ID_Last<> @ID) OR (@Finish<>@Start_Last)
BEGIN
UPDATE Data
SET Amount = Amount + @Total
WHERE Start = @Start_Last
@Total = 0
END
END
CLOSE CCursor
DEALLOCATE CCursor
Dies alles funktioniert und hat eine akzeptable Leistung für typische Daten, die ich verwende.
Ich habe ein kleines Problem mit dem obigen Code finden. Ursprünglich war die Aktualisierung ich die Datentabelle auf jeder Schleife durch den Cursor. Aber das hat nicht funktioniert. Es scheint, dass Sie nur ein Update auf einem Datensatz tun, und dass mehrere Aktualisierungen (zu halten, um Hinzufügen von Daten) zu der Lese des ursprünglichen Inhalt des Datensatz wieder zurück.