Acessar (Jet) SQL: carimbos de data e hora na tabela B que flanqueiam cada carimbo de data e hora na tabela A

21

Primeiras palavras

Você pode ignorar com segurança as seções abaixo (incluindo) JOINs: iniciando se você quiser apenas entender o código. Os antecedentes e os resultados servem apenas como contexto. Consulte o histórico de edições antes de 06/10/2015, se quiser ver como era o código inicialmente.


Objetivo

Por fim, quero calcular coordenadas GPS interpoladas para o transmissor ( Xou Xmit) com base nos carimbos de data / hora dos dados de GPS disponíveis na tabela SecondTableque flanqueiam diretamente a observação na tabela FirstTable.

Meu objetivo imediato de alcançar o objetivo final é descobrir a melhor forma de aderir FirstTablea SecondTableesses pontos de tempo de flanqueamento. Posteriormente, posso usar essas informações para calcular coordenadas intermediárias de GPS, assumindo um ajuste linear ao longo de um sistema de coordenadas equiretangular (palavras sofisticadas para dizer que não me importo que a Terra seja uma esfera nessa escala).


Questões

  1. Existe uma maneira mais eficiente de gerar os carimbos de hora antes e depois mais próximos?
    • Corrigido por mim mesmo apenas pegando o "depois" e depois obtendo o "antes" apenas no que se refere ao "depois".
  2. Existe uma maneira mais intuitiva que não envolva a (A<>B OR A=B)estrutura.
    • Byrdzeye forneceu as alternativas básicas, no entanto, minha experiência no "mundo real" não se alinhou com todas as quatro estratégias de junção da mesma maneira. Mas o crédito total a ele por abordar os estilos de união alternativos.
  3. Quaisquer outros pensamentos, truques e conselhos que possa ter.
    • Assim, byrdzeye e Phrancis têm sido bastante úteis nesse sentido. Descobri que o conselho de Phrancis foi excelentemente apresentado e prestou ajuda em um estágio crítico, por isso darei a ele a vantagem aqui.

Eu ainda gostaria de receber qualquer ajuda adicional que eu possa receber com relação à pergunta 3. Os marcadores refletem quem eu acredito que me ajudou mais na questão individual.


Definições da tabela

Representação 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

Tabela 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

Tabela ValidXmitters

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

Violino SQL ...

... para que você possa brincar com as definições e o código da tabela Esta pergunta é para o MSAccess, mas como Phrancis apontou, não há estilo de violino SQL para o Access. Portanto, você deve poder ir aqui para ver minhas definições e código de tabela com base na resposta de Phrancis :
http://sqlfiddle.com/#!6/e9942/4 (link externo)


JOINs: começando

Minha atual "coragem interior"

Primeiro, crie uma FirstTable_rekeyed com a ordem das colunas e a chave primária composta, (RecTStamp, ReceivID, XmitID)todas indexadas / classificadas ASC. Também criei índices em cada coluna individualmente. Em seguida, preencha assim.

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;

A consulta acima preenche a nova tabela com 153006 registros e retorna em questão de 10 segundos ou mais.

O seguinte é concluído dentro de um ou dois segundos quando todo esse método é agrupado em um "SELECT Count (*) FROM (...)" quando o método da subconsulta TOP 1 é usado

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 JOIN "coragem interior" anterior

Primeiro (fastish ... mas não é bom o suficiente)

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 (mais 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; 

fundo

Eu tenho uma tabela de telemetria (com o nome A) de pouco menos de 1 milhão de entradas com uma chave primária composta com base em um DateTimecarimbo, um ID do transmissor e um ID do dispositivo de gravação. Devido a circunstâncias fora do meu controle, minha linguagem SQL é o Jet DB padrão no Microsoft Access (os usuários usarão 2007 e versões posteriores). Apenas cerca de 200.000 dessas entradas são relevantes para a consulta devido ao ID do transmissor.

Há uma segunda tabela de telemetria (alias B) que envolve aproximadamente 50.000 entradas com uma única DateTimechave primária

Na primeira etapa, concentrei-me em encontrar os carimbos de data e hora mais próximos dos carimbos na primeira tabela da segunda tabela.


Resultados JOIN

Peculiaridades que eu descobri ...

... ao longo do caminho durante a depuração

Parece realmente estranho escrever a JOINlógica, FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)que como @byrdzeye apontou em um comentário (que desapareceu desde então) é uma forma de junção cruzada. Note-se que a substituição LEFT OUTER JOINde INNER JOINno código acima parece fazer nenhum impacto na quantidade ou identidade das linhas retornadas. Também não consigo deixar de fora a cláusula ON ou dizer ON (1=1). O simples uso de uma vírgula para ingressar (em vez de INNERou LEFT OUTER JOIN) resulta em Count(select * from A) * Count(select * from B)linhas retornadas nesta consulta, em vez de apenas uma linha por tabela A, conforme JOINretornos explícitos (A <> B OR A = B) . Claramente, isso não é adequado. FIRSTparece não estar disponível para uso, devido a um tipo de chave primária composta.

