Erste Worte
Sie können die Abschnitte unter (und einschließlich) JOINs: Starting Off ignorieren, wenn Sie nur den Code knacken möchten. Der Hintergrund und die Ergebnisse dienen lediglich als Kontext. Sehen Sie sich den Bearbeitungsverlauf vor dem 06.10.2015 an, um zu sehen, wie der Code ursprünglich aussah.
Zielsetzung
Letztendlich möchte ich interpolierte GPS-Koordinaten für den Sender ( X
oder Xmit
) basierend auf den DateTime-Stempeln der verfügbaren GPS-Daten in der Tabelle berechnen SecondTable
, die die Beobachtung in der Tabelle direkt flankieren FirstTable
.
Mein unmittelbares Ziel des ultimative Ziel zu erreichen ist , herauszufinden , wie am besten verbinden , FirstTable
um SecondTable
diese flankierenden Zeit Punkte zu bekommen. Später kann ich diese Informationen verwenden, um GPS-Zwischenkoordinaten unter der Annahme einer linearen Anpassung entlang eines gleichwinkligen Koordinatensystems zu berechnen.
Fragen
- Gibt es eine effizientere Möglichkeit, die nächsten Vorher-Nachher-Zeitstempel zu generieren?
- Das Problem wurde von mir behoben, indem ich einfach das "Nachher" und dann das "Vorher" nur in Bezug auf das "Nachher" abgerufen habe.
- Gibt es einen intuitiveren Weg, der die
(A<>B OR A=B)
Struktur nicht mit einbezieht ?- Byrdzeye lieferte die grundlegenden Alternativen, aber meine "reale" Erfahrung stimmte nicht mit allen vier seiner Join-Strategien überein. Ihm gebührt jedoch die Ehre, die alternativen Join-Stile angesprochen zu haben.
- Alle anderen Gedanken, Tricks und Ratschläge, die Sie möglicherweise haben.
- Bisher waren sowohl byrdzeye als auch Phrancis in dieser Hinsicht sehr hilfreich. Ich stellte fest, dass Phrancis 'Rat in einer kritischen Phase hervorragend ausgelegt und hilfreich war, also gebe ich ihm hier den entscheidenden Vorteil .
Ich würde mich immer noch über jede zusätzliche Hilfe freuen, die ich zu Frage 3 erhalten kann. Die Bulletpoints geben an, von wem ich glaube, dass er mir bei der einzelnen Frage am meisten geholfen hat.
Tabellendefinitionen
Halbvisuelle Darstellung
FirstTable
Fields
RecTStamp | DateTime --can contain milliseconds via VBA code (see Ref 1)
ReceivID | LONG
XmitID | TEXT(25)
Keys and Indices
PK_DT | Primary, Unique, No Null, Compound
XmitID | ASC
RecTStamp | ASC
ReceivID | ASC
UK_DRX | Unique, No Null, Compound
RecTStamp | ASC
ReceivID | ASC
XmitID | ASC
SecondTable
Fields
X_ID | LONG AUTONUMBER -- seeded after main table has been created and already sorted on the primary key
XTStamp | DateTime --will not contain partial seconds
Latitude | Double --these are in decimal degrees, not degrees/minutes/seconds
Longitude | Double --this way straight decimal math can be performed
Keys and Indices
PK_D | Primary, Unique, No Null, Simple
XTStamp | ASC
UIDX_ID | Unique, No Null, Simple
X_ID | ASC
ReceiverDetails- Tabelle
Fields
ReceivID | LONG
Receiver_Location_Description | TEXT -- NULL OK
Beginning | DateTime --no partial seconds
Ending | DateTime --no partial seconds
Lat | DOUBLE
Lon | DOUBLE
Keys and Indicies
PK_RID | Primary, Unique, No Null, Simple
ReceivID | ASC
ValidXmitters- Tabelle
Field (and primary key)
XmitID | TEXT(25) -- primary, unique, no null, simple
SQL-Geige ...
... damit Sie mit den Tabellendefinitionen und dem Code spielen können Diese Frage ist für MSAccess, aber wie Phrancis betont hat, gibt es keinen SQL-Geigenstil für Access. Sie sollten also in der Lage sein, hier meine Tabellendefinitionen und meinen Code basierend auf der Antwort von Phrancis einzusehen :
http://sqlfiddle.com/#!6/e9942/4 (externer Link)
JOINs: Los geht's
Mein aktueller "innerer Mut" JOIN Strategie
Erstellen Sie zunächst eine FirstTable_rekeyed mit einer Spaltenreihenfolge und einem zusammengesetzten Primärschlüssel, die (RecTStamp, ReceivID, XmitID)
alle indiziert / sortiert sind ASC
. Ich habe auch Indizes für jede Spalte einzeln erstellt. Dann fülle es so aus.
INSERT INTO FirstTable_rekeyed (RecTStamp, ReceivID, XmitID)
SELECT DISTINCT ROW RecTStamp, ReceivID, XmitID
FROM FirstTable
WHERE XmitID IN (SELECT XmitID from ValidXmitters)
ORDER BY RecTStamp, ReceivID, XmitID;
Die obige Abfrage füllt die neue Tabelle mit 153006 Datensätzen und gibt sie innerhalb von ungefähr 10 Sekunden zurück.
Die folgenden Schritte werden innerhalb von ein oder zwei Sekunden ausgeführt, wenn diese gesamte Methode in "SELECT Count (*) FROM (...)" eingeschlossen ist, wenn die TOP 1-Unterabfragemethode verwendet wird
SELECT
ReceiverRecord.RecTStamp,
ReceiverRecord.ReceivID,
ReceiverRecord.XmitID,
(SELECT TOP 1 XmitGPS.X_ID FROM SecondTable as XmitGPS WHERE ReceiverRecord.RecTStamp < XmitGPS.XTStamp ORDER BY XmitGPS.X_ID) AS AfterXmit_ID
FROM FirstTable_rekeyed AS ReceiverRecord
-- INNER JOIN SecondTable AS XmitGPS ON (ReceiverRecord.RecTStamp < XmitGPS.XTStamp)
GROUP BY RecTStamp, ReceivID, XmitID;
-- No separate join needed for the Top 1 method, but it would be required for the other methods.
-- Additionally no restriction of the returned set is needed if I create the _rekeyed table.
-- May not need GROUP BY either. Could try ORDER BY.
-- The three AfterXmit_ID alternatives below take longer than 3 minutes to complete (or do not ever complete).
-- FIRST(XmitGPS.X_ID)
-- MIN(XmitGPS.X_ID)
-- MIN(SWITCH(XmitGPS.XTStamp > ReceiverRecord.RecTStamp, XmitGPS.X_ID, Null))
Vorherige "innere Eingeweide" JOIN-Abfrage
Zuerst (fastisch ... aber nicht gut genug)
SELECT
A.RecTStamp,
A.ReceivID,
A.XmitID,
MAX(IIF(B.XTStamp<= A.RecTStamp,B.XTStamp,Null)) as BeforeXTStamp,
MIN(IIF(B.XTStamp > A.RecTStamp,B.XTStamp,Null)) as AfterXTStamp
FROM FirstTable as A
INNER JOIN SecondTable as B ON
(A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)
GROUP BY A.RecTStamp, A.ReceivID, A.XmitID
-- alternative for BeforeXTStamp MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
-- alternatives for AfterXTStamp (see "Aside" note below)
-- 1.0/(MAX(1.0/(-(B.XTStamp>A.RecTStamp)*B.XTStamp)))
-- -1.0/(MIN(1.0/((B.XTStamp>A.RecTStamp)*B.XTStamp)))
Sekunde (langsamer)
SELECT
A.RecTStamp, AbyB1.XTStamp AS BeforeXTStamp, AbyB2.XTStamp AS AfterXTStamp
FROM (FirstTable AS A INNER JOIN
(select top 1 B1.XTStamp, A1.RecTStamp
from SecondTable as B1, FirstTable as A1
where B1.XTStamp<=A1.RecTStamp
order by B1.XTStamp DESC) AS AbyB1 --MAX (time points before)
ON A.RecTStamp = AbyB1.RecTStamp) INNER JOIN
(select top 1 B2.XTStamp, A2.RecTStamp
from SecondTable as B2, FirstTable as A2
where B2.XTStamp>A2.RecTStamp
order by B2.XTStamp ASC) AS AbyB2 --MIN (time points after)
ON A.RecTStamp = AbyB2.RecTStamp;
Hintergrund
Ich habe eine Telemetrietabelle (Alias A) mit knapp 1 Million Einträgen mit einem zusammengesetzten Primärschlüssel, der auf einem DateTime
Stempel, einer Sender-ID und einer Aufzeichnungsgeräte-ID basiert . Aufgrund von Umständen, auf die ich keinen Einfluss habe, ist meine SQL-Sprache die Standard-Jet-DB in Microsoft Access (Benutzer verwenden 2007 und neuere Versionen). Nur etwa 200.000 dieser Einträge sind aufgrund der Sender-ID für die Abfrage relevant.
Es gibt eine zweite Telemetrietabelle (Alias B), die ungefähr 50.000 Einträge mit einer einzelnen enthält DateTime
Primärschlüssel umfasst
Im ersten Schritt habe ich mich darauf konzentriert, die Zeitstempel zu finden, die den Stempeln in der ersten Tabelle aus der zweiten Tabelle am nächsten kommen.
JOIN Ergebnisse
Macken, die ich entdeckt habe ...
... unterwegs beim Debuggen
Es fühlt sich wirklich seltsam an, die JOIN
Logik zu schreiben, die FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)
, wie @byrdzeye in einem Kommentar hervorhob (der inzwischen verschwunden ist), eine Form des Cross-Joins ist. Beachten Sie, dass das Ersetzen LEFT OUTER JOIN
von INNER JOIN
im obigen Code keine Auswirkungen auf die Menge oder Identität der zurückgegebenen Zeilen zu haben scheint. Ich kann auch nicht scheinen, die ON-Klausel wegzulassen oder zu sagen ON (1=1)
. Die einfache Verwendung eines Kommas zum Verknüpfen (anstelle von INNER
oder LEFT OUTER
JOIN
) führt zu Count(select * from A) * Count(select * from B)
Zeilen, die in dieser Abfrage zurückgegeben werden, und nicht nur zu einer Zeile pro Tabelle A, wie dies durch den expliziten JOIN
Rückgabewert (A <> B OR A = B) angegeben wird . Dies ist eindeutig nicht geeignet. FIRST
scheint bei einem zusammengesetzten Primärschlüsseltyp nicht verfügbar zu sein.
Der zweite JOIN
Stil ist zwar besser lesbar, leidet aber darunter, langsamer zu sein. Dies kann daran liegen, dass zwei zusätzliche Innenseiten JOIN
für den größeren Tisch sowie die beiden Innenseiten CROSS JOIN
in beiden Optionen erforderlich sind .
Nebenbei: Das Ersetzen der IIF
Klausel durch MIN
/ MAX
scheint die gleiche Anzahl von Einträgen zurückzugeben.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
Funktioniert für den MAX
Zeitstempel "Before" ( ), jedoch nicht direkt für das "After" ( MIN
),
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
da das Minimum für die FALSE
Bedingung immer 0 ist. Diese 0 ist kleiner als jede Post-Epoche DOUBLE
(zu der ein DateTime
Feld in Access gehört und in die diese Berechnung das Feld umwandelt). Die IIF
und MIN
/ MAX
Methoden Die für den AfterXTStamp-Wert vorgeschlagenen Alternativen funktionieren, da die Division durch Null ( FALSE
) Nullwerte generiert, die von den Aggregatfunktionen MIN und MAX übersprungen werden.
Nächste Schritte
Um dies weiter zu verfolgen, möchte ich die Zeitstempel in der zweiten Tabelle finden, die die Zeitstempel in der ersten Tabelle direkt flankieren, und eine lineare Interpolation der Datenwerte aus der zweiten Tabelle basierend auf dem Zeitabstand zu diesen Punkten durchführen (dh wenn der Zeitstempel von Die erste Tabelle ist 25% des Weges zwischen "Vorher" und "Nachher". Ich möchte, dass 25% des berechneten Wertes aus den Daten der zweiten Tabelle stammen, die dem "Nachher" -Punkt und 75% aus dem "Vorher" -Punkt zugeordnet sind. ). Unter Verwendung des überarbeiteten Verknüpfungstyps als Teil der inneren Eingeweide und nach den unten vorgeschlagenen Antworten produziere ich ...
SELECT
AvgGPS.XmitID,
StrDateIso8601Msec(AvgGPS.RecTStamp) AS RecTStamp_ms,
-- StrDateIso8601MSec is a VBA function returning a TEXT string in yyyy-mm-dd hh:nn:ss.lll format
AvgGPS.ReceivID,
RD.Receiver_Location_Description,
RD.Lat AS Receiver_Lat,
RD.Lon AS Receiver_Lon,
AvgGPS.Before_Lat * (1 - AvgGPS.AfterWeight) + AvgGPS.After_Lat * AvgGPS.AfterWeight AS Xmit_Lat,
AvgGPS.Before_Lon * (1 - AvgGPS.AfterWeight) + AvgGPS.After_Lon * AvgGPS.AfterWeight AS Xmit_Lon,
AvgGPS.RecTStamp AS RecTStamp_basic
FROM ( SELECT
AfterTimestampID.RecTStamp,
AfterTimestampID.XmitID,
AfterTimestampID.ReceivID,
GPSBefore.BeforeXTStamp,
GPSBefore.Latitude AS Before_Lat,
GPSBefore.Longitude AS Before_Lon,
GPSAfter.AfterXTStamp,
GPSAfter.Latitude AS After_Lat,
GPSAfter.Longitude AS After_Lon,
( (AfterTimestampID.RecTStamp - GPSBefore.XTStamp) / (GPSAfter.XTStamp - GPSBefore.XTStamp) ) AS AfterWeight
FROM (
(SELECT
ReceiverRecord.RecTStamp,
ReceiverRecord.ReceivID,
ReceiverRecord.XmitID,
(SELECT TOP 1 XmitGPS.X_ID FROM SecondTable as XmitGPS WHERE ReceiverRecord.RecTStamp < XmitGPS.XTStamp ORDER BY XmitGPS.X_ID) AS AfterXmit_ID
FROM FirstTable AS ReceiverRecord
-- WHERE ReceiverRecord.XmitID IN (select XmitID from ValidXmitters)
GROUP BY RecTStamp, ReceivID, XmitID
) AS AfterTimestampID INNER JOIN SecondTable AS GPSAfter ON AfterTimestampID.AfterXmit_ID = GPSAfter.X_ID
) INNER JOIN SecondTable AS GPSBefore ON AfterTimestampID.AfterXmit_ID = GPSBefore.X_ID + 1
) AS AvgGPS INNER JOIN ReceiverDetails AS RD ON (AvgGPS.ReceivID = RD.ReceivID) AND (AvgGPS.RecTStamp BETWEEN RD.Beginning AND RD.Ending)
ORDER BY AvgGPS.RecTStamp, AvgGPS.ReceivID;
... was 152928 Datensätze zurückgibt, die (zumindest ungefähr) der endgültigen Anzahl der erwarteten Datensätze entsprechen. Die Laufzeit beträgt wahrscheinlich 5-10 Minuten auf meinem i7-4790, 16 GB RAM, keine SSD, Win 8.1 Pro-System.
Referenz 1: MS Access kann Millisekunden- Zeitwerte verarbeiten - Really und zugehörige Quelldatei [08080011.txt]