Access (Jet) SQL: DateTime-Stempel in Tabelle B, die jeden DateTime-Stempel in Tabelle A flankieren

21

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 ( Xoder 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 , FirstTableum SecondTablediese 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

  1. 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.
  2. 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.
  3. 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 DateTimeStempel, 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 JOINLogik 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 JOINvon INNER JOINim 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 INNERoder 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 JOINRückgabewert (A <> B OR A = B) angegeben wird . Dies ist eindeutig nicht geeignet. FIRSTscheint bei einem zusammengesetzten Primärschlüsseltyp nicht verfügbar zu sein.

Der zweite JOINStil ist zwar besser lesbar, leidet aber darunter, langsamer zu sein. Dies kann daran liegen, dass zwei zusätzliche Innenseiten JOINfür den größeren Tisch sowie die beiden Innenseiten CROSS JOINin beiden Optionen erforderlich sind .

Nebenbei: Das Ersetzen der IIFKlausel durch MIN/ MAXscheint die gleiche Anzahl von Einträgen zurückzugeben.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
Funktioniert für den MAXZeitstempel "Before" ( ), jedoch nicht direkt für das "After" ( MIN),
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
da das Minimum für die FALSEBedingung immer 0 ist. Diese 0 ist kleiner als jede Post-Epoche DOUBLE(zu der ein DateTimeFeld in Access gehört und in die diese Berechnung das Feld umwandelt). Die IIFund MIN/ MAXMethoden 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]

mpag
quelle

Antworten:

10

Zunächst möchte ich Ihnen für Ihren Mut gratulieren, mit einer Access-Datenbank so etwas zu tun. Nach meiner Erfahrung ist es sehr schwierig , etwas SQL-ähnliches zu tun. Wie auch immer, auf die Überprüfung.


Zuerst beitreten

Für Ihre Feldauswahl kann es hilfreich sein, stattdessen IIFeine Switch-Anweisung zu verwenden. Es scheint manchmal vor allem bei SQL-Dingen der Fall zu sein, dass a SWITCH(allgemeiner als CASEin typischem SQL bekannt) recht schnell ist, wenn nur einfache Vergleiche im Rumpf von a durchgeführt werden SELECT. Die Syntax in Ihrem Fall wäre nahezu identisch, obwohl ein Schalter erweitert werden kann, um einen großen Teil der Vergleiche in einem Feld abzudecken. Etwas zu beachten.

  SWITCH (
    expr1, val1,
    expr2, val2,
    val3        -- default value or "else"
  )

Ein Schalter kann auch bei größeren Anweisungen die Lesbarkeit verbessern. Im Zusammenhang:

  MAX(SWITCH(B.XTStamp <= A.RecTStamp,B.XTStamp,Null)) as BeforeXTStamp,
  --alternatively MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp) as BeforeXTStamp,
  MIN(SWITCH(B.XTStamp>A.RecTStamp,B.XTStamp,Null)) as AfterXTStamp

Was den Join selbst betrifft, denke ich, (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)ist er ungefähr so ​​gut, wie Sie es erwarten, wenn Sie das tun, was Sie versuchen. Es ist nicht so schnell, aber ich würde es auch nicht erwarten.


Zweiter Join

Sie sagten, das ist langsamer. Es ist auch vom Standpunkt des Codes aus weniger lesbar. Bei gleichermaßen zufriedenstellenden Ergebnismengen zwischen 1 und 2 würde ich für 1 sprechen. Zumindest ist es offensichtlich, was Sie auf diese Weise versuchen. Unterabfragen sind oft nicht sehr schnell (obwohl oft unvermeidbar), insbesondere in diesem Fall, wenn Sie jeweils einen zusätzlichen Join hinzufügen, was den Ausführungsplan sicherlich komplizieren muss.

Eine Bemerkung, ich habe gesehen, dass Sie die alte ANSI-89-Join-Syntax verwendet haben. Es ist am besten, dies zu vermeiden, da die Leistung mit der moderneren Join-Syntax gleich oder besser ist und sie weniger mehrdeutig oder leichter zu lesen sind und es schwieriger ist, Fehler zu machen.