O segundo JOINestilo, embora indiscutivelmente mais legível, sofre por ser mais lento. Isso pode ocorrer porque JOINsão necessários dois s internos adicionais em relação à tabela maior, bem como os dois CROSS JOINs encontrados nas duas opções.

Lado: Substituir a IIFcláusula por MIN/ MAXparece retornar o mesmo número de entradas.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
funciona para o MAXcarimbo de data / hora "Antes" ( ), mas não funciona diretamente para o "Depois" ( MIN) da seguinte maneira:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
porque o mínimo é sempre 0 para a FALSEcondição. Esse 0 é menor que qualquer pós-época DOUBLE(da qual um DateTimecampo é um subconjunto no Access e que esse cálculo transforma o campo em). Os métodos IIFe MIN/ MAXAs alternativas propostas para o valor AfterXTStamp funcionam porque a divisão por zero ( FALSE) gera valores nulos, que as funções agregadas MIN e MAX ignoram.

Próximos passos

Levando isso adiante, desejo encontrar os registros de data e hora na segunda tabela que flanqueiam diretamente os registros de data e hora na primeira tabela e executar uma interpolação linear dos valores dos dados da segunda tabela com base na distância do tempo até esses pontos (por exemplo, se o registro de data e hora de a primeira tabela fica a 25% do caminho entre o "antes" e o "depois", gostaria que 25% do valor calculado viesse dos dados do valor da 2ª tabela associados ao ponto "depois" e 75% do "antes" ) Usando o tipo de junção revisado como parte das entranhas internas, e após as respostas sugeridas abaixo, produzo ...

    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 retorna 152928 registros, em conformidade (pelo menos aproximadamente) com o número final de registros esperados. O tempo de execução é provavelmente de 5 a 10 minutos no meu i7-4790, 16 GB de RAM, sem SSD, sistema Win 8.1 Pro.


Referência 1: O MS Access pode manipular valores de tempo em milissegundos - realmente e acompanha o arquivo de origem [08080011.txt]

mpag
fonte

Respostas:

10

Primeiro, devo elogiá-lo por sua coragem de fazer algo assim com um banco de dados do Access, o que, pela minha experiência, é muito difícil fazer algo semelhante ao SQL. De qualquer forma, para a revisão.


Primeira participação

Suas IIFseleções de campo podem se beneficiar do uso de uma instrução Switch . Às vezes parece, especialmente no caso do SQL, que a SWITCH(mais comumente conhecido como CASEno SQL típico) é bastante rápido ao fazer comparações simples no corpo de a SELECT. A sintaxe no seu caso seria quase idêntica, embora uma opção possa ser expandida para cobrir uma grande parte das comparações em um campo. Algo a considerar.

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

Um switch também pode ajudar na legibilidade, em declarações maiores. No 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

Quanto à junção em si, acho que (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)é o melhor que você conseguirá, considerando o que está tentando fazer. Não é tão rápido, mas eu também não esperava que fosse.


Segunda junção

Você disse que isso é mais lento. Também é menos legível do ponto de vista do código. Dado conjuntos de resultados igualmente satisfatórios entre 1 e 2, eu diria que vá para 1. Pelo menos é óbvio o que você está tentando fazer dessa maneira. As subconsultas geralmente não são muito rápidas (embora muitas vezes inevitáveis), especialmente neste caso, você está lançando uma junção extra em cada uma delas, o que certamente deve complicar o plano de execução.

