Access (Jet) SQL: TableA'daki her DateTime damgasını çevreleyen TableB'deki DateTime damgaları


21

İ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 ( Xveya Xmit) vericisinin enterpolasyonlu GPS koordinatlarını , tablodaki SecondTablegö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 FirstTablekarar SecondTablevermektir. 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

  1. 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.
  2. (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.
  3. 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 DateTimedamga, 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 DateTimebirincil 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 JOINmantığı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 JOININNER JOINON (1=1)INNERLEFT OUTER JOINCount(select * from A) * Count(select * from B)JOINFIRST

İkinci JOINstil, tartışmasız daha okunaklı olmasına rağmen, daha yavaştır. Bunun sebebi , her iki seçenekte JOINolduğ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 IIFyan tümce MIN/ / ile değiştirilir MAX.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
"Önce" ( MAX) zaman damgası için çalışır , ancak MINaşağıdaki gibi "Sonra" ( ) için doğrudan çalışmaz :
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
çünkü FALSEkoş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üğü). IIFVe MIN/ MAXsı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]

Yanıtlar:


10

Önce, deneyimimden SQL benzeri bir şey yapmanın çok zor olduğu bir Access DB ile böyle bir şey yapma cesaretiniz için size iltifat etmeliyim . Neyse, inceleme için.


İlk katılım

Kişisel IIFalan seçimleri bir kullanarak yarar olabilir Anahtarı deyimi yerine. Bazen, özellikle de SQL'de olduğu gibi, bir SWITCH( CASEbasitçe SQL'de olduğu gibi bilinir ) oldukça hızlı olduğu için basit bir karşılaştırma yapılıyor SELECT. Durumunuzdaki sözdizimi neredeyse aynı olacaktır, ancak bir alanda geniş bir karşılaştırma yığınını kapsayacak şekilde bir anahtar genişletilebilir. Dikkate alınması gereken bir şey.

  SWITCH (
    expr1, val1,
    expr2, val2,
    val3        -- default value or "else"
  )

Bir anahtar ayrıca daha büyük açıklamalarda okunabilirliğe de yardımcı olabilir. Bağlamda:

  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

Katılmaya gelince (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp), yapmaya çalıştığınız şey göz önüne alındığında, alacağınız kadar iyi olduğunu düşünüyorum . O kadar hızlı değil, ama ben de olmasını beklemiyorum.


İkinci katılım

Bunun daha yavaş olduğunu söyledin. Ayrıca kod açısından daha az okunabilir. 1 ile 2 arasında eşit derecede tatmin edici sonuç verildiğinde, 1'e gidelim derdim. En azından bu şekilde ne yapmaya çalıştığınız belli. Alt sorgular genellikle çok hızlı değildir (çoğu zaman kaçınılmaz olsa da), özellikle bu durumda, her birine fazladan bir birleşim atıyorsunuz, bu da yürütme planını kesinlikle zorlaştırıyor.

Bir açıklama, eski ANSI-89 birleştirme sözdizimini kullandığınızı gördüm. Bundan kaçınmak en iyisidir, performans daha modern birleştirme sözdizimi ile aynı ya da daha iyi olacaktır ve hata yapmak daha zor, daha az belirsiz ya da daha kolay, hata yapmak daha zordur.

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)

Bir şeyleri adlandırmak

Bence eşyalarınızın isimlendirme şekli en iyi ihtimalle yararsızdır ve en kötüsü de kriptiktir. A, B, A1, B1vb tablo takma adları olarak daha iyi olacağını düşünüyorum. Ayrıca, alan adlarının çok iyi olmadığını düşünüyorum, ancak bunun üzerinde kontrol sahibi olamayacağınızı fark ediyorum. Şeyleri adlandırma konusunda Kodsuz Kod'u çabucak alıntılayacağım ve bu konuda bırakacağım ...

“Invective!” Diye cevapladı rahibe. “Patlayıcı isimlerinizi fiil!”


"Sonraki adımlar" sorgusu

