Access (Jet) SQL: отметки DateTime в таблице B, фланкирующие каждую отметку DateTime в таблице A

21

Первые слова

Вы можете спокойно игнорировать разделы ниже (и в том числе) JOINs: начиная с Off, если вы просто хотите взломать код. Фон и результаты просто служат в качестве контекста. Пожалуйста, посмотрите историю изменений до 2015-10-06, если вы хотите увидеть, как изначально выглядел код.


Задача

В конечном итоге я хочу рассчитать интерполированные GPS-координаты для передатчика ( Xили Xmit) на основе меток DateTime доступных GPS-данных в таблице, SecondTableкоторые располагаются непосредственно по направлению к наблюдению в таблице FirstTable.

Моя ближайшая цель для достижения конечной цели, чтобы выяснить , как лучше всего присоединиться FirstTableк SecondTableполучить эти фланговые моменты времени. Позже я смогу использовать эту информацию, чтобы вычислить промежуточные координаты GPS, предполагая линейную подгонку вдоль равносторонней системы координат (причудливые слова, чтобы сказать, что мне все равно, что Земля - ​​это сфера в этом масштабе).


Вопросов

  1. Есть ли более эффективный способ генерировать самые близкие метки времени до и после?
    • Я сам исправил это, просто схватив «после», а затем получив «до» только в том случае, если он связан с «после».
  2. Есть ли более интуитивный способ, который не включает в себя (A<>B OR A=B)структуру.
    • Byrdzeye предоставил основные альтернативы, однако мой опыт «реального мира» не совпал со всеми четырьмя его стратегиями объединения, выполняющими то же самое. Но ему следует отдать должное за рассмотрение альтернативных стилей соединения.
  3. Любые другие мысли, хитрости и советы, которые вы можете иметь.
    • Thusfar как byrdzeye и Phrancis были весьма полезными в этом отношении. Я обнаружил, что совет Фрэнсиса был превосходно изложен и оказал помощь на критическом этапе, поэтому я дам ему преимущество.

Я по-прежнему был бы признателен за любую дополнительную помощь, которую я могу получить в отношении вопроса 3. В маркированных списках отражено, кто, по моему мнению, больше всего помог мне в данном вопросе.


Табличные Определения

Полувизуальное представление

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

Таблица 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

Таблица ValidXmitters

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

SQL скрипка ...

... чтобы вы могли поиграть с определениями таблиц и кодом. Этот вопрос относится к MSAccess, но, как указал Франсис, для Access нет стиля SQL-скрипки. Итак, вы должны быть в состоянии пойти сюда, чтобы увидеть мои определения таблиц и код, основанный на ответе Франциса :
http://sqlfiddle.com/#!6/e9942/4 (внешняя ссылка)


ПРИСОЕДИНЯЕТСЯ: Начиная

Мои текущие "внутренние силы"

Сначала создайте FirstTable_rekeyed с порядком столбцов и составным первичным ключом, который (RecTStamp, ReceivID, XmitID)проиндексирован / отсортирован ASC. Я также создал индексы для каждого столбца в отдельности. Тогда заполните это так.

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;

Приведенный выше запрос заполняет новую таблицу 153006 записями и возвращается в течение примерно 10 секунд.

Следующее завершается в течение одной или двух секунд, когда весь этот метод заключен в «SELECT Count (*) FROM (...)», когда используется метод подзапроса 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))

Предыдущий запрос "Внутренние кишки"

Сначала (быстро ... но недостаточно хорошо)

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

Второй (медленнее)

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; 

Задний план

У меня есть таблица телеметрии (с псевдонимом A) чуть менее 1 миллиона записей с составным первичным ключом на основе DateTimeштампа, идентификатора передатчика и идентификатора записывающего устройства. Из-за не зависящих от меня обстоятельств моим языком SQL является стандартная Jet DB в Microsoft Access (пользователи будут использовать версии 2007 и более поздние). Только около 200 000 из этих записей относятся к запросу из-за идентификатора передатчика.

Существует вторая таблица телеметрии (псевдоним B), которая включает приблизительно 50 000 записей с одним DateTimeпервичным ключом

Для первого шага я сосредоточился на поиске ближайших меток времени к маркам в первой таблице из второй таблицы.


РЕЙТИНГ Результаты