Uma observação, vi que você usava a sintaxe antiga de junção ANSI-89. É melhor evitar isso, o desempenho será igual ou melhor com a sintaxe de junção mais moderna e elas são menos ambíguas ou mais fáceis de ler, mais difíceis de cometer erros.

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)

Nomeando coisas

Eu acho que a maneira como suas coisas são nomeadas é inútil, na melhor das hipóteses, e enigmática, na pior das hipóteses. A, B, A1, B1etc. como alias de tabela, acho que poderia ser melhor. Além disso, acho que os nomes dos campos não são muito bons, mas sei que você pode não ter controle sobre isso. Vou citar rapidamente O Código Sem Código sobre o tema de nomear coisas e deixar por isso mesmo ...

“Invectivo!” Respondeu a sacerdotisa. "Verbo seus substantivos palavrões!"


Consulta "Próximas etapas"

Eu não conseguia entender muito bem como foi escrito, tive que levá-lo a um editor de texto e fazer algumas mudanças de estilo para torná-lo mais legível. Sei que o editor SQL do Access está além de desajeitado, por isso costumo escrever minhas consultas em um bom editor como o Notepad ++ ou Sublime Text. Algumas das mudanças estilísticas que apliquei para torná-lo mais legível:

  • Recuo de 4 espaços em vez de 2 espaços
  • Espaços em torno de operadores matemáticos e de comparação
  • Colocação mais natural de chaves e recuo (usei chaves no estilo Java, mas também poderia ser no estilo C, conforme sua preferência)

Portanto, essa é uma consulta muito complicada. Para entender isso, eu tenho que começar a partir da consulta mais interna, seu IDconjunto de dados, que eu entendo é o mesmo que sua Primeira Junção. Ele retorna os IDs e os registros de data e hora dos dispositivos nos quais os registros de data e hora anteriores e posteriores são os mais próximos, dentro do subconjunto de dispositivos de seu interesse. Portanto, em vez de IDpor que não chamá-lo ClosestTimestampID.

Sua Detassociação é usada apenas uma vez:

insira a descrição da imagem aqui

O resto do tempo, ele une apenas os valores que você já possui ClosestTimestampID. Então, em vez disso, devemos ser capazes de fazer isso:

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

Talvez não seja um enorme ganho de desempenho, mas qualquer coisa que possamos fazer para ajudar o pobre otimizador do Jet DB ajudará!


Eu não posso afastar a sensação de que os cálculos / algoritmo para BeforeWeighte AfterWeightque você usa para interpolate poderia ser feito melhor, mas infelizmente eu não sou muito bom com aqueles.

Uma sugestão para evitar falhas (embora não seja o ideal, dependendo do seu aplicativo) seria dividir suas subconsultas aninhadas em tabelas próprias e atualizá-las quando necessário. Não sei com que frequência você precisa que seus dados de origem sejam atualizados, mas se não forem com muita frequência, pense em escrever algum código VBA para agendar uma atualização das tabelas e tabelas derivadas e deixe sua consulta mais externa para puxar dessas tabelas em vez da fonte original. Apenas um pensamento, como eu disse, não é o ideal, mas, dada a ferramenta, você pode não ter escolha.


Tudo 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;
Phrancis
fonte
5
  • Atributos adicionais e condições de filtro adicionados.
  • Qualquer forma de junção cruzada é eliminada usando consultas mínimas e máximas aninhadas. Este é o maior ganho de desempenho.
  • Os valores mínimo e máximo do flanco retornados pela consulta interna mais aninhada são valores da chave primária (varreduras) que são usados ​​para recuperar atributos adicionais do flanco (lat e lon) usando uma busca pelos cálculos finais (o acesso tem um equivalente aplicável).
  • Os atributos das tabelas primárias são recuperados e filtrados na consulta mais interna e devem ajudar no desempenho.
  • Não há necessidade de formatar (StrDateIso8601Msec) o valor do tempo para classificação. Usar o valor datetime da tabela é equivalente.

Planos de execução do SQL Server (porque o Access não pode mostrar isso)
Sem o pedido final devido ao alto custo:
Varredura de índice em cluster [ReceiverDetails]. [PK_ReceiverDetails] Custo 16% em
busca de índice em cluster [FirstTable]. [PK_FirstTable] Custo em 19%
Índice em cluster Buscar [SecondTable]. [PK_SecondTable] Custo 16% em
cluster de busca Buscar [SecondTable]. [PK_SecondTable] Custo em 16% em
busca de índice em cluster [SecondTable]. [PK_SecondTable] [TL2] Custo em 16% em
busca de índice em cluster [SecondTable]. [PK_SecondTable] [TL1] Custo 16%

