Pierwsze słowa
Możesz bezpiecznie zignorować poniższe sekcje (i włącznie) DOŁĄCZENIA: Rozpoczęcie, jeśli chcesz po prostu złamać kod. Tło i wyniki po prostu służyć jako kontekst. Proszę spojrzeć na historię edycji przed 06.10.2015, jeśli chcesz zobaczyć, jak początkowo wyglądał kod.
Cel
Ostatecznie chcę obliczyć interpolowane współrzędne GPS dla nadajnika ( X
lub Xmit
) na podstawie znaczników DateTime dostępnych danych GPS w tabeli, SecondTable
które bezpośrednio towarzyszą obserwacji w tabeli FirstTable
.
Moja najbliższa cel do osiągnięcia ostatecznego celu jest dowiedzieć się, jak najlepiej dołączyć FirstTable
do SecondTable
dostać te flankujące punktach czasowych. Później mogę wykorzystać te informacje, aby obliczyć pośrednie współrzędne GPS przy założeniu liniowego dopasowania wzdłuż układu współrzędnych prostokątnych (wymyślne słowa, które mówią, że nie obchodzi mnie, że Ziemia jest kulą w tej skali).
pytania
- Czy istnieje bardziej skuteczny sposób generowania najbliższych znaczników czasu przed i po?
- Naprawione przeze mnie, po prostu chwytając „po”, a następnie zdobywając „przed” tylko w związku z „po”.
- Czy istnieje bardziej intuicyjny sposób, który nie wymaga
(A<>B OR A=B)
struktury. - Wszelkie inne myśli, sztuczki i porady, które możesz mieć.
- Thusfar zarówno byrdzeye i Phrancis były bardzo pomocne w tym względzie. Przekonałem się, że rada Phrancis została doskonale rozłożona i zapewniła pomoc na krytycznym etapie, więc dam mu przewagę.
Będę wdzięczny za wszelką dodatkową pomoc, jaką mogę otrzymać w związku z pytaniem 3. Punkty kulminacyjne odzwierciedlają, kto moim zdaniem najbardziej pomógł w danym pytaniu.
Definicje tabel
Pół-wizualna reprezentacja
Pierwsza tabela
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
Skrzypce SQL ...
... abyś mógł bawić się definicjami tabel i kodem To pytanie dotyczy MSAccess, ale jak zauważył Phrancis, nie ma stylu skrzypowania SQL dla Access. Powinieneś więc móc przejść tutaj, aby zobaczyć moje definicje tabel i kod oparty na odpowiedzi Phrancis :
http://sqlfiddle.com/#!6/e9942/4 (link zewnętrzny)
DOŁĄCZENIA: Zaczynam
Moja obecna strategia „wewnętrznej odwagi” DOŁĄCZ
Najpierw utwórz FirstTable_rekeyed z kolejnością kolumn i złożonym kluczem podstawowym, (RecTStamp, ReceivID, XmitID)
wszystkie indeksowane / sortowane ASC
. Utworzyłem również indeksy dla każdej kolumny osobno. Następnie wypełnij go w ten sposób.
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;
Powyższe zapytanie wypełnia nową tabelę 153006 rekordami i zwraca w ciągu około 10 sekund.
Poniższa czynność kończy się w ciągu sekundy lub dwóch, gdy cała metoda jest opakowana w „WYBIERZ Licznik (*) OD (...)”, gdy używana jest metoda podzapytania 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))
Poprzednie zapytanie DOŁĄCZ „wewnętrzne wnętrzności”
Po pierwsze (szybki ... ale niewystarczająco dobry)
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)))
Drugi (wolniejszy)
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;
tło
Mam tabelę telemetryczną (alias jako A) zawierającą prawie 1 milion wpisów ze złożonym kluczem podstawowym opartym na DateTime
pieczęci, identyfikatorze nadajnika i identyfikatorze urządzenia rejestrującego. Ze względu na okoliczności, na które nie mam wpływu, moim językiem SQL jest standardowy Jet DB w Microsoft Access (użytkownicy będą używać wersji 2007 i późniejszych). Tylko około 200 000 tych wpisów jest istotnych dla zapytania z powodu ID nadajnika.
Istnieje druga tabela telemetryczna (alias B), która obejmuje około 50 000 pozycji z jednym DateTime
kluczem podstawowym
W pierwszym kroku skupiłem się na znalezieniu najbliższych znaczników czasowych znaczków w pierwszym stole z drugiego stołu.
DOŁĄCZ wyniki
Dziwactwa, które odkryłem ...
... po drodze podczas debugowania
To naprawdę dziwne pisać JOIN
logikę, FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)
która, jak zauważył @byrdzeye w komentarzu (który od tego czasu zniknął), jest formą połączenia krzyżowego. Należy zauważyć, że zastąpienie LEFT OUTER JOIN
przez INNER JOIN
w powyższym kodzie pojawia się nie mieć wpływu na ilość lub tożsamości linii zwróconych. Nie mogę też pominąć klauzuli ON ani powiedzieć ON (1=1)
. Po prostu użycie przecinka do połączenia (zamiast INNER
lub LEFT OUTER
JOIN
) powoduje, że Count(select * from A) * Count(select * from B)
wiersze są zwracane w tym zapytaniu, a nie tylko jeden wiersz na tabelę A, gdy JOIN
zwraca jawnie (A <> B LUB A = B) . To oczywiście nie jest odpowiednie. FIRST
nie wydaje się być dostępny do użycia, biorąc pod uwagę złożony klucz podstawowy.
Drugi JOIN
styl, choć prawdopodobnie bardziej czytelny, cierpi z powodu spowolnienia. Może to być spowodowane tym, że dodatkowe dwa wewnętrzne JOIN
są wymagane w stosunku do większego stołu, a także dwa CROSS JOIN
w obu opcjach.
Poza tym: Zastąpienie IIF
klauzuli słowem MIN
/ MAX
wydaje się zwracać tę samą liczbę pozycji.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
działa dla MAX
znacznika czasu „Przed” ( ), ale nie działa bezpośrednio dla „Po” ( MIN
) w następujący sposób:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
ponieważ minimum wynosi zawsze 0 dla FALSE
warunku. Ta wartość 0 jest mniejsza niż jakakolwiek epoka po epoce DOUBLE
(której DateTime
pole jest podzbiorem w programie Access i na którą to obliczenie przekształca pole). Metody IIF
i MIN
/ MAX
Alternatywy zaproponowane dla wartości AfterXTStamp działają, ponieważ dzielenie przez zero ( FALSE
) generuje wartości zerowe, które funkcje agregujące MIN i MAX pomijają.
Następne kroki
Idąc dalej, chciałbym znaleźć znaczniki czasu w drugiej tabeli, które bezpośrednio flankują znaczniki czasu w pierwszej tabeli i wykonać liniową interpolację wartości danych z drugiej tabeli na podstawie odległości czasu od tych punktów (tj. Jeśli znacznik czasu od pierwsza tabela to 25% drogi między „przed” a „po”, chciałbym, aby 25% obliczonej wartości pochodziło z danych wartości 2. tabeli związanych z punktem „po” i 75% z „przed” ). Używając zmienionego typu łączenia jako części wnętrzności i po sugerowanych odpowiedziach poniżej tworzę ...
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;
... która zwraca 152928 rekordów, co odpowiada (przynajmniej w przybliżeniu) ostatecznej liczbie oczekiwanych rekordów. Czas pracy to prawdopodobnie 5-10 minut na moim i7-4790, 16 GB pamięci RAM, bez dysku SSD i systemu Win 8.1 Pro.
Odniesienie 1: MS Access może obsłużyć milisekundowe wartości czasu - naprawdę i towarzyszący mu plik źródłowy [08080011.txt]