Access (Jet) SQL: Cap DateTime di TableB mengapit setiap cap DateTime di TableA

21

Kata-kata pertama

Anda dapat dengan aman mengabaikan bagian di bawah ini (dan termasuk) GABUNG: Memulai jika Anda hanya ingin mengambil sedikit kode. The latar belakang dan hasil hanya berfungsi sebagai konteks. Silakan lihat histori edit sebelum 2015-10-06 jika Anda ingin melihat seperti apa kode itu pada awalnya.


Objektif

Pada akhirnya saya ingin menghitung koordinat GPS yang diinterpolasi untuk pemancar ( Xatau Xmit) berdasarkan perangko DateTime dari data GPS yang tersedia dalam tabel SecondTableyang secara langsung mengapit pengamatan dalam tabel FirstTable.

Tujuan jangka pendek saya untuk mencapai tujuan utamanya adalah untuk mencari tahu cara terbaik untuk bergabung FirstTableke SecondTableuntuk mendapatkan orang-mengapit titik waktu. Kemudian saya dapat menggunakan informasi itu saya dapat menghitung koordinat GPS menengah dengan asumsi pemasangan linier sepanjang sistem koordinat equirectangular (kata-kata mewah untuk mengatakan saya tidak peduli bahwa Bumi adalah bola pada skala ini).


Pertanyaan

  1. Apakah ada cara yang lebih efisien untuk menghasilkan prangko waktu sebelum dan sesudah?
    • Diperbaiki sendiri dengan hanya meraih "setelah," dan kemudian mendapatkan "sebelum" hanya karena terkait dengan "setelah".
  2. Apakah ada cara yang lebih intuitif yang tidak melibatkan (A<>B OR A=B)struktur.
    • Byrdzeye memberikan alternatif-alternatif dasar, namun pengalaman "dunia nyata" saya tidak sejalan dengan keempat strategi gabungannya yang melakukan hal yang sama. Tapi kredit penuh kepadanya untuk mengatasi gaya bergabung alternatif.
  3. Pikiran, trik, dan saran lain yang mungkin Anda miliki.
    • Demikian pula, baik byrdzeye dan Phrancis telah cukup membantu dalam hal ini. Saya menemukan bahwa saran Phrancis sangat baik diletakkan dan memberikan bantuan pada tahap kritis, jadi saya akan memberinya keunggulan di sini.

Saya masih akan menghargai setiap bantuan tambahan yang dapat saya terima sehubungan dengan pertanyaan 3. Bulletpoints mencerminkan siapa yang saya percaya paling membantu saya pada pertanyaan individu.


Definisi Tabel

Representasi semi-visual

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

Tabel kedua

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

Tabel 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

Tabel ValidXmitters

Field (and primary key)
  XmitID    | TEXT(25) -- primary, unique, no null, simple

Biola SQL ...

... sehingga Anda bisa bermain dengan definisi dan kode tabel. Pertanyaan ini untuk MSAccess, tetapi seperti yang ditunjukkan Phrancis, tidak ada gaya biola SQL untuk Access. Jadi, Anda dapat pergi ke sini untuk melihat definisi dan kode tabel saya berdasarkan jawaban Phrancis :
http://sqlfiddle.com/#!6/e9942/4 (tautan eksternal)


BERGABUNG: Memulai

Strategi GINJAL "batin" saya saat ini

Pertama buat FirstTable_rekeyed dengan urutan kolom dan kunci primer majemuk (RecTStamp, ReceivID, XmitID)semua diindeks / diurutkan ASC. Saya juga membuat indeks pada setiap kolom secara individual. Kemudian isi seperti itu.

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;

Kueri di atas mengisi tabel baru dengan 153006 catatan dan kembali dalam hitungan 10 detik atau lebih.

Berikut ini selesai dalam satu atau dua detik ketika seluruh metode ini dibungkus dalam "SELECT Count (*) FROM (...)" ketika metode subquery TOP 1 digunakan

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

Permintaan JOIN "nyali batin" sebelumnya

