Access (Jet) SQL: sellos de fecha y hora en la tabla B que flanquean cada sello de fecha y hora en la tabla A


21

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 ( Xo Xmit) en función de los sellos de fecha y hora de los datos GPS disponibles en la tabla SecondTableque flanquean directamente la observación en la tabla FirstTable.

Mi objetivo inmediato para lograr el objetivo final es encontrar la manera de mejor unen FirstTablea SecondTableconseguir 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

  1. ¿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".
  2. ¿Hay alguna forma más intuitiva que no implique la (A<>B OR A=B)estructura?
    • Byrdzeye proporcionó las alternativas básicas, sin embargo, mi experiencia en el "mundo real" no se alineó con las 4 de sus estrategias de combinación que funcionan igual. Pero le damos todo el crédito por abordar los estilos de unión alternativos.
  3. 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 DateTimesello, 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 DateTimeclave 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 JOINló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 JOINde INNER JOINen 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 INNERo 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 JOINdevuelve el (A <> B OR A = B) explícito . Esto claramente no es adecuado. FIRSTno parece estar disponible para usar dado un tipo de clave primaria compuesta.

El segundo JOINestilo, aunque podría decirse que es más legible, sufre de ser más lento. Esto puede deberse a que JOINse requieren dos s internos adicionales contra la tabla más grande, así como los dos CROSS JOINs que se encuentran en ambas opciones.

Aparte: Reemplazar la IIFcláusula con MIN/ MAXparece devolver el mismo número de entradas.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
funciona para la MAXmarca 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 FALSEcondición. Este 0 es menor que cualquier post-época DOUBLE(que un DateTimecampo es un subconjunto de en Access y que este cálculo transforma el campo en). Los métodos IIFy MIN/ MAXLas 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]

Respuestas:


10

Primero debo felicitarlo por su coraje para hacer algo así con una base de datos de Access, que según mi experiencia es muy difícil de hacer algo similar a SQL. De todos modos, a la revisión.


Primero únete

Sus IIFselecciones de campo podrían beneficiarse al usar una instrucción Switch en su lugar. A veces parece ser el caso, especialmente con las cosas SQL, que a SWITCH(más comúnmente conocido como CASEen SQL típico) es bastante rápido cuando solo se hacen comparaciones simples en el cuerpo de a SELECT. La sintaxis en su caso sería casi idéntica, aunque un interruptor se puede ampliar para cubrir una gran parte de las comparaciones en un campo. Algo a tener en cuenta.

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

Un cambio también puede ayudar a la legibilidad, en declaraciones más grandes. En contexto:

  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

En cuanto a la unión en sí, creo que (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)es tan bueno como lo que vas a obtener, dado lo que estás tratando de hacer. No es tan rápido, pero tampoco esperaría que fuera así.


Segunda unión

Dijiste que esto es más lento. También es menos legible desde el punto de vista del código. Dado un conjunto de resultados igualmente satisfactorio entre 1 y 2, diría que vaya a 1. Al menos es obvio lo que está tratando de hacer de esa manera. Las subconsultas a menudo no son muy rápidas (aunque a menudo inevitables), especialmente en este caso, está agregando una combinación adicional en cada una, lo que ciertamente debe complicar el plan de ejecución.

Una observación, vi que usaste la antigua sintaxis de unión ANSI-89. Es mejor evitar eso, el rendimiento será igual o mejor con la sintaxis de unión más moderna, y son menos ambiguas o más fáciles de leer, más difíciles de cometer errores.

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)

Nombrando cosas

Creo que la forma en que se nombran tus cosas es inútil en el mejor de los casos y críptica en el peor. A, B, A1, B1etc. como alias de tabla, creo que podría ser mejor. Además, creo que los nombres de campo no son muy buenos, pero me doy cuenta de que es posible que no tenga control sobre esto. Citaré rápidamente The Codeless Code sobre el tema de nombrar cosas, y lo dejaré así ...