FROM (FirstTable AS A INNER JOIN 
  (select top 1 B1.XTStamp, A1.RecTStamp 
   from SecondTable as B1
   inner join FirstTable as A1
     on B1.XTStamp <= A1.RecTStamp
   order by B1.XTStamp DESC) AS AbyB1 --MAX (time points before)

Dinge benennen

Ich denke, die Art und Weise, wie Ihre Sachen benannt sind, ist im besten Fall nicht hilfreich und im schlimmsten Fall kryptisch. A, B, A1, B1etc. als tabelle alias könnte ich besser sein. Ich denke auch, dass die Feldnamen nicht sehr gut sind, aber mir ist klar, dass Sie möglicherweise keine Kontrolle darüber haben. Ich zitiere einfach schnell The Codeless Code zum Thema Benennung von Dingen und lasse es dabei ...

"Beschimpfungen!" Antwortete die Priesterin. "Verbe deine expletiven Nomen!"


Abfrage "Nächste Schritte"

Ich konnte nicht viel verstehen, wie es geschrieben wurde, ich musste es in einen Texteditor bringen und einige Stiländerungen vornehmen, um es lesbarer zu machen. Ich weiß, dass der SQL-Editor von Access unhandlich ist, daher schreibe ich meine Abfragen normalerweise in einem guten Editor wie Notepad ++ oder Sublime Text. Einige der Stiländerungen, die ich vorgenommen habe, um die Lesbarkeit zu verbessern:

  • 4 Leerzeichen anstelle von 2 Leerzeichen einrücken
  • Leerzeichen um mathematische Operatoren und Vergleichsoperatoren
  • Natürlichere Platzierung von Zahnspangen und Einrückungen (ich habe mich für Zahnspangen im Java-Stil entschieden, könnte aber auch nach Ihren Wünschen im C-Stil sein)

Wie sich herausstellt, ist dies in der Tat eine sehr komplizierte Abfrage. Um dies zu verstehen, muss ich von der innersten Abfrage ausgehen, Ihrem IDDatensatz, von dem ich verstehe, dass er mit Ihrem ersten Join identisch ist. Es gibt die IDs und Zeitstempel der Geräte , bei denen die vor / nach dem Zeitstempel ist in der Nähe, in der Teilmenge der Geräte , die Sie interessieren. Anstatt also IDwarum es nicht nennen ClosestTimestampID.

Ihr DetJoin wird nur einmal verwendet:

Bildbeschreibung hier eingeben

Der Rest der Zeit verbindet sich nur mit den Werten, die Sie bereits haben ClosestTimestampID. Also sollten wir stattdessen in der Lage sein, Folgendes zu tun:

    ) AS ClosestTimestampID
    INNER JOIN SecondTable AS TL1 
        ON ClosestTimestampID.BeforeXTStamp = TL1.XTStamp) 
    INNER JOIN SecondTable AS TL2 
        ON ClosestTimestampID.AfterXTStamp = TL2.XTStamp
    WHERE ClosestTimestampID.XmitID IN (<limited subset S>)

Vielleicht kein großer Leistungszuwachs, aber alles, was wir tun können, um dem armen Jet DB-Optimierer zu helfen, wird helfen!


Ich kann das Gefühl nicht los , dass die Berechnungen / Algorithmus für BeforeWeightund AfterWeightden Sie interpolieren verwenden könnte besser gemacht werden, aber leider bin ich nicht sehr gut mit denen.

Ein Vorschlag, um Abstürze zu vermeiden (obwohl dies je nach Anwendung nicht ideal ist), besteht darin, Ihre verschachtelten Unterabfragen in eigene Tabellen aufzuteilen und diese bei Bedarf zu aktualisieren. Ich bin mir nicht sicher, wie oft die Quelldaten aktualisiert werden müssen. Wenn dies jedoch nicht zu oft der Fall ist, möchten Sie möglicherweise VBA-Code schreiben, um eine Aktualisierung der Tabellen und abgeleiteten Tabellen zu planen, und die äußerste Abfrage abrufen aus diesen Tabellen anstelle der ursprünglichen Quelle. Nur ein Gedanke, wie ich schon sagte, nicht ideal, aber angesichts des Tools haben Sie möglicherweise keine Wahl.