Com o pedido final por:
Classificar Custo 36%
Verificação de Índice em Cluster [ReceiverDetails]. [PK_ReceiverDetails] Custo 10% em
Busca de Índice em Cluster [FirstTable]. [PK_FirstTable] Custo 12%
Busca de índice em cluster [SecondTable]. [PK_SecondTable] Custo 10%
Busca de índice em cluster [SecondTable]. [PK_SecondTable] Custo de 10% em
busca de índice em cluster [SecondTable]. [PK_SecondTable] [TL2] Custo de 10% de
busca em índice em cluster [SecondTable]. [ PK_SecondTable] [TL1] Custo 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;

Desempenho testando minha consulta contra a consulta que contém a junção cruzada.

O FirstTable foi carregado com 13 registros e o SecondTable com 1.000.000.
Os planos de execução da minha consulta não mudaram muito em relação ao que foi postado.
Planos de execução para a junção cruzada:
loops aninhados O custo de 81% usando INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
loops aninhados cai para 75% se usando a Varredura de índice CROSS JOIN SecondTable AS B' or ',SecondTable AS B
agregada de 8% do fluxo
[SecondTable] [UK_ID] [B] 6% de
spool de tabela 5%
Vários outros grupos de buscas e pesquisas de índice em cluster (semelhante à minha consulta publicada) com custo de 0%.

O tempo de execução é de 0,007 e 8 a 9 segundos para minha consulta e o CROSS JOIN.
Comparação de custos 0% e 100%.

Carreguei o FirstTable com 50.000 registros e um único registro no ReceiverDetails para obter uma condição de associação e executei minha consulta.
50.013 retornaram entre 0,9 e 1,0 segundo.

Executei a segunda consulta com a junção cruzada e permiti que ela fosse executada por cerca de 20 minutos antes de matá-la.
Se a consulta de junção cruzada for filtrada para retornar apenas os 13 originais, o tempo de execução será novamente, 8 a 9 segundos.
A colocação da condição do filtro foi no interior mais seleto, externo mais seleto e ambos. Não faz diferença.

Há uma diferença entre essas duas condições de junção em favor do CROSS JOIN, o primeiro usa um predicado, o CROSS JOIN não:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B

byrdzeye
fonte
A execução da parte ClosestTimestampID no meu sistema retorna instantaneamente 152928 registros quando encapsulados em um Count (*). Meu MSAccess travou ao retornar os registros reais nesse estágio - talvez as tabelas temporárias do outro método estivessem sobrecarregando todos os tipos de memória. Penso que a consulta final que produzo a partir da sua metodologia será muito semelhante à que estou usando atualmente. Que suponho que é uma coisa boa :)
MPAG
1
No seu comentário original, você indicou que recuperou alguns registros imediatamente. Isso é importante no que diz respeito à maneira como o acesso funciona, apresentando uma estratégia de acesso e definindo expectativas para o tempo de execução. É chamado de execução adiada. (Ele travou quando você atingiu o último registro.) Qual é a contagem máxima de registros de retorno limite esperada na consulta final?
Bydzeye 13/10
Eu acredito que 152928
mpag 13/10/2015
Qual é a natureza dos valores DateTime em ambas as tabelas à medida que novos registros são adicionados. São carimbos de data / hora atuais ou valores recentes ou completamente aleatórios?
Bydzeye
a primeira tabela possui carimbos de data e hora 2013 ou mais recentes. A segunda tabela possui selos DateTime que estão dentro de alguns meses em meados de 2015. Se novos valores forem adicionados, eles provavelmente serão (mas não garantidos) após o conjunto existente. Novos valores podem ser adicionados a qualquer tabela.
Mpag 15/10
2

Adicionando uma segunda resposta, não melhor que a primeira, mas sem alterar nenhum dos requisitos apresentados, existem algumas maneiras de colocar o Access em envio e parecer rápido. 'Materialize' as complicações, um pouco de cada vez, usando 'gatilhos'. As tabelas de acesso não possuem gatilhos para interceptar e injetar os processos brutos.

--*** 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;
byrdzeye
fonte