Pertama (fastish ... tapi tidak cukup bagus)

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

Kedua (lebih lambat)

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; 

Latar Belakang

Saya memiliki tabel telemetri (alias A) dengan jumlah hanya di bawah 1 juta entri dengan kunci primer majemuk berdasarkan DateTimecap, ID Pemancar, dan ID Perangkat Perekaman. Karena keadaan di luar kendali saya, bahasa SQL saya adalah Jet DB standar di Microsoft Access (pengguna akan menggunakan versi 2007 dan yang lebih baru). Hanya sekitar 200.000 entri ini yang relevan dengan kueri karena ID Pemancar.

Ada tabel telemetri kedua (alias B) yang melibatkan sekitar 50.000 entri dengan DateTimekunci primer tunggal

Untuk langkah pertama, saya fokus pada menemukan cap waktu terdekat ke perangko di tabel pertama dari tabel kedua.


GABUNGKAN Hasil

Keanehan yang Saya Temukan ...

... selama debugging

Rasanya sangat aneh untuk menulis JOINlogika seperti FROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)yang ditunjukkan oleh @byrdzeye dalam komentar (yang telah menghilang) adalah bentuk cross-join. Perhatikan bahwa mengganti LEFT OUTER JOINuntuk INNER JOINdalam kode di atas muncul untuk membuat dampak dalam kuantitas atau identitas dari garis kembali. Saya juga sepertinya tidak bisa meninggalkan klausa ON atau mengatakan ON (1=1). Hanya menggunakan koma untuk bergabung (bukan INNERatau LEFT OUTER JOIN) menghasilkan Count(select * from A) * Count(select * from B)baris yang dikembalikan dalam kueri ini, bukan hanya satu baris per tabel A, sebagai JOINpengembalian eksplisit (A <> B ATAU A = B) . Ini jelas tidak cocok. FIRSTtampaknya tidak tersedia untuk digunakan mengingat jenis kunci utama majemuk.

Gaya Kedua JOIN, meskipun bisa dibilang lebih terbaca, menderita lebih lambat. Ini mungkin karena dua inner JOINtambahan diperlukan terhadap tabel yang lebih besar serta keduanya CROSS JOINditemukan di kedua opsi.

Selain: Mengganti IIFklausa dengan MIN/ MAXmuncul untuk mengembalikan jumlah entri yang sama.
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
berfungsi untuk MAXcap waktu "Sebelum" ( ), tetapi tidak bekerja langsung untuk "Setelah" ( MIN) sebagai berikut:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
karena minimum selalu 0 untuk FALSEkondisi. Angka 0 ini lebih kecil dari pada setiap periode pasca DOUBLE( DateTimebidang mana yang merupakan bagian dari Access dan perhitungan ini mengubah bidang menjadi). Metode IIFdan MIN/ / MAXAlternatif yang diusulkan untuk nilai AfterXTStamp berfungsi karena pembagian dengan nol ( FALSE) menghasilkan nilai nol, yang fungsi agregatnya MIN dan MAX lewati.

Langkah selanjutnya

Mengambil ini lebih lanjut, saya ingin menemukan cap waktu di tabel kedua yang secara langsung mengapit cap waktu di tabel pertama dan melakukan interpolasi linier dari nilai data dari tabel kedua berdasarkan jarak waktu ke titik-titik tersebut (yaitu jika cap waktu dari tabel pertama adalah 25% dari jalan antara "sebelum" dan "setelah", saya ingin 25% dari nilai yang dihitung berasal dari data nilai tabel 2 yang terkait dengan titik "setelah" dan 75% dari "sebelum" ). Menggunakan tipe gabungan yang direvisi sebagai bagian dari nyali batin, dan setelah jawaban yang disarankan di bawah ini saya ...

    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;

... yang mengembalikan 152928 catatan, menyesuaikan (setidaknya kira-kira) ke jumlah akhir dari catatan yang diharapkan. Run time mungkin 5-10 menit pada i7-4790, RAM 16GB, tanpa SSD, sistem Win 8.1 Pro saya.


