Доступ (Jet) SQL: Марки DateTime в TableB, посилаючись на кожну марку DateTime в TableA


21

Перші слова

Ви можете сміливо ігнорувати наведені нижче розділи (та включаючи) ПРИЄДНАЙТЕСЬ: Починаючи з, якщо ви просто хочете зламати код. Фон і результати просто служать в якості контексту. Перегляньте історію редагування до 2015-10-06, якщо ви хочете побачити, як виглядав код спочатку.


Об'єктивна

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

Моя найближча мета для досягнення кінцевої мети, щоб з'ясувати , як найкраще приєднатися FirstTableдо SecondTableотримати ці флангові моменти часу. Пізніше я можу скористатись цією інформацією. Я можу обчислити проміжні GPS координати, припускаючи, що лінійне розміщення уздовж прямокутної системи координат (химерні слова, щоб сказати, мені не байдуже, що Земля є сферою в такому масштабі).


Запитання

  1. Чи існує більш ефективний спосіб генерувати найближчі позначки до і після?
    • Виправлено самим, просто схопивши "після", а потім отримавши "раніше" лише так, як це стосується "після".
  2. Чи є інтуїтивніший спосіб, який не передбачає (A<>B OR A=B)структури.
    • Byrdzeye запропонував основні альтернативи, однак мій досвід "реального світу" не співпадав із усіма 4 його стратегіями приєднання, що виконують те саме. Але повна заслуга йому за звернення до альтернативних стилів приєднання.
  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, але, як зазначив Франкіс, немає стилю скриптів 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 <> B АБО = B) JOINповертається. Це явно не підходить. FIRSTне здається доступним для використання з урахуванням складеного типу первинного ключа.

Другий JOINстиль, хоча і, можливо, більш розбірливий, страждає від повільності. Це може бути тому, що додаткові два внутрішніх JOINs потрібні проти більшої таблиці, а також два CROSS JOINs, знайдені в обох варіантах.

Убік: Заміна IIFпункту на MIN/ MAXвидається, що повертає однакову кількість записів.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
працює для MAXчасової позначки "До" ( ), але не працює безпосередньо для "Після" ( 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 записів, що відповідають (принаймні приблизно) кінцевій кількості очікуваних записів. На моїй i7-4790, 16 Гб оперативної пам’яті, без SSD, Win 8.1 Pro, можливо, час роботи буде 5-10 хвилин.


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

Відповіді:


10

Спершу я повинен похвалити вас за вашу сміливість зробити щось подібне з базою даних DB, що, з мого досвіду, дуже важко зробити що-небудь схоже на SQL. У будь-якому випадку, на огляд.


Спочатку приєднуйтесь

Ваші IIFвибір поля може скористатись вимогою використання оператора Switch . Здається, іноді буває так, особливо з речами SQL, що a SWITCH(більш відомий як CASEу типовому SQL) досить швидкий при простому порівнянні в тілі 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, але також міг бути у С-стилі на ваш вибір)

Отже, як виявляється, це справді дуже складний запит. Щоб мати сенс у цьому, я повинен почати з найпотаємнішого запиту, вашого 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;

5
  • Додано додаткові атрибути та умови фільтра.
  • Будь-яка форма перехресного з'єднання усувається за допомогою використання мінімальних та максимальних вкладених запитів. Це найбільший приріст продуктивності.
  • Значення min та max flank, повернені внутрішнім самим вкладеним запитом, - це значення первинного ключа (сканування), які використовуються для отримання додаткових атрибутів флангу (lat та lon), використовуючи пошук остаточних обчислень (доступ має еквівалент застосунку).
  • Атрибути первинних таблиць витягуються та фільтруються за найпотужнішим запитом і повинні сприяти виконанню.
  • Немає необхідності форматувати (StrDateIso8601Msec) значення часу для сортування. Використання значення дати з таблиці є рівнозначним.

Плани виконання SQL Server (оскільки Access не може цього показати)
Без остаточного замовлення через його дорогий:
Кластерне сканування індексів [ReceiverDetails]. [PK_ReceiverDetails] Вартість 16%
Кластерний індекс Шукайте [FirstTable]. [PK_FirstTable] Вартість 19%
Кластерний індекс Шукайте [SecondTable]. [PK_SecondTable] Вартість 16%
кластерного індексу Seek [SecondTable]. [PK_SecondTable] Cost 16%
Clustered Index Seek [SecondTable]. [PK_SecondTable] [TL2] Cost 16%
Clustered Index See [SecondTable]. [PK_See [TL1] Вартість 16%

За остаточним замовленням:
Сортувати вартість 36%
Кластерне сканування індексів [ReceiverDetails]. [PK_ReceiverDetails] Вартість 10%
Кластерний індекс Шукайте [FirstTable]. [PK_FirstTable] Вартість 12%
Пошук кластерних індексів [SecondTable]. [PK_SecondTable] Вартість 10%
Кластерний індекс Seek [SecondTable]. [PK_SecondTable] Cost 10%
Clustered Index Seek [SecondTable]. [PK_SecondTable] [TL2] Cost 10%
Clustered Index See [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,07 та 8-9 секунд для мого запиту та КРОССЬКОГО ПРИЄДНАННЯ.
Порівняння витрат 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


Запуск частини 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;
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.