Nasıl yazıldığını pek anlamadım, bir metin editörüne götürmek ve daha okunaklı hale getirmek için bazı stil değişiklikleri yapmak zorunda kaldım. Access'in SQL editörünün zorlu olduğunu biliyorum, bu yüzden genellikle sorgularımı Notepad ++ veya Sublime Text gibi iyi bir editöre yazıyorum. Daha okunaklı hale getirmek için uyguladığım bazı stilistik değişiklikler:

  • 2 boşluk yerine 4 boşluk girinti
  • Matematiksel ve uzayla ilgili operatörler
  • Parantezlerin ve girintilerin daha doğal yerleştirilmesi (Java stili diş telleri ile gittim, ancak tercihinize göre C stili de olabilir)

Görünüşe göre, bu gerçekten çok karmaşık bir sorgu. Bunu IDanlamak için, en içteki sorgudan başlamalıyım, anladığım veri setiniz, İlk Katılmanızla aynıdır. İlgilendiğiniz aygıtların alt kümesinde, önceki / sonraki zaman damgalarının en yakın olduğu aygıtların kimliklerini ve zaman damgalarını döndürür. Bu IDnedenle neden aramıyorsunuz ClosestTimestampID?

Sizin Detsadece bir kez kullanılır katılmak:

görüntü tanımını buraya girin

Zamanın geri kalanı, sadece zaten sahip olduğunuz değerlere katılır ClosestTimestampID. Öyleyse bunun yerine sadece bunu yapabilmeliyiz:

    ) 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>)

Belki de büyük bir performans kazancı olmayabilir, ancak zayıf Jet DB iyileştiricisinin yardımcı olması için yapabileceğimiz bir şey var!


Ben hesaplamalar / algoritma bu duyguyu tutamadığım BeforeWeightve AfterWeightsen interpolate için hangi iyi yapılabilir, ama maalesef bu çok iyi değilim.

Kilitlenmekten kaçınmak için bir öneri (uygulamanıza bağlı olarak ideal olmasa da) iç içe geçmiş sorgularınızı kendi tablolarına ayırmak ve gerektiğinde bunları güncellemektir. Kaynak verilerinizin ne sıklıkta yenilenmesi gerektiğinden emin değilim, ancak çok sık değilse, tabloların ve türetilmiş tabloların bir güncellemesini programlamak için bazı VBA kodları yazmayı düşünebilir ve Orijinal kaynak yerine bu tablolardan. Sadece bir düşünce, ideal olmadığını söylediğim gibi ama aleti verdiğinde seçme şansın olmayabilir.


Her şey birlikte:

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
  • Ek özellikler ve filtre koşulları eklendi.
  • Herhangi bir çapraz birleşme şekli, min ve max iç içe geçmiş sorgular kullanılarak elimine edilir. Bu en büyük performans kazancı.
  • En iç içe geçmiş iç sorgu tarafından döndürülen min ve maksimum kanat değerleri, son hesaplamalar için bir arama kullanarak ek kanat niteliklerini (lat ve lon) almak için kullanılan birincil anahtar değerlerdir (erişim eşdeğerdir).
  • Birincil tablolar öznitelikleri en içteki sorguda alınır ve filtrelenir ve performansa yardımcı olmalıdır.
  • Sıralama için zaman değerini formatlamaya gerek yoktur (StrDateIso8601Msec). Tablodan datetime değerini kullanmak eşdeğerdir.

SQL Server Yürütme Planları (çünkü Access bunu gösteremez)
Son sırasına göre pahalı çünkü:
Kümelenmiş Dizin Taraması [AlıcıDetaylar]. [PK_ReceiverDetails] Maliyet% 16
Kümelenmiş Dizin [FirstTable]. [PK_FirstTable] Maliyet 19%
Kümelenmiş Dizin [SecondTable]. [PK_SecondTable] Maliyet 16%
Kümelenmiş Dizin Arayın [SecondTable]. [PK_SecondTable] Maliyet 16%
Kümelenmiş Dizin Arayın [SecondTable]. [PK_SecondTable] [TL2] Maliyet 16%
Kümelenmiş Dizin Arayın [SecondTable]. [TL1] Maliyet 16%

Son siparişle:
Sıralama Maliyeti 36%
Kümelenmiş Endeks Taraması [AlıcıDetayları]. [PK_ReceiverDetails] Maliyet 10%
Kümelenmiş Endeksi Arayın [FirstTable]. [PK_FirstTable] Maliyet 12%
Kümelenmiş Dizin Arama [SecondTable]. [PK_SecondTable] Maliyet% 10
Kümelenmiş Dizin Arama [SecondTable]. [PK_SecondTable] Maliyet% 10
Kümelenmiş Dizin Arama [SecondTable]. [PK_SecondTable] [TL2] Maliyet 10%
Kümelenmiş Dizin Arama [SecondTable] [. PK_SecondTable] [TL1] Maliyet 10%