Referensi 1: MS Access Dapat Menangani Nilai Waktu Milidetik - Benar-benar dan menyertai file sumber [08080011.txt]

mpag
sumber

Jawaban:

10

Saya pertama-tama harus memuji Anda atas keberanian Anda untuk melakukan sesuatu seperti ini dengan Access DB, yang dari pengalaman saya sangat sulit untuk melakukan apa pun seperti SQL. Bagaimanapun, ke ulasan.


Gabung dulu

IIFPilihan bidang Anda mungkin mendapat manfaat dari menggunakan pernyataan Switch sebagai gantinya. Tampaknya kadang-kadang terjadi, terutama dengan hal-hal SQL, bahwa SWITCH(lebih dikenal sebagai CASESQL khas) cukup cepat ketika hanya membuat perbandingan sederhana dalam tubuh a SELECT. Sintaksis dalam kasus Anda akan hampir identik, meskipun sebuah saklar dapat diperluas untuk mencakup sejumlah besar perbandingan dalam satu bidang. Sesuatu untuk dipertimbangkan.

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

Switch juga dapat membantu keterbacaan, dalam pernyataan yang lebih besar. Dalam konteks:

  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

Sedangkan untuk bergabung sendiri, saya pikir (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)adalah tentang sebaik yang akan Anda dapatkan, mengingat apa yang Anda coba lakukan. Ini tidak secepat itu, tapi saya juga tidak mengharapkannya.


Gabung kedua

Anda mengatakan ini lebih lambat. Ini juga kurang dapat dibaca dari sudut pandang kode. Diberikan set hasil yang sama memuaskan antara 1 dan 2, saya akan mengatakan untuk 1. Setidaknya jelas apa yang Anda coba lakukan dengan cara itu. Subquery sering tidak terlalu cepat (meskipun sering tidak dapat dihindari) terutama dalam hal ini Anda melemparkan bergabung ekstra di masing-masing, yang tentunya harus mempersulit rencana eksekusi.

Satu komentar, saya melihat bahwa Anda menggunakan sintaks ANSI-89 tua. Yang terbaik untuk menghindari itu, kinerjanya akan sama atau lebih baik dengan sintaks bergabung yang lebih modern, dan mereka kurang ambigu atau lebih mudah dibaca, lebih sulit untuk membuat kesalahan.

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)

Memberi nama benda

Saya pikir cara nama barang Anda tidak membantu terbaik, dan samar terburuk. A, B, A1, B1dll sebagai alias tabel saya pikir bisa lebih baik. Juga, saya pikir nama-nama bidang tidak terlalu baik, tetapi saya menyadari Anda mungkin tidak memiliki kendali atas ini. Saya hanya akan dengan cepat mengutip Kode Tanpa Kode pada topik penamaan hal-hal, dan biarkan saja ...

"Curang!" Jawab pendeta itu. "Kata kerja kata benda sumpah serapahmu!"


Permintaan "Langkah selanjutnya"

Saya tidak bisa memahami bagaimana itu ditulis, saya harus membawanya ke editor teks dan melakukan beberapa perubahan gaya agar lebih mudah dibaca. Saya tahu Access 'SQL editor' tidak bisa digunakan, jadi saya biasanya menulis pertanyaan saya di editor yang bagus seperti Notepad ++ atau Sublime Text. Beberapa perubahan gaya yang saya terapkan agar lebih mudah dibaca:

  • 4 spasi indent bukan 2 spasi
  • Spasi di sekitar operator matematika dan perbandingan
  • Penempatan kawat gigi dan lekukan yang lebih alami (saya menggunakan kawat gigi gaya Jawa, tetapi juga bisa bergaya C, sesuai keinginan Anda)

Jadi ternyata, ini adalah permintaan yang sangat rumit. Untuk memahaminya, saya harus mulai dari kueri paling dalam, IDkumpulan data Anda , yang saya pahami sama dengan Gabung Pertama Anda. Ini mengembalikan ID dan stempel waktu perangkat di mana cap waktu sebelum / sesudah adalah yang terdekat, dalam subset perangkat yang Anda minati. Jadi, alih-alih IDmengapa tidak menyebutnya ClosestTimestampID.

