Access (Jet) SQL: timbri DateTime in TableB che fiancheggiano ogni timbro DateTime in TableA


21

Prime parole

Puoi tranquillamente ignorare le sezioni seguenti (e inclusi) JOIN: Iniziare se vuoi solo prendere una crepa del codice. Lo sfondo e i risultati servono solo come contesto. Si prega di guardare la cronologia delle modifiche prima del 06-10-2015 se si desidera vedere l'aspetto iniziale del codice.


Obbiettivo

In definitiva, voglio calcolare le coordinate GPS interpolate per il trasmettitore ( Xo Xmit) in base ai timbri DateTime dei dati GPS disponibili nella tabella SecondTableche affiancano direttamente l'osservazione nella tabella FirstTable.

Il mio obiettivo immediato di raggiungere l'obiettivo finale è quello di capire come unirmi FirstTableal meglio SecondTableper ottenere quei punti di tempo a fianco. Più tardi posso usare queste informazioni per calcolare le coordinate GPS intermedie assumendo un adattamento lineare lungo un sistema di coordinate equirettangolare (parole fantasiose per dire che non mi interessa che la Terra sia una sfera su questa scala).


Domande

  1. Esiste un modo più efficiente per generare i timestamp prima e dopo più vicini?
    • Risolto da solo afferrando il "dopo" e poi ottenendo il "prima" solo in relazione al "dopo".
  2. Esiste un modo più intuitivo che non coinvolge la (A<>B OR A=B)struttura?
    • Byrdzeye ha fornito le alternative di base, tuttavia la mia esperienza del "mondo reale" non si è allineata con tutte e 4 le sue strategie di join che si sono comportate allo stesso modo. Ma pieno merito a lui per aver affrontato gli stili di join alternativi.
  3. Eventuali altri pensieri, trucchi e consigli che potresti avere.
    • Così sia byrdzeye che Phrancis sono stati molto utili in questo senso. Ho scoperto che il consiglio di Phrancis è stato presentato in modo eccellente e ha fornito aiuto in una fase critica, quindi gli darò il vantaggio qui.

Gradirei ancora qualsiasi ulteriore aiuto che posso ricevere in merito alla domanda 3. I punti elenco riflettono chi credo mi abbia aiutato di più nella singola domanda.


Definizioni di tabella

Rappresentazione semi-visiva

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

Tabella ReceiverDetails

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

Tabella ValidXmitters

Field (and primary key)
  XmitID    | TEXT(25) -- primary, unique, no null, simple

Violino SQL ...

... in modo da poter giocare con le definizioni e il codice della tabella Questa domanda è per MSAccess, ma come ha sottolineato Phrancis, non esiste uno stile di violino SQL per Access. Quindi, dovresti essere in grado di andare qui per vedere le definizioni e il codice della mia tabella basati sulla risposta di Phrancis :
http://sqlfiddle.com/#!6/e9942/4 (link esterno)


JOIN: inizio

La mia attuale strategia JOIN "budella interna"

Per prima cosa crea un FirstTable_rekeyed con ordine di colonna e chiave primaria composta (RecTStamp, ReceivID, XmitID)tutti indicizzati / ordinati ASC. Ho anche creato indici su ogni colonna singolarmente. Quindi riempilo in questo modo.

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;

La query sopra riempie la nuova tabella con 153006 record e restituisce entro 10 secondi circa.

Quanto segue viene completato entro un secondo o due quando l'intero metodo viene racchiuso in un "SELECT Count (*) FROM (...)" quando viene utilizzato il metodo di subquery TOP 1

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))

Query JOIN "budella interna" precedente

Primo (fastish ... ma non abbastanza buono)

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)))

Secondo (più lento)

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; 

sfondo

Ho una tabella di telemetria (aliasata come A) di poco meno di 1 milione di voci con una chiave primaria composta basata su un DateTimetimbro, un ID trasmettitore e un ID dispositivo di registrazione. A causa di circostanze al di fuori del mio controllo, il mio linguaggio SQL è il Jet DB standard in Microsoft Access (gli utenti useranno 2007 e versioni successive). Solo circa 200.000 di queste voci sono rilevanti per la query a causa dell'ID trasmettitore.

Esiste una seconda tabella di telemetria (alias B) che coinvolge circa 50.000 voci con una sola DateTimechiave primaria

Per il primo passo, mi sono concentrato sulla ricerca dei timestamp più vicini ai timbri nella prima tabella dalla seconda tabella.


