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 ( X
o Xmit
) in base ai timbri DateTime dei dati GPS disponibili nella tabella SecondTable
che affiancano direttamente l'osservazione nella tabella FirstTable
.
Il mio obiettivo immediato di raggiungere l'obiettivo finale è quello di capire come unirmi FirstTable
al meglio SecondTable
per 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
- 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".
- 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.
- 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 DateTime
timbro, 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 DateTime
chiave 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 JOIN
logica 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 JOIN
per INNER JOIN
il 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 INNER
o 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. FIRST
non sembra essere disponibile per l'uso dato un tipo di chiave primaria composta.
Il secondo JOIN
stile, sebbene probabilmente più leggibile, soffre di essere più lento. Ciò può essere dovuto JOIN
al fatto che sono necessari altri due interni alla tabella più grande e ai due CROSS JOIN
che si trovano in entrambe le opzioni.
A parte: la sostituzione della IIF
clausola con MIN
/ MAX
sembra restituire lo stesso numero di voci.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
funziona per il MAX
timestamp "Before" ( ), ma non funziona direttamente per "After" ( MIN
) come segue:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
perché il minimo è sempre 0 per la FALSE
condizione. Questo 0 è inferiore a qualsiasi post-epoca DOUBLE
(di cui un DateTime
campo è un sottoinsieme di in Access e che questo calcolo trasforma il campo in). I IIF
e MIN
/ MAX
metodi 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]