DetGabung Anda hanya digunakan sekali:

masukkan deskripsi gambar di sini

Sisa waktu, itu hanya menggabungkan nilai-nilai yang sudah Anda miliki ClosestTimestampID. Jadi alih-alih, kita harus bisa melakukan ini:

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

Mungkin bukan pencapaian kinerja yang sangat besar, tetapi apa pun yang dapat kita lakukan untuk membantu pengoptimal Jet DB yang buruk akan membantu!


Saya tidak dapat menghilangkan perasaan bahwa perhitungan / algoritma untuk BeforeWeightdan AfterWeightyang Anda gunakan untuk interpolasi dapat dilakukan dengan lebih baik, tapi sayangnya saya tidak terlalu baik dengan itu.

Satu saran untuk menghindari crash (meskipun tidak ideal tergantung pada aplikasi Anda) adalah untuk membagi subqueries bersarang Anda ke dalam tabel mereka sendiri dan memperbaruinya saat diperlukan. Saya tidak yakin seberapa sering Anda membutuhkan data sumber Anda untuk di-refresh, tetapi jika tidak terlalu sering Anda mungkin berpikir untuk menulis beberapa kode VBA untuk menjadwalkan pembaruan tabel dan tabel turunan, dan biarkan kueri terluar Anda untuk menarik dari tabel itu bukan sumber aslinya. Hanya sebuah pemikiran, seperti yang saya katakan tidak ideal tetapi diberikan alat Anda mungkin tidak punya pilihan.


Semuanya bersama:

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;
Phrancis
sumber
5
  • Menambahkan atribut dan kondisi filter tambahan.
  • Segala bentuk gabungan silang dihilangkan dengan menggunakan kueri minimum dan maksimal. Ini adalah perolehan kinerja terbesar.
  • Nilai sisi minimum dan maks yang dikembalikan oleh kueri paling bersarang bagian dalam adalah nilai kunci primer (pemindaian) yang digunakan untuk mengambil atribut sisi tambahan (lat dan lon) menggunakan pencarian untuk perhitungan akhir (akses memang memiliki setara yang berlaku).
  • Atribut tabel primer diambil dan difilter dalam kueri paling dalam dan akan membantu kinerja.
  • Tidak perlu memformat (StrDateIso8601Msec) nilai waktu untuk menyortir. Menggunakan nilai datetime dari tabel adalah sama.