Risultati JOIN

Stranezze che ho scoperto ...

... lungo la strada durante il debug

È davvero strano scrivere la JOINlogica FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)che, come sottolineato da @byrdzeye in un commento (che da allora è scomparso) è una forma di cross-join. Si noti che la sostituzione LEFT OUTER JOINper INNER JOINil codice di cui sopra non sembra avere alcun impatto nella quantità o l'identità delle linee restituiti. Inoltre, non riesco a lasciare la clausola ON o dire ON (1=1). Il solo utilizzo di una virgola per unire (piuttosto che INNERo LEFT OUTER JOIN) genera Count(select * from A) * Count(select * from B)righe restituite in questa query, anziché solo una riga per tabella A, poiché restituisce l'espressione (A <> B OR A = B) JOIN. Questo chiaramente non è adatto. FIRSTnon sembra essere disponibile per l'uso dato un tipo di chiave primaria composta.

Il secondo JOINstile, sebbene probabilmente più leggibile, soffre di essere più lento. Ciò può essere dovuto JOINal fatto che sono necessari altri due interni alla tabella più grande e ai due CROSS JOINche si trovano in entrambe le opzioni.

A parte: la sostituzione della IIFclausola con MIN/ MAXsembra restituire lo stesso numero di voci.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
funziona per il MAXtimestamp "Before" ( ), ma non funziona direttamente per "After" ( MIN) come segue:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
perché il minimo è sempre 0 per la FALSEcondizione. Questo 0 è inferiore a qualsiasi post-epoca DOUBLE(di cui un DateTimecampo è un sottoinsieme di in Access e che questo calcolo trasforma il campo in). I IIFe MIN/ MAXmetodi Le alternative proposte per il lavoro valore AfterXTStamp perché la divisione per zero ( FALSE) genera valori nulli, quali le funzioni di aggregazione MIN e MAX saltare.

Prossimi passi

Prendendo ciò, desidero trovare i timestamp nella seconda tabella che affiancano direttamente i timestamp nella prima tabella ed eseguire un'interpolazione lineare dei valori dei dati dalla seconda tabella in base alla distanza temporale da quei punti (cioè se il timestamp da la prima tabella è il 25% della distanza tra "prima" e "dopo", vorrei che il 25% del valore calcolato provenisse dai dati del valore della seconda tabella associati al punto "dopo" e il 75% dal "prima" ). Usando il tipo di join rivisto come parte dell'intestino interno, e dopo le risposte suggerite di seguito produco ...

    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;

... che restituisce 152928 record, conformi (almeno approssimativamente) al numero finale di record previsti. Il tempo di esecuzione è probabilmente di 5-10 minuti sul mio i7-4790, 16 GB di RAM, nessun SSD, sistema Win 8.1 Pro.


Riferimento 1: MS Access è in grado di gestire valori di tempo in millisecondi - File sorgente realmente e di accompagnamento [08080011.txt]

Risposte:


10

Devo prima complimentarmi per il tuo coraggio nel fare qualcosa del genere con un Access DB, che per la mia esperienza è molto difficile fare qualcosa di simile a SQL. Comunque, sulla recensione.


Primo join

Le IIFselezioni dei campi potrebbero trarre vantaggio dall'utilizzo di un'istruzione Switch . A volte sembra essere il caso, specialmente con le cose SQL, che un SWITCH(più comunemente noto come CASEnel tipico SQL) è abbastanza veloce quando si fanno semplicemente confronti nel corpo di a SELECT. La sintassi nel tuo caso sarebbe quasi identica, sebbene un interruttore possa essere espanso per coprire una grande porzione di confronti in un campo. Qualcosa da considerare.

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

Un interruttore può anche aiutare la leggibilità, in dichiarazioni più grandi. Nel contesto:

  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

Per quanto riguarda il join stesso, penso che (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)sia buono quanto hai intenzione di ottenere, dato quello che stai cercando di fare. Non è così veloce, ma non mi aspetterei neanche.


Secondo join

Hai detto che è più lento. È anche meno leggibile dal punto di vista del codice. Dati i risultati ugualmente soddisfacenti tra 1 e 2, direi di andare per 1. Almeno è ovvio cosa stai cercando di fare in quel modo. Le subquery spesso non sono molto veloci (anche se spesso inevitabili) soprattutto in questo caso si sta lanciando un join aggiuntivo in ciascuna, il che deve certamente complicare il piano di esecuzione.

