Primeras palabras
Puede ignorar con seguridad las secciones siguientes (e incluir) UNIONES: Comenzando si solo desea descifrar el código. El fondo y los resultados solo sirven como contexto. Mire el historial de edición antes del 2015-10-06 si quiere ver cómo se veía el código inicialmente.
Objetivo
En última instancia, quiero calcular las coordenadas GPS interpoladas para el transmisor ( X
o Xmit
) en función de los sellos de fecha y hora de los datos GPS disponibles en la tabla SecondTable
que flanquean directamente la observación en la tabla FirstTable
.
Mi objetivo inmediato para lograr el objetivo final es encontrar la manera de mejor unen FirstTable
a SecondTable
conseguir esos puntos de tiempo de flanqueo. Más tarde, puedo usar esa información, puedo calcular coordenadas GPS intermedias suponiendo un ajuste lineal a lo largo de un sistema de coordenadas equirrectangulares (palabras elegantes para decir que no me importa que la Tierra sea una esfera a esta escala).
Preguntas
- ¿Existe una manera más eficiente de generar las marcas de tiempo antes y después más cercanas?
- Lo arreglé yo solo agarrando el "después" y luego obteniendo el "antes" solo en relación con el "después".
- ¿Hay alguna forma más intuitiva que no implique la
(A<>B OR A=B)
estructura? - Cualquier otro pensamiento, truco y consejo que pueda tener.
- Hasta ahora, tanto byrdzeye como Phrancis han sido muy útiles a este respecto. Descubrí que el consejo de Phrancis fue excelentemente diseñado y proporcionó ayuda en una etapa crítica, así que le daré la ventaja aquí.
Todavía agradecería cualquier ayuda adicional que pueda recibir con respecto a la pregunta 3. Los puntos en la viñeta reflejan quién creo que me ayudó más en la pregunta individual.
Definiciones de tabla
Representación semi-visual
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
Tabla de detalles del receptor
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
Tabla ValidXmitters
Field (and primary key)
XmitID | TEXT(25) -- primary, unique, no null, simple
Violín SQL ...
... para que pueda jugar con las definiciones y el código de la tabla Esta pregunta es para MSAccess, pero como señaló Phrancis, no hay un estilo de violín SQL para Access. Entonces, debería poder ir aquí para ver las definiciones y el código de mi tabla basados en la respuesta de Phrancis :
http://sqlfiddle.com/#!6/e9942/4 (enlace externo)
ÚNETE: Comenzando
Mis actuales "entrañas internas" ÚNETE Estrategia
Primero cree un FirstTable_rekeyed con orden de columnas y clave primaria compuesta, (RecTStamp, ReceivID, XmitID)
todas indexadas / ordenadas ASC
. También creé índices en cada columna individualmente. Luego llénalo así.
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 consulta anterior llena la nueva tabla con 153006 registros y regresa en cuestión de 10 segundos más o menos.
Lo siguiente se completa en uno o dos segundos cuando todo este método se envuelve en un "SELECT Count (*) FROM (...)" cuando se utiliza el método de subconsulta 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))
Consulta previa de "tripas internas" JOIN
Primero (rápido ... pero no lo suficientemente bueno)
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)))
Segundo (más 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;
Fondo
Tengo una tabla de telemetría (con alias como A) de poco menos de 1 millón de entradas con una clave primaria compuesta basada en un DateTime
sello, una ID del transmisor y una ID del dispositivo de grabación. Debido a circunstancias fuera de mi control, mi lenguaje SQL es el Jet DB estándar en Microsoft Access (los usuarios usarán 2007 y versiones posteriores). Solo alrededor de 200,000 de estas entradas son relevantes para la consulta debido a la ID del transmisor.
Hay una segunda tabla de telemetría (alias B) que involucra aproximadamente 50,000 entradas con una sola DateTime
clave primaria
Para el primer paso, me concentré en encontrar las marcas de tiempo más cercanas a los sellos en la primera tabla de la segunda tabla.
ÚNASE Resultados
Extravagantes que he descubierto ...
... en el camino durante la depuración
Se siente realmente extraño escribir la JOIN
lógica FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)
que, como señaló @byrdzeye en un comentario (que desde entonces ha desaparecido) es una forma de unión cruzada. Tenga en cuenta que la sustitución LEFT OUTER JOIN
de INNER JOIN
en el código anterior parece tener ningún impacto en la cantidad o la identidad de las líneas devueltos. También parece que no puedo dejar de lado la cláusula ON o decir ON (1=1)
. Simplemente usar una coma para unir (en lugar de INNER
o LEFT OUTER
JOIN
) da como resultado Count(select * from A) * Count(select * from B)
filas devueltas en esta consulta, en lugar de solo una línea por tabla A, como JOIN
devuelve el (A <> B OR A = B) explícito . Esto claramente no es adecuado. FIRST
no parece estar disponible para usar dado un tipo de clave primaria compuesta.
El segundo JOIN
estilo, aunque podría decirse que es más legible, sufre de ser más lento. Esto puede deberse a que JOIN
se requieren dos s internos adicionales contra la tabla más grande, así como los dos CROSS JOIN
s que se encuentran en ambas opciones.
Aparte: Reemplazar la IIF
cláusula con MIN
/ MAX
parece devolver el mismo número de entradas.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
funciona para la MAX
marca de tiempo "Antes" ( ), pero no funciona directamente para "Después" ( MIN
) de la siguiente manera:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
porque el mínimo siempre es 0 para la FALSE
condición. Este 0 es menor que cualquier post-época DOUBLE
(que un DateTime
campo es un subconjunto de en Access y que este cálculo transforma el campo en). Los métodos IIF
y MIN
/ MAX
Las alternativas propuestas para el valor AfterXTStamp funcionan porque la división por cero ( FALSE
) genera valores nulos, que las funciones agregadas MIN y MAX omiten.
Próximos pasos
Llevando esto más lejos, deseo encontrar las marcas de tiempo en la segunda tabla que flanquean directamente las marcas de tiempo en la primera tabla y realizar una interpolación lineal de los valores de datos de la segunda tabla en función de la distancia de tiempo a esos puntos (es decir, si la marca de tiempo de la primera tabla es el 25% del camino entre el "antes" y el "después", me gustaría que el 25% del valor calculado provenga de los datos del valor de la segunda tabla asociados con el punto "después" y el 75% del "antes" ) Usando el tipo de unión revisado como parte de las tripas internas, y después de las respuestas sugeridas a continuación, produzco ...
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;
... que devuelve 152928 registros, conforme (al menos aproximadamente) al número final de registros esperados. El tiempo de ejecución es probablemente de 5-10 minutos en mi i7-4790, 16 GB de RAM, sin SSD, sistema Win 8.1 Pro.
Referencia 1: MS Access puede manejar valores de tiempo de milisegundos - Realmente y el archivo fuente que lo acompaña [08080011.txt]