Paket Eksekusi SQL Server (karena Access tidak dapat menampilkan ini)
Tanpa urutan akhir oleh karena mahal:
Pemindaian Clustered Index [ReceiverDetails]. [PK_ReceiverDetails] Biaya 16%
Indeks Clustered Mencari [FirstTable]. [PK_FirstTable] Biaya 19%
Indeks Clustered Carilah [SecondTable]. [PK_SecondTable] Biaya 16%
Indeks Clustered Carilah [SecondTable]. [PK_SecondTable] Biaya 16%
Indeks Clustered Carilah [SecondTable]. [PK_SecondTable] [TL2] Biaya 16%
Indeks Clustered Carilah [SecondTable]. [PK_SecTableTable [TL1] Biaya 16%

Dengan urutan terakhir berdasarkan:
Biaya Urut 36%
Pemindaian Indeks Berkelompok [ReceiverDetails]. [PK_ReceiverDetails] Biaya 10%
Indeks Clustered Mencari [FirstTable]. [PK_FirstTable] Biaya 12%
Indeks Clustered Seek [SecondTable]. [PK_SecondTable] Biaya 10%
Indeks Clustered Seek [SecondTable]. [PK_SecondTable] Biaya 10%
Indeks Clustered Seek [SecondTable]. [PK_SecondTable] [TL2] Biaya 10%
Indeks Clustered Seek [SecondTable] [ PK_SecondTable] [TL1] Biaya 10%

Kode:

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;

Kinerja menguji kueri saya terhadap kueri yang mengandung gabungan silang.

FirstTable dimuat dengan 13 catatan dan SecondTable dengan 1.000.000.
Rencana pelaksanaan untuk permintaan saya tidak banyak berubah dari apa yang telah diposting.
Rencana eksekusi untuk sambungan silang:
Nested Loops Biaya 81% menggunakan INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
Nested Loops turun menjadi 75% jika menggunakan CROSS JOIN SecondTable AS B' or ',SecondTable AS B
Stream Aggregate 8%
Scan Indeks [SecondTable] [UK_ID] [B] 6%
Table Spool 5%
Beberapa Indeks Clustered lainnya Mencari dan Mencari Indeks (mirip dengan permintaan saya seperti yang diposting) dengan Biaya 0%.

Waktu pelaksanaan adalah 0,007 dan 8-9 detik untuk permintaan saya dan CROSS JOIN.
Perbandingan biaya 0% dan 100%.

Saya memuat FirstTable dengan 50.000 catatan dan satu catatan ke ReceiverDetails untuk kondisi bergabung dan menjalankan kueri saya.
50.013 dikembalikan antara 0,9 dan 1,0 detik.

Saya menjalankan kueri kedua dengan salib bergabung dan membiarkannya berjalan sekitar 20 menit sebelum saya membunuhnya.
Jika kueri gabung silang difilter untuk hanya mengembalikan yang asli 13, waktu eksekusi lagi, 8-9 detik.
Penempatan kondisi filter berada di paling dalam pilih, paling luar pilih dan keduanya. Tidak ada perbedaan.

Ada perbedaan antara kedua kondisi gabungan ini yang mendukung CROSS JOIN, yang pertama menggunakan predikat, CROSS JOIN tidak:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B

byrdzeye
sumber
Menjalankan bagian ClosestTimestampID pada sistem saya secara instan mengembalikan 152928 catatan ketika dienkapsulasi dalam Hitungan (*). MSAccess saya terkunci saat mengembalikan catatan aktual pada tahap itu - mungkin tabel temp dari metode lain memonopoli semua jenis memori. Saya pikir permintaan terakhir yang saya hasilkan dari metodologi Anda akan sangat mirip dengan apa yang saya gunakan saat ini. Yang saya kira adalah hal yang baik :)
mpag
1
Dalam komentar asli Anda, Anda menunjukkan bahwa Anda segera mengembalikan beberapa catatan. Ini penting dalam hal bagaimana akses bekerja, menghasilkan strategi Akses dan menetapkan harapan untuk waktu eksekusi. Ini disebut eksekusi yang ditangguhkan. (Ini mogok saat Anda mencapai catatan terakhir.) Berapakah jumlah catatan pengembalian batas atas yang diharapkan dalam kueri akhir?
byrdzeye
Saya percaya 152928
mpag
Apa sifat dari nilai DateTime di kedua tabel saat catatan baru ditambahkan. Apakah perangko waktu saat ini atau nilai terbaru atau sepenuhnya acak?
olehrdzeye
tabel pertama memiliki prangko DateTime yang 2013 atau lebih baru. Tabel kedua memiliki prangko DateTime yang berada dalam beberapa bulan di pertengahan 2015. Jika nilai baru ditambahkan, mereka kemungkinan akan (tetapi tidak dijamin) setelah set yang ada. Nilai baru dapat ditambahkan ke salah satu tabel.
mpag
2

Menambahkan jawaban kedua, tidak lebih baik dari yang pertama tetapi tanpa mengubah salah satu persyaratan yang disajikan, ada beberapa cara untuk mengalahkan Access menjadi pengajuan dan tampak tajam. 'Mewujudkan' komplikasi sedikit demi sedikit dengan menggunakan 'pemicu'. Tabel akses tidak memiliki pemicu sehingga memotong dan menyuntikkan proses kasar.

--*** 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;
byrdzeye
sumber