Un'osservazione, ho visto che hai usato la vecchia sintassi del join ANSI-89. È meglio evitarlo, le prestazioni saranno uguali o migliori con la sintassi di join più moderna e sono meno ambigue o più facili da leggere, più difficili da commettere errori.

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)

Nominare le cose

Penso che il modo in cui vengono chiamate le tue cose sia inutile nella migliore delle ipotesi e criptico nella peggiore delle ipotesi. A, B, A1, B1ecc. come alias di tabella penso che potrebbe essere migliore. Inoltre, penso che i nomi dei campi non siano molto buoni, ma mi rendo conto che potresti non avere il controllo su questo. Citerò rapidamente il codice senza codice sull'argomento della denominazione delle cose e lo lascerò in quel ...

"Invettiva!" Rispose la sacerdotessa. "Verbi i tuoi nomi esplicativi!"


Query "Passaggi successivi"

Non riuscivo a capire molto come fosse scritto, dovevo portarlo a un editor di testo e apportare alcune modifiche di stile per renderlo più leggibile. So che l'editor SQL di Access è al di là di tutto ciò, quindi di solito scrivo le mie query in un buon editor come Notepad ++ o Sublime Text. Alcuni dei cambiamenti stilistici che ho applicato per renderlo più leggibile:

  • 4 spazi rientri invece di 2 spazi
  • Spazi intorno agli operatori matematici e di confronto
  • Posizionamento più naturale delle parentesi graffe e del rientro (sono andato con le parentesi graffe in stile Java, ma potrei anche essere in stile C, a tua preferenza)

Quindi, a quanto pare, questa è davvero una domanda molto complicata. Per dare un senso, devo iniziare dalla query più interna, il tuo IDset di dati, che capisco è lo stesso del tuo First Join. Restituisce gli ID e i timestamp dei dispositivi in ​​cui i timestamp prima / dopo sono i più vicini, all'interno del sottoinsieme dei dispositivi a cui sei interessato. Quindi, invece di IDperché non chiamarlo ClosestTimestampID.

L' Detunione viene utilizzata una sola volta:

inserisci qui la descrizione dell'immagine

Il resto del tempo, unisce solo i valori che hai già ClosestTimestampID. Quindi invece dovremmo essere in grado di fare solo questo:

    ) 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>)

Forse non sarà un enorme guadagno in termini di prestazioni, ma tutto ciò che possiamo fare per aiutare il povero ottimizzatore Jet DB ti aiuterà!


Non riesco a scuotere la sensazione che i calcoli / algoritmo per BeforeWeighte AfterWeightche si usa per interpolare poteva essere fatto meglio, ma purtroppo non sono molto bravo con quelli.

Un suggerimento per evitare arresti anomali (sebbene non sia l'ideale a seconda dell'applicazione) sarebbe quello di suddividere le sottoquery nidificate in tabelle proprie e aggiornarle quando necessario. Non sono sicuro della frequenza con cui è necessario aggiornare i dati di origine, ma se non è troppo frequente potresti pensare di scrivere del codice VBA per pianificare un aggiornamento delle tabelle e delle tabelle derivate e lasciare semplicemente la query più esterna per estrarre da quelle tabelle anziché dalla fonte originale. Solo un pensiero, come ho detto non ideale, ma dato lo strumento potresti non avere scelta.


Tutto insieme:

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;

5
  • Aggiunti ulteriori attributi e condizioni di filtro.
  • Qualsiasi forma di cross join viene eliminata utilizzando le query nidificate min e max. Questo è il più grande guadagno in termini di prestazioni.
  • I valori del fianco minimo e massimo restituiti dalla query più nidificata interna sono valori chiave primari (scansioni) che vengono utilizzati per recuperare ulteriori attributi del fianco (lat e lon) utilizzando una ricerca per i calcoli finali (l'accesso ha un equivalente applica).
  • Gli attributi delle tabelle primarie vengono recuperati e filtrati nella query più interna e dovrebbero aiutare le prestazioni.
  • Non è necessario formattare (StrDateIso8601Msec) il valore temporale per l'ordinamento. L'uso del valore datetime dalla tabella è equivalente.

Piani di esecuzione di SQL Server (perché Access non può mostrarlo)
Senza l'ordine finale perché costoso:
Scansione indice cluster [ReceiverDetails]. [PK_ReceiverDetails] Costo 16%
Indice cluster Cerca [FirstTable]. [PK_FirstTable] Costo 19%
Indice cluster Cerca [SecondTable]. [PK_SecondTable] Costo 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] Costo 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] [TL2] Costo 16%
Clustered Index Seek [SecondTable]. [PK_SecondT [TL1] Costo 16%

Con l'ordine finale per:
Ordina costo 36%
Scansione indice cluster [ReceiverDetails]. [PK_ReceiverDetails] Costo 10%
Ricerca indice cluster [FirstTable]. [PK_FirstTable] Costo 12%
Ricerca indice cluster [SecondTable]. [PK_SecondTable] Costo 10%
Ricerca indice cluster [SecondTable]. [PK_SecondTable] Costo 10%
Ricerca cluster dell'indice [SecondTable]. [PK_SecondTable] [TL2] Costo 10%
Clustered Index Seek [SecondTable] PK_SecondTable] [TL1]