"¡Invective!", Respondió la sacerdotisa. "Verbo tus sustantivos improperios!"


Consulta "Próximos pasos"

No podía entender mucho cómo estaba escrito, tuve que llevarlo a un editor de texto y hacer algunos cambios de estilo para hacerlo más legible. Sé que el editor SQL de Access es más que torpe, por lo que generalmente escribo mis consultas en un buen editor como Notepad ++ o Sublime Text. Algunos de los cambios estilísticos que apliqué para hacerlo más legible:

  • Sangría de 4 espacios en lugar de 2 espacios
  • Espacios alrededor de operadores matemáticos y de comparación
  • Colocación más natural de llaves y sangría (elegí llaves de estilo Java, pero también podría ser de estilo C, según prefiera)

Como resultado, esta es una consulta muy complicada. Para tener sentido, tengo que comenzar desde la consulta más interna, su IDconjunto de datos, que entiendo es lo mismo que su primera unión. Devuelve los ID y las marcas de tiempo de los dispositivos donde las marcas de tiempo antes / después son las más cercanas, dentro del subconjunto de dispositivos que le interesan. Entonces, en lugar de IDpor qué no llamarlo ClosestTimestampID.

Su Detunión se usa solo una vez:

ingrese la descripción de la imagen aquí

El resto del tiempo, solo une los valores que ya tiene ClosestTimestampID. Entonces, en cambio, deberíamos poder hacer esto:

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

Tal vez no sea una gran ganancia de rendimiento, ¡pero cualquier cosa que podamos hacer para ayudar al pobre optimizador Jet DB ayudará!


No puedo evitar la sensación de que los cálculos / algoritmo para BeforeWeighty AfterWeightque se utiliza para interpolar se podría hacer mejor, pero por desgracia no soy muy bueno con ellos.

Una sugerencia para evitar fallas (aunque no es ideal dependiendo de su aplicación) sería dividir sus subconsultas anidadas en tablas propias y actualizarlas cuando sea necesario. No estoy seguro de con qué frecuencia necesita que se actualicen sus datos de origen, pero si no es tan frecuente, podría pensar en escribir algún código de VBA para programar una actualización de las tablas y las tablas derivadas, y simplemente dejar su consulta más externa para extraer de esas tablas en lugar de la fuente original. Solo un pensamiento, como dije, no es ideal, pero dada la herramienta, es posible que no tenga otra opción.


Todo junto:

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
  • Se agregaron atributos adicionales y condiciones de filtro.
  • Cualquier forma de unión cruzada se elimina mediante consultas anidadas mín. Y máx. Esta es la mayor ganancia de rendimiento.
  • Los valores de flanco mínimo y máximo devueltos por la consulta más anidada interna son valores de clave primaria (escaneos) que se utilizan para recuperar atributos de flanco adicionales (lat y lon) mediante la búsqueda de cálculos finales (el acceso tiene un equivalente de aplicación).
  • Los atributos de las tablas primarias se recuperan y filtran en la consulta más interna y deberían ayudar al rendimiento.
  • No es necesario formatear (StrDateIso8601Msec) el valor de tiempo para la ordenación. Usar el valor de fecha y hora de la tabla es equivalente.

Planes de ejecución de SQL Server (porque Access no puede mostrar esto)
Sin el orden final porque es costoso:
Análisis de índice agrupado [ReceiverDetails]. [PK_ReceiverDetails] Costo 16%
Búsqueda de índice agrupado [FirstTable]. [PK_FirstTable] Costo 19%
Índice agrupado Busque [SecondTable]. [PK_SecondTable] Coste 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] Cost 16%
Clustered Seek [SecondTable]. [PK_SecondTable] [TL2] Coste 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] [TL1] Costo 16%