Alles zusammen:

SELECT
    InGPS.XmitID,
    StrDateIso8601Msec(InGPS.RecTStamp) AS RecTStamp_ms,
       -- StrDateIso8601MSec is a VBA function returning a TEXT string in yyyy-mm-dd hh:nn:ss.lll format
    InGPS.ReceivID,
    RD.Receiver_Location_Description,
    RD.Lat AS Receiver_Lat,
    RD.Lon AS Receiver_Lon,
    InGPS.Before_Lat * InGPS.BeforeWeight + InGPS.After_Lat * InGPS.AfterWeight AS Xmit_Lat,
    InGPS.Before_Lon * InGPS.BeforeWeight + InGPS.After_Lon * InGPS.AfterWeight AS Xmit_Lon,
    InGPS.RecTStamp AS RecTStamp_basic
FROM (
    SELECT 
        ClosestTimestampID.RecTStamp,
        ClosestTimestampID.XmitID,
        ClosestTimestampID.ReceivID,
        ClosestTimestampID.BeforeXTStamp, 
        TL1.Latitude AS Before_Lat, 
        TL1.Longitude AS Before_Lon,
        (1 - ((ClosestTimestampID.RecTStamp - ClosestTimestampID.BeforeXTStamp) 
            / (ClosestTimestampID.AfterXTStamp - ClosestTimestampID.BeforeXTStamp))) AS BeforeWeight,
        ClosestTimestampID.AfterXTStamp, 
        TL2.Latitude AS After_Lat, 
        TL2.Longitude AS After_Lon,
        (     (ClosestTimestampID.RecTStamp - ClosestTimestampID.BeforeXTStamp) 
            / (ClosestTimestampID.AfterXTStamp - ClosestTimestampID.BeforeXTStamp)) AS AfterWeight
        FROM (((
            SELECT 
                A.RecTStamp, 
                A.ReceivID, 
                A.XmitID,
                MAX(SWITCH(B.XTStamp <= A.RecTStamp, B.XTStamp, Null)) AS BeforeXTStamp,
                MIN(SWITCH(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)
            WHERE A.XmitID IN (<limited subset S>)
            GROUP BY A.RecTStamp, ReceivID, XmitID
        ) AS ClosestTimestampID
        INNER JOIN FirstTable AS Det 
            ON (Det.XmitID = ClosestTimestampID.XmitID) 
            AND (Det.ReceivID = ClosestTimestampID.ReceivID) 
            AND (Det.RecTStamp = ClosestTimestampID.RecTStamp)) 
        INNER JOIN SecondTable AS TL1 
            ON ClosestTimestampID.BeforeXTStamp = TL1.XTStamp) 
        INNER JOIN SecondTable AS TL2 
            ON ClosestTimestampID.AfterXTStamp = TL2.XTStamp
        WHERE Det.XmitID IN (<limited subset S>)
    ) AS InGPS
INNER JOIN ReceiverDetails AS RD 
    ON (InGPS.ReceivID = RD.ReceivID) 
    AND (InGPS.RecTStamp BETWEEN <valid parameters from another table>)
ORDER BY StrDateIso8601Msec(InGPS.RecTStamp), InGPS.ReceivID;
Phrancis
quelle
5
  • Zusätzliche Attribute und Filterbedingungen hinzugefügt.
  • Jede Form von Cross-Join wird durch die Verwendung von verschachtelten Min- und Max-Abfragen beseitigt. Dies ist der größte Leistungszuwachs.
  • Die minimalen und maximalen Flankenwerte, die von der innersten verschachtelten Abfrage zurückgegeben werden, sind Primärschlüsselwerte (Scans), die zum Abrufen zusätzlicher Flankenattribute (lat und lon) unter Verwendung einer Suche nach endgültigen Berechnungen verwendet werden (für den Zugriff gilt ein entsprechendes Äquivalent).
  • Die primären Tabellenattribute werden in der innersten Abfrage abgerufen und gefiltert und sollten die Leistung verbessern.
  • Es ist nicht erforderlich, den Zeitwert für die Sortierung zu formatieren (StrDateIso8601Msec). Die Verwendung des datetime-Werts aus der Tabelle ist gleichberechtigt.

SQL Server-Ausführungspläne (da Access dies nicht anzeigen kann)
Ohne die endgültige Reihenfolge, weil sie teuer ist:
Clustered Index Scan [ReceiverDetails]. [PK_ReceiverDetails] Kosten 16%
Clustered Index Suchen Sie [FirstTable]. [PK_FirstTable] Kosten 19%
Clustered Index Seek [SecondTable]. [PK_SecondTable] Cost 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] Cost 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] [TL2] Cost 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] [TL1] Kosten 16%