Причуды, которые я обнаружил ...

... по пути во время отладки

Очень странно писать JOINлогику, FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)которая, как указал @byrdzeye в комментарии (которая с тех пор исчезла), является формой перекрестного соединения. Обратите внимание , что замена LEFT OUTER JOINна INNER JOINв приведенном выше коде появляется не сделать никакого влияния на количество или идентичности строк , возвращаемых. Я также не могу оставить предложение ON или сказать ON (1=1). Простое использование запятой для соединения (а не INNERили LEFT OUTER JOIN) приводит к Count(select * from A) * Count(select * from B)строкам, возвращаемым в этом запросе, а не к одной строке на таблицу A, как явным образом JOINвозвращает (A <> B OR A = B) . Это явно не подходит. FIRSTне представляется возможным использовать данный тип составного первичного ключа.

Второй JOINстиль, хотя и, возможно, более разборчивый, страдает от того, что он медленнее. Это может быть связано JOINс тем, что для более крупной таблицы требуются дополнительные два внутренних s, а также два CROSS JOINs, найденные в обоих вариантах.

В сторону: замена IIFпредложения на MIN/, по- MAXвидимому, возвращает такое же количество записей.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
работает для MAXметки времени «Before» ( ), но не работает непосредственно для метки «After» ( MIN) следующим образом:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
потому что минимум всегда равен 0 для FALSEусловия. Этот 0 меньше, чем любая постэпоха DOUBLE( DateTimeполе является подмножеством в Access и в которое этот расчет преобразует поле). В IIFи MIN/ MAXметоды Заместители , предложенные для значения работы AfterXTStamp , потому что деление на ноль ( FALSE) генерирует нулевые значения, которые агрегатные функции MIN и MAX пропустить через.

Следующие шаги

Продолжая это, я хотел бы найти временные метки во второй таблице, которые непосредственно граничат с временными метками в первой таблице, и выполнить линейную интерполяцию значений данных из второй таблицы на основе расстояния до этих точек (т. Е. Если временная метка из первая таблица находится на 25% пути между «до» и «после», мне бы хотелось, чтобы 25% рассчитанного значения приходилось из данных значений второй таблицы, связанных с точкой «после», и 75% от «до» ). Используя пересмотренный тип соединения, как часть внутренней кишки, и после предложенных ответов ниже я получаю ...

    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;

... который возвращает 152928 записей, соответствующих (по крайней мере приблизительно) окончательному числу ожидаемых записей. Время выполнения, вероятно, 5-10 минут на моем i7-4790, 16 ГБ ОЗУ, нет SSD, система Win 8.1 Pro.


Ссылка 1: MS Access может обрабатывать миллисекундные значения времени - действительно и сопровождающий исходный файл [08080011.txt]

МПа
источник

Ответы:

10

Сначала я должен поблагодарить вас за вашу смелость сделать что-то подобное с базой данных Access, что, по моему опыту, очень сложно сделать что-нибудь похожее на SQL. В любом случае, на обзор.


Первое присоединение

Для IIFвыбора полей может быть полезно использование оператора Switch . Иногда кажется, что, особенно в случае с SQL, дело обстоит так, что a SWITCH(более широко известный как CASEтипичный SQL) довольно быстр, когда просто делает простые сравнения в теле a SELECT. Синтаксис в вашем случае будет почти идентичным, хотя переключатель можно расширить, чтобы охватить большую часть сравнений в одном поле. Что-то, чтобы рассмотреть.

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

Переключатель также может помочь читаемости, в больших выражениях. В контексте:

  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

Что касается самого объединения, я думаю, что (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)это примерно так же хорошо, как вы собираетесь получить, учитывая то, что вы пытаетесь сделать. Это не так быстро, но я бы не ожидал, что так будет.


Второе соединение

Вы сказали, что это медленнее. Это также менее читаемо с точки зрения кода. Учитывая одинаково удовлетворительные наборы результатов между 1 и 2, я бы сказал, пойти на 1. По крайней мере, очевидно, что вы пытаетесь сделать таким образом. Подзапросы часто бывают не очень быстрыми (хотя часто и неизбежными), особенно в этом случае вы добавляете дополнительное объединение в каждом, что, безусловно, должно усложнить план выполнения.

