İlk kelimeler
Aşağıdaki bölümleri (ve de dahil olmak üzere) güvenli bir şekilde yok sayabilirsiniz. JOINs: Sadece bir kod kırıntısı almak istiyorsanız Başlatılıyor . Arka plan ve sonuçlar sadece bağlam olarak işlev görür. Kodun başlangıçta nasıl göründüğünü görmek istiyorsanız, lütfen 2015-10-06'dan önceki düzenleme geçmişine bakın.
Amaç
Sonunda, vericinin ( X
veya Xmit
) vericisinin enterpolasyonlu GPS koordinatlarını , tablodaki SecondTable
gözlemi doğrudan çevreleyen tablodaki mevcut GPS verilerinin DateTime damgalarına dayanarak hesaplamak istiyorum FirstTable
.
Nihai hedefi başarmak için acil hedefim, bu yan zaman noktalarına ulaşmak için en iyi nasıl katılmaya FirstTable
karar SecondTable
vermektir. Daha sonra bu bilgiyi, eşkenar dörtgen bir koordinat sistemi boyunca doğrusal uyumu varsayarak (bu dünyanın bir küre olduğu umrumda değil diyen süslü kelimeler) varsayarak, orta GPS koordinatlarını hesaplayabilirim.
Sorular
- En yakın öncesi ve sonrası zaman damgalarını oluşturmanın daha etkili bir yolu var mı?
- Sadece "sonra" yı alıp "sonra" sadece "sonra" ile ilgili olanı alarak "önce" düzeltildi.
(A<>B OR A=B)
Yapısını içermeyen daha sezgisel bir yol var mı ?- Byrdzeye temel alternatifleri sağladı, ancak "gerçek dünya" deneyimim aynı performans gösteren 4 stratejisine de uymadı. Ancak alternatif birleşme stillerini ele aldığı için ona tam kredi.
- Aklınıza gelebilecek diğer düşünceler, püf noktaları ve tavsiyeler.
- Her iki Thusfar byrdzeye ve Phrancis bu konuda oldukça yararlı olmuştur. Ben bulundu Phrancis' tavsiyesi Burada ona kenar vereceğiz böylece mükemmel, düzenlendiği ve kritik bir aşamada yardıma sağlandı.
3. soru ile ilgili olarak alabileceğim herhangi bir ek yardım için yine de minnettar olurum.
Tablo Tanımları
Yarı görsel temsil
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
AlıcıAyrıntılar tablosu
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 tablosu
Field (and primary key)
XmitID | TEXT(25) -- primary, unique, no null, simple
SQL keman ...
... tablonun tanımları ve koduyla oynayabilmeniz için bu soru MSAccess içindir, ancak Phrancis'ın işaret ettiği gibi, Access için SQL keman tarzı yoktur. Öyleyse, burada tablo tanımlarımı ve Phrancis'in cevabını temel alan kodumu görmek için buraya gitmelisiniz :
http://sqlfiddle.com/#!6/e9942/4 (external link)
JOINs: Başlangıç
Şimdiki "iç bağırsaklarım" JOIN Stratejisi
Öncelikle, sütun düzenine sahip bir FirstTable_rekeyed ve (RecTStamp, ReceivID, XmitID)
tümü indekslenmiş / sıralanmış birleşik birincil anahtar oluşturun ASC
. Ayrıca her bir sütunda ayrı ayrı indeksler oluşturdum. Öyleyse öyle doldur.
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;
Yukarıdaki sorgu, yeni tabloyu 153006 kayıtla doldurur ve 10 saniye içinde döndürür.
TOP 1 alt sorgu yöntemi kullanıldığında, bu yöntem bir "SELECT Count (*) FROM (...)" seçeneğine kaydırıldığında aşağıdakiler bir veya iki saniye içinde tamamlanır.
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))
Önceki "iç bağırsaklar" JOIN sorgusu
İlk (oruç ... ama yeterince iyi değil)
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)))
İkincisi (daha yavaş)
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;
Arka fon
Bir DateTime
damga, bir Verici Kimliği ve bir Kayıt Cihazı Kimliği'ne dayanan birleşik birincil anahtarla birlikte 1 milyonun altındaki girişlerin telemetri tablosu (A olarak adlandırılmış) var . Kontrolüm dışındaki koşullar nedeniyle, SQL dilim Microsoft Access'teki standart Jet DB'dir (kullanıcılar 2007 ve sonraki sürümleri kullanacaktır). Bu girişlerin yalnızca yaklaşık 200.000'i Verici Kimliği nedeniyle sorgu ile ilgilidir.
Tek bir DateTime
birincil anahtarla yaklaşık 50.000 giriş içeren ikinci bir telemetri tablosu (takma B) vardır
İlk adım için, ikinci tablodaki ilk tablodaki damgalara en yakın zaman damgasını bulmaya odaklandım.
JOIN Sonuçları
Keşfettiğim Tuhaflıklar ...
... hata ayıklama sırasında
@Byrdzeye'nin (o zamandan beri ortadan kayboldu) bir yorumda işaret ettiği gibi bir birleştirme şekli olduğu JOIN
mantığını yazmak gerçekten garip geliyor . İkame olduğu Not için görüntülenene Yukarıdaki kod iade çizgilerin miktarı veya kimliği hiçbir etki yaratmak için. Ayrıca ON cümleciklerini bırakamıyorum ya da söyleyemiyorum . Yalnızca ( veya yerine ) katılmak için virgül kullanmak , (A <> B VEYA A = B) açıkça döndürüldüğünde , tablo A başına yalnızca bir satır yerine, bu sorguda döndürülen satırlarla sonuçlanır . Bu açıkça uygun değil. Bileşik birincil anahtar türü verilen kullanmak için uygun görünmüyor.FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)
LEFT OUTER JOIN
INNER JOIN
ON (1=1)
INNER
LEFT OUTER
JOIN
Count(select * from A) * Count(select * from B)
JOIN
FIRST
İkinci JOIN
stil, tartışmasız daha okunaklı olmasına rağmen, daha yavaştır. Bunun sebebi , her iki seçenekte JOIN
olduğu gibi, daha büyük olan masaya karşı iki iç kısmın daha gerekli olmasıdır CROSS JOIN
.
Bir kenara: Aynı sayıdaki girişi döndürmek için IIF
yan tümce MIN
/ / ile değiştirilir MAX
.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
"Önce" ( MAX
) zaman damgası için çalışır , ancak MIN
aşağıdaki gibi "Sonra" ( ) için doğrudan çalışmaz :
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
çünkü FALSE
koşul için minimum her zaman 0'dır . Bu 0, herhangi bir çağ sonrasından daha azdır DOUBLE
(bu DateTime
, Access'te bir alanın alt kümesidir ve bu hesaplamanın alanı dönüştürdüğü). IIF
Ve MIN
/ MAX
sıfıra bölme için AfterXTStamp değeri çalışmaları için önerilen yöntemler dönüşümlü ( FALSE
) toplama işlevlerini MIN ve MAX atlamak boş değerler üretir.
Sonraki adımlar
Bunu daha da ileri götürmek gerekirse, ikinci tabloda, ilk tablodaki zaman damgalarını doğrudan çevreleyen zaman damgalarını bulmak ve ikinci tablodan bu noktalara olan zaman mesafesine bağlı olarak veri değerlerinin doğrusal bir enterpolasyonunu yapmak istiyorum (yani zaman damgası eğer ilk tablo "önce" ve "sonra" arasındaki yolun% 25’idir, hesaplanan değerin% 25’inin "sonra" noktasıyla ilişkilendirilmiş 2. tablo değeri verilerinden gelmesini ve "önce" nin% 75’inden gelmesini istiyorum ). Gözden geçirilmiş birleşim tipini iç bağırsakların bir parçası olarak kullanmak ve aşağıda önerilen cevaplardan sonra ...
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 kayıt döndürür, beklenen kayıt sayısına (en azından yaklaşık olarak) uyar. Çalışma süresi muhtemelen i7-4790, 16GB RAM, SSD, Win 8.1 Pro sistemimde 5-10 dakika.
Referans 1: MS Access Milisaniye Zaman Değerlerini Kullanabilir - Gerçekten ve beraberindeki kaynak dosya [08080011.txt]