Kod:

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;

Çapraz birleştirmeyi içeren sorguya karşı sorguma karşı performans testi.

FirstTable 13 kayıtla ve SecondTable ile 1.000.000 yüklendi.
Sorgum için yürütme planları, kaydedilenden çok farklı değildi.
Çapraz birleştirme için yürütme planları:
Yuvalanmış Döngüler Yuvalanmış Döngüler kullanılarak gerçekleştirilen% 81 Maliyet Akış Toplamı % 8 Dizin Taraması INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
kullanılırsa% 75'e düşer [SecondTable] [UK_ID] [B]% 6 Tablo Biriktirme% 5% Diğer Kümelenmiş Dizin Arama ve Dizin Aramaları (Gönderdiğim sorguya benzer) Maliyeti% 0.CROSS JOIN SecondTable AS B' or ',SecondTable AS B




Sorgu ve CROSS JOIN için yürütme süresi .007 ve 8-9 saniyedir.
Maliyet karşılaştırması% 0 ve% 100.

FirstTable'ı 50.000 kayıt ve bir katılım koşulu için ReceiverDetails'e tek bir kayıtla yükledim ve sorgumu koştum.
50,013, 0,9 ile 1,0 saniye arasında geri döndü.

Haç birleşimi ile ikinci bir sorgu yaptım ve öldürmeden önce 20 dakika kadar çalışmasına izin verdim.
Çapraz birleştirme sorgusu yalnızca orijinal 13'ü döndürmek için filtrelenmişse, yürütme süresi yine 8-9 saniyedir.
Filtre koşulunun yerleştirilmesi en seçkin iç, en seçkin dış ve her ikisiydi. Fark yok.

Bu iki birleşme koşulu arasında CROSS JOIN lehine bir fark var, ilk önce bir belirti kullanıyor, CROSS JOIN yapmıyor:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B


Sistemimde ClosestTimestampID bölümünü çalıştırmak, Kont (*) içine alındığında anında 152928 kayıt döndürür. MSAccess'im bu aşamadaki gerçek kayıtları döndürürken kilitlendi - belki diğer yöntemlerden gelen temp tabloları her tür belleği saklıyordu. Sanırım metodolojinizden ürettiğim son sorgu şu anda kullandıklarımla çok benzer olacaktır. Diyelim ki iyi bir şey :)
mpag 12:15

1
Orijinal yorumunuzda derhal bazı kayıtları geri aldığınızı belirttiniz. Bu, erişimin nasıl çalıştığı, bir Access stratejisinin ortaya çıkması ve yürütme süresi için beklentilerin belirlenmesi açısından önemlidir. Buna ertelenmiş icra denir. (Son rekora bastığınızda çarptı.) Son sorguda olması beklenen üst limit geri dönüş rekoru sayısı nedir?
byrdzeye

152928
mpag

DateTime değerlerinin niteliği nedir, her iki tabloda da yeni kayıtlar eklenir. Geçerli zaman damgaları mı yoksa son değerler mi yoksa tamamen rasgele mi?
byrdzeye

İlk tablo 2013 veya daha yeni olan DateTime pullarına sahiptir. İkinci tabloda, 2015'in ortalarında birkaç ay içinde olan DateTime pulları var. Yeni değerler eklenirse, büyük olasılıkla mevcut setten sonra (olacakları garanti edilmeyeceklerdir) olacaklardır. Her iki tabloya da yeni değerler eklenebilir.
mpag

2

Ikinci bir cevap eklemek, birinciden daha iyi değil, ama sunulan gereklilikleri değiştirmeden, gönderime erişimi yenmenin ve çabuk görünmesinin birkaç yolu vardır. 'Tetikleyicileri' kullanarak komplikasyonları bir miktar etkililikte biraz 'belirginleştirin'. Erişim tabloları tetikleyicileri içermez, bu nedenle engelleme işlemlerini durdurur ve enjekte eder.

--*** 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;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.