Перші слова
Ви можете сміливо ігнорувати наведені нижче розділи (та включаючи) ПРИЄДНАЙТЕСЬ: Починаючи з, якщо ви просто хочете зламати код. Фон і результати просто служать в якості контексту. Перегляньте історію редагування до 2015-10-06, якщо ви хочете побачити, як виглядав код спочатку.
Об'єктивна
Зрештою, я хочу обчислити інтерпольовані координати GPS для передавача ( X
або Xmit
) на основі маркерів DateTime наявних даних GPS у таблиці, SecondTable
які безпосередньо узгоджують спостереження в таблиці FirstTable
.
Моя найближча мета для досягнення кінцевої мети, щоб з'ясувати , як найкраще приєднатися FirstTable
до SecondTable
отримати ці флангові моменти часу. Пізніше я можу скористатись цією інформацією. Я можу обчислити проміжні GPS координати, припускаючи, що лінійне розміщення уздовж прямокутної системи координат (химерні слова, щоб сказати, мені не байдуже, що Земля є сферою в такому масштабі).
Запитання
- Чи існує більш ефективний спосіб генерувати найближчі позначки до і після?
- Виправлено самим, просто схопивши "після", а потім отримавши "раніше" лише так, як це стосується "після".
- Чи є інтуїтивніший спосіб, який не передбачає
(A<>B OR A=B)
структури.- Byrdzeye запропонував основні альтернативи, однак мій досвід "реального світу" не співпадав із усіма 4 його стратегіями приєднання, що виконують те саме. Але повна заслуга йому за звернення до альтернативних стилів приєднання.
- Будь-які інші думки, підказки та поради, які ви можете мати.
- 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
стиль, хоча і, можливо, більш розбірливий, страждає від повільності. Це може бути тому, що додаткові два внутрішніх JOIN
s потрібні проти більшої таблиці, а також два CROSS JOIN
s, знайдені в обох варіантах.
Убік: Заміна 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]