Premiers mots
Vous pouvez ignorer en toute sécurité les sections ci-dessous (et y compris) JOINs: Démarrage si vous voulez juste prendre une fissure du code. Le contexte et les résultats servent simplement de contexte. Veuillez consulter l'historique des modifications avant le 06/10/2015 si vous souhaitez voir à quoi ressemblait le code au départ.
Objectif
En fin de compte, je veux calculer les coordonnées GPS interpolées pour l'émetteur ( X
ou Xmit
) sur la base des tampons DateTime des données GPS disponibles dans le tableau SecondTable
qui encadrent directement l'observation dans le tableau FirstTable
.
Mon objectif immédiat d'atteindre l'objectif ultime est de savoir comment mieux rejoindre FirstTable
pour SecondTable
obtenir ces points de temps flanquant. Plus tard, je peux utiliser ces informations, je peux calculer des coordonnées GPS intermédiaires en supposant un ajustement linéaire le long d'un système de coordonnées équirectangulaires (des mots fantaisistes pour dire que je me fiche que la Terre soit une sphère à cette échelle).
Des questions
- Existe-t-il un moyen plus efficace de générer les horodatages avant et après les plus proches?
- Fixé par moi-même en saisissant simplement «l'après», puis en obtenant «l'avant» uniquement en ce qui concerne «l'après».
- Existe-t-il une manière plus intuitive qui n'implique pas la
(A<>B OR A=B)
structure.- Byrdzeye a fourni les alternatives de base, mais mon expérience "dans le monde réel" ne correspondait pas aux quatre stratégies de jointure qui effectuaient la même chose. Mais tout le mérite lui revient d'avoir abordé les styles de jointure alternatifs.
- Toutes autres pensées, astuces et conseils que vous pourriez avoir.
- Ainsi, byrdzeye et Phrancis ont été très utiles à cet égard. J'ai trouvé que les conseils de Phrancis étaient très bien présentés et ont fourni de l'aide à un stade critique, alors je vais lui donner l'avantage ici.
J'apprécierais toujours toute aide supplémentaire que je pourrais recevoir en ce qui concerne la question 3. Les puces reflètent qui, selon moi, m'a le plus aidé sur la question individuelle.
Définitions de table
Représentation semi-visuelle
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
Tableau 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
Table ValidXmitters
Field (and primary key)
XmitID | TEXT(25) -- primary, unique, no null, simple
Violon SQL ...
... afin que vous puissiez jouer avec les définitions et le code de la table Cette question s'adresse à MSAccess, mais comme Phrancis l'a souligné, il n'y a pas de style SQL pour Access. Donc, vous devriez pouvoir aller ici pour voir mes définitions de table et mon code basé sur la réponse de Phrancis :
http://sqlfiddle.com/#!6/e9942/4 (lien externe)
JOINs: Démarrage
Ma stratégie "JOIN"
Créez d'abord un FirstTable_rekeyed avec l'ordre des colonnes et la clé primaire composée (RecTStamp, ReceivID, XmitID)
tous indexés / triés ASC
. J'ai également créé des index sur chaque colonne individuellement. Remplissez-le ainsi.
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 requête ci-dessus remplit la nouvelle table avec 153006 enregistrements et retourne en l'espace de 10 secondes environ.
Ce qui suit se termine en une seconde ou deux lorsque toute cette méthode est enveloppée dans un "SELECT Count (*) FROM (...)" lorsque la méthode de sous-requête TOP 1 est utilisée
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))
Requête JOIN "entrailles" précédente
D'abord (fastish ... mais pas assez bon)
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)))
Deuxième (plus lent)
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;
Contexte
J'ai une table de télémétrie (aliasée A) d'un peu moins de 1 million d'entrées avec une clé primaire composée basée sur un DateTime
tampon, un ID d'émetteur et un ID de périphérique d'enregistrement. En raison de circonstances indépendantes de ma volonté, mon langage SQL est le Jet DB standard dans Microsoft Access (les utilisateurs utiliseront les versions 2007 et ultérieures). Seulement environ 200 000 de ces entrées sont pertinentes pour la requête en raison de l'ID de l'émetteur.
Il existe une deuxième table de télémétrie (alias B) qui implique environ 50 000 entrées avec une seule DateTime
clé primaire
Pour la première étape, je me suis concentré sur la recherche des horodatages les plus proches des tampons dans le premier tableau à partir du second tableau.
Résultats JOIN
Des bizarreries que j'ai découvertes ...
... en cours de route lors du débogage
Cela semble vraiment étrange d'écrire la JOIN
logique FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)
qui, comme @byrdzeye l'a souligné dans un commentaire (qui a depuis disparu) est une forme de jointure croisée. Notez que le remplacement LEFT OUTER JOIN
de INNER JOIN
dans le code ci-dessus semble n'avoir aucun impact sur la quantité ou l'identité des lignes retournées. Je n'arrive pas non plus à laisser de côté la clause ON ou à dire ON (1=1)
. Le simple fait d'utiliser une virgule pour joindre (plutôt que INNER
ou LEFT OUTER
JOIN
) entraîne des Count(select * from A) * Count(select * from B)
lignes renvoyées dans cette requête, plutôt qu'une seule ligne par table A, comme le JOIN
renvoie explicitement (A <> B OU A = B) . Ce n'est clairement pas approprié. FIRST
ne semble pas être disponible à utiliser étant donné un type de clé primaire composé.
Le deuxième JOIN
style, bien que sans doute plus lisible, souffre d'être plus lent. Cela peut être dû au fait que deux internes supplémentaires JOIN
sont nécessaires par rapport à la plus grande table ainsi que les deux CROSS JOIN
disponibles dans les deux options.
En plus: le remplacement de la IIF
clause par MIN
/ MAX
semble renvoyer le même nombre d'entrées.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
fonctionne pour l' MAX
horodatage "Before" ( ), mais ne fonctionne pas directement pour "After" ( MIN
) comme suit:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
car le minimum est toujours 0 pour la FALSE
condition. Ce 0 est inférieur à n'importe quelle post-époque DOUBLE
(dans laquelle un DateTime
champ est un sous-ensemble d'Access et que ce calcul transforme le champ). Les méthodes IIF
et MIN
/ MAX
Les alternatives proposées pour la valeur AfterXTStamp fonctionnent car la division par zéro ( FALSE
) génère des valeurs nulles, que les fonctions d'agrégation MIN et MAX ignorent.
Prochaines étapes
Pour aller plus loin, je souhaite trouver les horodatages dans le deuxième tableau qui flanquent directement les horodatages dans le premier tableau et effectuer une interpolation linéaire des valeurs de données du deuxième tableau en fonction de la distance temporelle à ces points (c'est-à-dire si l'horodatage de la première table est à 25% du chemin entre "avant" et "après", je voudrais que 25% de la valeur calculée provienne des données de la 2ème valeur de table associées au point "après" et 75% de la "avant" ). En utilisant le type de jointure révisé dans le cadre des entrailles intérieures, et après les réponses suggérées ci-dessous, je produis ...
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;
... qui renvoie 152928 enregistrements, conformes (au moins approximativement) au nombre final d'enregistrements attendus. Le temps d'exécution est probablement de 5 à 10 minutes sur mon i7-4790, 16 Go de RAM, pas de SSD, système Win 8.1 Pro.
Référence 1: MS Access peut gérer des valeurs de temps en millisecondes - Vraiment et le fichier source qui l'accompagne [08080011.txt]