Bei der endgültigen Bestellung nach:
Kosten
sortieren 36% Clustered Index Scan [ReceiverDetails]. [PK_ReceiverDetails] Kosten 10%
Clustered Index Seek [FirstTable]. [PK_FirstTable] Kosten 12%
Suche nach gruppiertem Index [SecondTable]. [PK_SecondTable] kostet 10%
Suche nach gruppiertem
Index [SecondTable]. [PK_SecondTable] kostet 10% Suche nach gruppiertem
Index [SecondTable]. [PK_SecondTable]. [TL2] kostet 10% Suche nach gruppiertem Index [SecondTable]. [PK_SecondTable]. [TL1] kostet 10%

Code:

select
     ClosestTimestampID.XmitID
    --,StrDateIso8601Msec(InGPS.RecTStamp) AS RecTStamp_ms
    ,ClosestTimestampID.ReceivID
    ,ClosestTimestampID.Receiver_Location_Description
    ,ClosestTimestampID.Lat
    ,ClosestTimestampID.Lon
,[TL1].[Latitude] * (1 - ((ClosestTimestampID.RecTStamp - ClosestTimestampID.BeforeXTStamp) / (ClosestTimestampID.AfterXTStamp - ClosestTimestampID.BeforeXTStamp))) + [TL2].[Latitude] * ((ClosestTimestampID.RecTStamp - ClosestTimestampID.BeforeXTStamp) / (ClosestTimestampID.AfterXTStamp - ClosestTimestampID.BeforeXTStamp)) AS Xmit_Lat
,[TL1].[Longitude] * (1 - ((ClosestTimestampID.RecTStamp - ClosestTimestampID.BeforeXTStamp) / (ClosestTimestampID.AfterXTStamp - ClosestTimestampID.BeforeXTStamp))) + [TL2].[Longitude] * ((ClosestTimestampID.RecTStamp - ClosestTimestampID.BeforeXTStamp) / (ClosestTimestampID.AfterXTStamp - ClosestTimestampID.BeforeXTStamp)) AS Xmit_Lon
    ,ClosestTimestampID.RecTStamp as RecTStamp_basic
from (
        (
            (
                select
                     FirstTable.RecTStamp
                    ,FirstTable.ReceivID
                    ,FirstTable.XmitID
                    ,ReceiverDetails.Receiver_Location_Description
                    ,ReceiverDetails.Lat
                    ,ReceiverDetails.Lon
                    ,(
                        select max(XTStamp) as val
                        from SecondTable
                        where XTStamp <= FirstTable.RecTStamp
                     ) as BeforeXTStamp
                    ,(
                        select min(XTStamp) as val
                        from SecondTable
                        where XTStamp > FirstTable.RecTStamp
                     ) as AfterXTStamp
                from FirstTable
                inner join ReceiverDetails
                on ReceiverDetails.ReceivID = FirstTable.ReceivID
                where FirstTable.RecTStamp between #1/1/1990# and #1/1/2020#
                and FirstTable.XmitID in (100,110)
            ) as ClosestTimestampID
            inner join SecondTable as TL1
            on ClosestTimestampID.BeforeXTStamp = TL1.XTStamp
        )
        inner join SecondTable as TL2
        on ClosestTimestampID.AfterXTStamp = TL2.XTStamp
    )