Con el pedido final por:
Ordenar Costo 36%
Análisis de índice agrupado [ReceiverDetails]. [PK_ReceiverDetails] Costo 10%
Búsqueda de índice agrupado [FirstTable]. [PK_FirstTable] Costo 12%
Búsqueda de índice agrupado [SecondTable]. [PK_SecondTable] Costo 10%
Búsqueda de índice agrupado [SecondTable]. [PK_SecondTable] Costo 10%
Búsqueda de índice agrupado [SecondTable]. [PK_SecondTable] [TL2] Costo 10%
Búsqueda de índice agrupado [SecondTable]. [ PK_SecondTable] [TL1] Costo 10%

Código:

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;

Prueba de rendimiento de mi consulta contra la consulta que contiene la unión cruzada.

FirstTable se cargó con 13 registros y SecondTable con 1,000,000.
Los planes de ejecución para mi consulta no cambiaron mucho de lo publicado.
Planes de ejecución para la unión cruzada: el
costo de bucles anidados 81% usando INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
bucles anidados se reduce a 75% si se usa CROSS JOIN SecondTable AS B' or ',SecondTable AS B
Stream Aggregate 8%
Index Scan [SecondTable] [UK_ID] [B] 6%
Table Spool 5%
Varias otras búsquedas de índice agrupado y búsquedas de índice (similar a mi consulta publicada) con un costo del 0%.

El tiempo de ejecución es .007 y 8-9 segundos para mi consulta y CROSS JOIN.
Comparación de costos 0% y 100%.

Cargué FirstTable con 50,000 registros y un solo registro en ReceiverDetails para una condición de unión y ejecuté mi consulta.
50,013 regresaron entre 0.9 y 1.0 segundo.

Ejecuté una segunda consulta con la combinación cruzada y permití que se ejecutara durante unos 20 minutos antes de matarla.
Si la consulta de combinación cruzada se filtra para devolver solo el 13 original, el tiempo de ejecución es nuevamente, 8-9 segundos.
La colocación de la condición del filtro fue en el interior más selectivo, el más externo selecto y ambos. Ninguna diferencia.

Hay una diferencia entre estas dos condiciones de unión a favor de CROSS JOIN, la primera usa un predicado, la CROSS JOIN no:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B


La ejecución de la parte ClosestTimestampID en mi sistema devuelve instantáneamente 152928 registros cuando se encapsula en un Count (*). Mi MSAccess se bloqueó al devolver los registros reales en esa etapa; tal vez las tablas temporales del otro método estaban acaparando todo tipo de memoria. Creo que la consulta final que produzco a partir de su metodología será muy similar a la que estoy usando actualmente. Lo cual supongo que es algo bueno :)
mpag

1
En su comentario original indicó que recuperó algunos registros de inmediato. Esto es importante con respecto a cómo funciona el acceso, idear una estrategia de acceso y establecer expectativas para el tiempo de ejecución. Se llama ejecución diferida. (Se bloqueó cuando llegaste al último registro). ¿Cuál es el recuento de registros de retorno de límite superior que se espera que esté en la consulta final?
byrdzeye

Creo 152928
mpag

¿Cuál es la naturaleza de los valores de DateTime en ambas tablas a medida que se agregan nuevos registros? ¿Son marcas de tiempo actuales o valores recientes o completamente al azar?
byrdzeye

la primera tabla tiene sellos de fecha y hora que son 2013 o más recientes. La segunda tabla tiene sellos de fecha y hora que se encuentran dentro de unos meses a mediados de 2015. Si se agregan nuevos valores, es probable que sean (pero no se garantiza que sean) después del conjunto existente. Se pueden agregar nuevos valores a cualquier tabla.
mpag

2

Agregando una segunda respuesta, no mejor que la primera, pero sin cambiar ninguno de los requisitos presentados, hay algunas formas de vencer a Access en el envío y parecer ágil. 'Materialice' las complicaciones un poco a la vez usando 'disparadores'. Las tablas de acceso no tienen desencadenantes, por lo que intercepta e inyecta los procesos 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;
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.