Я заметил, что вы использовали старый синтаксис соединения ANSI-89. Лучше избегать этого, производительность будет такой же или лучше с более современным синтаксисом объединения, и они менее двусмысленны или легче читаются, труднее делать ошибки.

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)

Называть вещи

Я думаю, то, как ваши вещи названы, в лучшем случае бесполезно, а в худшем - загадочно. A, B, A1, B1и т.д. как псевдонимы таблиц, я думаю, может быть лучше. Кроме того, я думаю, что названия полей не очень хороши, но я понимаю, что вы не можете контролировать это. Я просто быстро процитирую Код без кода на тему именования вещей и оставлю это на этом ...

«Инвектив!» - ответила жрица. «Глагол свои ругательные существительные!»


Запрос «Следующие шаги»

Я не мог понять, как это было написано, мне пришлось перенести его в текстовый редактор и внести некоторые изменения в стиль, чтобы сделать его более читабельным. Я знаю, что SQL-редактор Access не очень сложен, поэтому я обычно пишу свои запросы в таком хорошем редакторе, как Notepad ++ или Sublime Text. Некоторые из стилистических изменений, которые я применил, чтобы сделать его более читабельным:

  • Отступ 4 пробела вместо 2 пробелов
  • Пробелы вокруг математических операторов и операторов сравнения
  • Более естественное размещение фигурных скобок и отступов (я использовал фигурные скобки в стиле Java, но также мог бы быть в стиле C, по вашему желанию)

Как оказалось, это действительно очень сложный запрос. Чтобы понять это, я должен начать с самого внутреннего запроса, вашего IDнабора данных, который, как я понимаю, совпадает с вашим первым соединением. Он возвращает идентификаторы и временные метки устройств, где временные метки до / после являются самыми близкими, в подмножестве интересующих вас устройств. Поэтому вместо того, чтобы IDне вызывать его ClosestTimestampID.

Ваше Detобъединение используется только один раз:

введите описание изображения здесь

В остальное время он объединяет только те значения, которые у вас уже есть ClosestTimestampID. Поэтому вместо этого мы должны просто сделать это:

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

Возможно, это не будет большим приростом производительности, но все, что мы можем сделать, чтобы помочь плохому оптимизатору Jet DB, поможет!


Я не могу избавиться от ощущения, что вычисления / алгоритм BeforeWeightи AfterWeightкоторые вы используете для интерполяции, могут быть сделаны лучше, но, к сожалению, я не очень хорош с ними.

Одним из предложений, позволяющих избежать сбоев (хотя это не идеально в зависимости от вашего приложения), было бы разбить вложенные подзапросы на собственные таблицы и обновлять их при необходимости. Я не уверен, как часто вам нужно обновлять исходные данные, но если это случается не так часто, вы можете подумать о написании некоторого кода VBA, чтобы запланировать обновление таблиц и производных таблиц, и просто оставить свой внешний запрос для извлечения. из этих таблиц вместо исходного источника. Просто мысль, как я сказал, не идеальная, но учитывая инструмент, у вас может не быть выбора.


Все вместе:

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
источник
5
  • Добавлены дополнительные атрибуты и условия фильтра.
  • Любая форма перекрестного соединения исключается путем использования минимальных и максимальных вложенных запросов. Это самый большой прирост производительности.
  • Минимальные и максимальные значения фланга, возвращаемые внутренним наиболее вложенным запросом, являются значениями первичного ключа (сканирования), которые используются для извлечения дополнительных атрибутов фланга (широта и долгота) с использованием поиска для окончательных вычислений (доступ имеет эквивалентный эквивалент).
  • Атрибуты первичных таблиц извлекаются и фильтруются в самом внутреннем запросе и должны способствовать повышению производительности.
  • Нет необходимости форматировать (StrDateIso8601Msec) значение времени для сортировки. Использование значения datetime из таблицы эквивалентно.

Планы выполнения SQL Server (потому что Access не может показать это)
Без окончательного заказа, потому что это дорого:
Clustered Index Scan [ReceiverDetails]. [PK_ReceiverDetails] Стоимость 16%
Clustered Index Seek [FirstTable]. [PK_FirstTable] Стоимость 19%
Clustered Index Seek [SecondTable]. [PK_SecondTable] Стоимость 16%
кластерного индекса Seek [SecondTable]. [PK_SecondTable] Стоимость 16%
кластерного индекса Seek [SecondTable]. [PK_SecondTable] [TL2] Стоимость 16%
поиска кластерного индекса [SecondTable]. [PK_SecondTable]. [TL1] Стоимость 16%