order by ClosestTimestampID.RecTStamp, ClosestTimestampID.ReceivID;

Testen der Leistung meiner Abfrage anhand der Abfrage, die den Cross-Join enthält.

FirstTable wurde mit 13 Datensätzen und SecondTable mit 1.000.000 geladen.
Die Ausführungspläne für meine Abfrage haben sich nicht wesentlich von den veröffentlichten geändert.
Ausführungspläne für die Cross - Join:
Nested Loops Kosten 81% unter Verwendung von INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
Nested Loops sinkt auf 75% bei Verwendung von CROSS JOIN SecondTable AS B' or ',SecondTable AS B
Stream - Aggregate 8%
Index Scan [SecondTable] [UK_ID] [B] 6%
Table Spool 5%
Mehrere andere Clustered Index Seek und Index Sucht (ähnlich wie bei meiner Anfrage) mit Kosten von 0%.

Die Ausführungszeit beträgt .007 und 8-9 Sekunden für meine Abfrage und den CROSS JOIN.
Kostenvergleich 0% und 100%.

Ich habe FirstTable mit 50.000 Datensätzen und einem einzelnen Datensatz für eine Join-Bedingung in ReceiverDetails geladen und meine Abfrage ausgeführt.
50.013 kehrten zwischen 0,9 und 1,0 Sekunden zurück.

Ich habe die zweite Abfrage mit dem Cross-Join ausgeführt und sie etwa 20 Minuten lang ausgeführt, bevor ich sie beendet habe.
Wenn die Cross-Join-Abfrage so gefiltert wird, dass nur die ursprünglichen 13 zurückgegeben werden, beträgt die Ausführungszeit erneut 8-9 Sekunden.
Die Platzierung der Filterbedingung war bei innerster Auswahl, äußerster Auswahl und beidem. Kein Unterschied.

Es gibt einen Unterschied zwischen diesen beiden Join-Bedingungen zugunsten des CROSS JOIN. Der erste verwendet ein Prädikat, der CROSS JOIN nicht:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B

byrdzeye
quelle
Das Ausführen des Abschnitts ClosestTimestampID auf meinem System gibt sofort 152928 Datensätze zurück, wenn es in einem Count (*) gekapselt ist. Mein MSAccess hat sich gesperrt, als die tatsächlichen Datensätze zu diesem Zeitpunkt zurückgegeben wurden - möglicherweise waren die temporären Tabellen der anderen Methode überlastet. Ich denke, die letzte Abfrage, die ich aus Ihrer Methodik heraus erzeuge, wird sehr ähnlich zu der sein, die ich derzeit verwende. Was ich denke, ist eine gute Sache :)
mpag
1
In Ihrem ursprünglichen Kommentar haben Sie angegeben, dass Sie einige Datensätze sofort zurückerhalten haben. Dies ist wichtig im Hinblick auf die Funktionsweise von Access, die Ausarbeitung einer Access-Strategie und die Festlegung der Erwartungen für die Ausführungszeit. Es wird als verzögerte Ausführung bezeichnet. (Es stürzte ab, als Sie den letzten Datensatz getroffen haben.) Wie hoch ist die erwartete Anzahl der Obergrenzen für die Rückgabe von Datensätzen in der endgültigen Abfrage?
Byrdzeye
Ich glaube 152928
mpag
Welcher Art sind die DateTime-Werte in beiden Tabellen, wenn neue Datensätze hinzugefügt werden? Sind sie aktuelle Zeitstempel oder aktuelle Werte oder völlig zufällig?
Byrdzeye
Die erste Tabelle enthält DateTime-Stempel, die 2013 oder neuer sind. Die zweite Tabelle enthält DateTime-Stempel, die sich innerhalb weniger Monate Mitte 2015 befinden. Wenn neue Werte hinzugefügt werden, werden sie wahrscheinlich (aber nicht garantiert) nach dem vorhandenen Satz liegen. Zu jeder Tabelle können neue Werte hinzugefügt werden.
mpag
2

