Первые слова
Вы можете спокойно игнорировать разделы ниже (и в том числе) JOINs: начиная с Off, если вы просто хотите взломать код. Фон и результаты просто служат в качестве контекста. Пожалуйста, посмотрите историю изменений до 2015-10-06, если вы хотите увидеть, как изначально выглядел код.
Задача
В конечном итоге я хочу рассчитать интерполированные GPS-координаты для передатчика ( X
или Xmit
) на основе меток DateTime доступных GPS-данных в таблице, SecondTable
которые располагаются непосредственно по направлению к наблюдению в таблице FirstTable
.
Моя ближайшая цель для достижения конечной цели, чтобы выяснить , как лучше всего присоединиться FirstTable
к SecondTable
получить эти фланговые моменты времени. Позже я смогу использовать эту информацию, чтобы вычислить промежуточные координаты GPS, предполагая линейную подгонку вдоль равносторонней системы координат (причудливые слова, чтобы сказать, что мне все равно, что Земля - это сфера в этом масштабе).
Вопросов
- Есть ли более эффективный способ генерировать самые близкие метки времени до и после?
- Я сам исправил это, просто схватив «после», а затем получив «до» только в том случае, если он связан с «после».
- Есть ли более интуитивный способ, который не включает в себя
(A<>B OR A=B)
структуру. - Любые другие мысли, хитрости и советы, которые вы можете иметь.
- 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 JOIN
s, найденные в обоих вариантах.
В сторону: замена 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]