В последнем порядке:
Сортировка Стоимость 36%
Сканирование кластерного индекса [ReceiverDetails]. [PK_ReceiverDetails] Стоимость 10%
Поиск кластерного индекса [FirstTable]. [PK_FirstTable] Стоимость 12%
Поиск кластерного индекса [SecondTable]. [PK_SecondTable] Стоимость 10% Поиск
кластерного индекса [SecondTable]. [PK_SecondTable] Стоимость 10%
Поиск кластерного индекса [SecondTable]. [PK_SecondTable] [TL2] Стоимость 10%
Поиск кластерного индекса [SecondTable]. [ PK_SecondTable] [TL1] Стоимость 10%

Код:

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;

Тестирование производительности моего запроса по запросу, содержащему перекрестное соединение.

FirstTable был загружен 13 записями, а SecondTable - 1 000 000.
Планы выполнения моего запроса не сильно изменились по сравнению с тем, что было опубликовано.
Планы выполнения для перекрестного объединения:
стоимость вложенных циклов 81% при использовании INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
вложенных циклов снижается до 75% при использовании сканирования CROSS JOIN SecondTable AS B' or ',SecondTable AS B
совокупного
индекса 8% [SecondTable] [UK_ID] [B] 6% буфера
таблицы 5%
Несколько других поисков кластерных индексов и поисков индекса (аналогично моему запросу, опубликованному) со стоимостью 0%.

Время выполнения составляет 0,007 и 8-9 секунд для моего запроса и CROSS JOIN.
Сравнение затрат 0% и 100%.

Я загрузил FirstTable с 50 000 записей и одной записью в ReceiverDetails для условия соединения и выполнил свой запрос.
50,013 вернулось между 0,9 и 1,0 секунды.

Я запустил второй запрос с перекрестным соединением и позволил ему работать в течение примерно 20 минут, прежде чем я убил его.
Если запрос на перекрестное соединение фильтруется так, чтобы он возвращал только исходные 13, время выполнения снова составляет 8-9 секунд.
Размещение условия фильтра было наибольший выбор, внешний выбор наиболее и оба. Нет разницы.

Существует разница между этими двумя условиями соединения в пользу CROSS JOIN, первое использует предикат, а CROSS JOIN - нет:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B

byrdzeye
источник
Запуск части ClosestTimestampID в моей системе мгновенно возвращает 152928 записей при инкапсуляции в Count (*). Мой MSAccess блокировался при возврате реальных записей на этом этапе - возможно, временные таблицы из другого метода занимали все виды памяти. Я думаю, что последний запрос, который я получу по вашей методологии, будет очень похож на тот, который я сейчас использую. Что я полагаю, это хорошо :)
mpag
1
В своем первоначальном комментарии вы указали, что сразу же вернули некоторые записи. Это важно в отношении того, как работает доступ, разрабатывая стратегию доступа и устанавливая ожидания для времени выполнения. Это называется отложенным исполнением. (Сбой при нажатии последней записи.) Какое количество записей записей верхнего предела ожидается в конечном запросе?
byrdzeye
Я считаю 152928
mpag
Какова природа значений DateTime в обеих таблицах при добавлении новых записей. Это текущие метки времени или недавние значения или они абсолютно случайны?
byrdzeye
В первой таблице есть штампы DateTime, выпущенные в 2013 году или более поздние. Во второй таблице есть метки DateTime, которые находятся в течение нескольких месяцев в середине 2015 года. Если будут добавлены новые значения, они, вероятно, будут (но не обязательно будут) после существующего набора. Новые значения могут быть добавлены в любую таблицу.
mpag
2

Добавление второго ответа, не лучше первого, но без изменения какого-либо из представленных требований, есть несколько способов превратить Access в представление и выглядеть быстро. Немного «материализуйте» осложнения, используя «триггеры». Таблицы доступа не имеют триггеров, поэтому перехватывать и вводить грубые процессы.

--*** 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
источник