Wenn Sie eine zweite Antwort hinzufügen, die nicht besser ist als die erste, ohne jedoch die Anforderungen zu ändern, gibt es verschiedene Möglichkeiten, Access in die Übergabe zu locken und bissig zu wirken. "Materialisieren" Sie die Komplikationen ein Stück für Stück mit "Triggern". Zugriffstabellen haben keine Trigger, um die Rohprozesse abzufangen und zu injizieren.

--*** Create a table for flank values.
    create table Flank (
         RecTStamp      datetime not null
        ,BeforeXTStamp  datetime null
        ,AfterXTStamp   datetime null
        ,constraint PK_Flank primary key clustered ( RecTStamp asc )
        )

--*** Create a FlankUpdateLoop sub. (create what is missing)
    -- loop until rowcount < 5000 or rowcount = 0
    -- a 5K limit appears to be manageable for Access, especially for the initial population.
    insert into Flank (
         RecTStamp
        ,BeforeXTStamp
        ,AfterXTStamp
        )
    select top 5000 FirstTable.RecTStamp
        ,(
            select max(XTStamp) as val
            from SecondTable
            where XTStamp <= FirstTable.RecTStamp
            ) as BeforeXTStamp
        ,(
            select min(XTStamp) as val
            from SecondTable
            where XTStamp > FirstTable.RecTStamp
            ) as AfterXTStamp
    from FirstTable
    left join Flank
        on FirstTable.RecTStamp = Flank.RecTStamp
    where Flank.RecTStamp is null;

--*** For FirstTable Adds, Changes or Deletes:
    delete from Flank where Flank.RecTStamp = CRUD_RecTStamp
    execute FlankUpdateLoop --See above. This will handle Adds, Changes or Deletes.

--*** For SecondTable Adds, Changes or Deletes:
    --delete from Flank where the old value is immediately before and after the new flank value.
    --They may or may not get be assigned a new value. Let FlankUpdate figure it out.

    --execute deletes for both beforextstamp and afterxtstamp
    --then update flank

    delete *
    from flank
    where beforextstamp between (
                    select min(beforextstamp)
                    from flank
                    where beforextstamp >= '3/16/2009 10:00:46 AM'
                    ) and (
                    select max(beforextstamp)
                    from flank
                    where beforextstamp <= '3/16/2009 10:00:46 AM'
                    );

    delete *
    from flank
    where afterxtstamp between (
                    select min(afterxtstamp)
                    from flank
                    where afterxtstamp >= '3/16/2009 10:00:46 AM'
                    ) and (
                    select max(afterxtstamp)
                    from flank
                    where afterxtstamp <= '3/16/2009 10:00:46 AM'
                    );

    execute FlankUpdateLoop

--*** Final Report Query***--
    --Should execute without issues including 'deferred execution' problem.
    --Add filters as needed.
    select FirstTable.XmitID
        ,FirstTable.ReceivID
        ,ReceiverDetails.Lat
        ,ReceiverDetails.Lon
        ,BeforeTable.Latitude * (1 - ((FirstTable.RecTStamp - BeforeXTStamp) / (AfterXTStamp - BeforeXTStamp))) + AfterTable.Latitude * ((FirstTable.RecTStamp - BeforeXTStamp) / (AfterXTStamp - BeforeXTStamp)) as Xmit_Lat
        ,BeforeTable.Longitude * (1 - ((FirstTable.RecTStamp - BeforeXTStamp) / (AfterXTStamp - BeforeXTStamp))) + AfterTable.Longitude * ((FirstTable.RecTStamp - BeforeXTStamp) / (AfterXTStamp - BeforeXTStamp)) as Xmit_Lon
        ,FirstTable.RecTStamp as RecTStamp_basic
    from (((
        FirstTable
    inner join Flank on FirstTable.RecTStamp = Flank.RecTStamp)
    inner join SecondTable as BeforeTable on Flank.BeforeXTStamp = BeforeTable.XTStamp)
    inner join SecondTable as AfterTable on Flank.AfterXTStamp = AfterTable.XTStamp)
    inner join ReceiverDetails on FirstTable.ReceivID = ReceiverDetails.ReceivID
    order by FirstTable.RecTStamp;
byrdzeye
quelle