Codice costo 10% :

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;

Test delle prestazioni della mia query rispetto alla query contenente il cross join.

FirstTable è stato caricato con 13 record e SecondTable con 1.000.000.
I piani di esecuzione per la mia query non sono cambiati molto da ciò che è stato pubblicato.
Piani di esecuzione per il cross join: il
costo dei loop nidificati dell'81% utilizzando i INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
loop nidificati scende al 75% se si utilizza la scansione dell'indice CROSS JOIN SecondTable AS B' or ',SecondTable AS B
Stream 8%
[SecondTable] [UK_ID] [B] 6%
Rocchetto tabella 5%
Diversi altri indici cluster Cerca e cerca indici (simile alla mia query come pubblicata) con costo dello 0%.

Il tempo di esecuzione è di 0,007 e 8-9 secondi per la mia query e CROSS JOIN.
Confronto dei costi 0% e 100%.

Ho caricato FirstTable con 50.000 record e un singolo record in ReceiverDetails per una condizione di join ed ho eseguito la mia query.
50.013 restituiti tra 0,9 e 1,0 secondi.

Ho eseguito una seconda query con il cross join e l'ho lasciato funzionare per circa 20 minuti prima di ucciderlo.
Se la query di join incrociato viene filtrata per restituire solo i 13 originali, il tempo di esecuzione è di nuovo, 8-9 secondi.
Il posizionamento della condizione del filtro era nella posizione più interna, nella parte più esterna e in entrambe. Nessuna differenza.

Esiste una differenza tra queste due condizioni di join a favore di CROSS JOIN, la prima utilizza un predicato, CROSS JOIN non:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B


L'esecuzione della porzione ClosestTimestampID sul mio sistema restituisce istantaneamente 152928 record quando incapsulato in un conteggio (*). Il mio MSAccess si è bloccato durante la restituzione dei record effettivi in ​​quella fase - forse le tabelle temporanee dell'altro metodo registravano tutti i tipi di memoria. Penso che la query finale che produco dalla tua metodologia sarà molto simile a quella che sto attualmente utilizzando. Suppongo sia una buona cosa :)
mpag,

1
Nel tuo commento originale hai indicato che hai recuperato immediatamente alcuni record. Ciò è importante per quanto riguarda il funzionamento dell'accesso, l'elaborazione di una strategia di accesso e l'impostazione delle aspettative per i tempi di esecuzione. Si chiama esecuzione differita. (Si è arrestato in modo anomalo quando hai raggiunto l'ultimo record.) Qual è il conteggio dei record di restituzione del limite superiore previsto nella query finale?
byrdzeye,

Credo che 152928
mpag

Qual è la natura dei valori DateTime in entrambe le tabelle quando vengono aggiunti nuovi record. Sono timestamp attuali o valori recenti o completamente casuali?
byrdzeye,

la prima tabella presenta timbri DateTime 2013 o più recenti. La seconda tabella contiene i timbri DateTime che si trovano entro pochi mesi a metà 2015. Se vengono aggiunti nuovi valori, saranno probabilmente (ma non garantiti) dopo l'insieme esistente. Nuovi valori possono essere aggiunti a entrambe le tabelle.
mpag,

2

Aggiungendo una seconda risposta, non migliore della prima, ma senza cambiare nessuno dei requisiti presentati, ci sono alcuni modi per superare Access in sottomissione e apparire scattanti. 'Materializza' le complicazioni un po 'alla volta con l'efficacia usando' trigger '. Le tabelle di accesso non hanno trigger in modo da intercettare e iniettare i processi crud.

--*** 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;
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.