Access (Jet) SQL: DateTime stamps ใน TableB ขนาบแต่ละ DateTime stamp ใน TableA


21

คำแรก

คุณสามารถละเว้นส่วนด้านล่าง (และรวมถึง) อย่างปลอดภัยเข้าร่วม: เริ่มต้นปิดถ้าคุณเพียงแค่ต้องการที่จะถอดรหัสรหัส พื้นหลังและผลเพียงทำหน้าที่เป็นบริบท โปรดดูประวัติการแก้ไขก่อน 2015-10-06 หากคุณต้องการดูว่ารหัสดูเหมือนเป็นครั้งแรก


วัตถุประสงค์

ในท้ายที่สุดผมต้องการที่จะคำนวณพิกัด GPS หยันสำหรับเครื่องส่งสัญญาณ ( XหรือXmit) ตามแสตมป์วันที่และเวลาของข้อมูลจีพีเอสที่มีอยู่ในตารางที่ด้านข้างโดยตรงสังเกตในตารางSecondTableFirstTable

เป้าหมายทันทีของฉันในการบรรลุเป้าหมายสูงสุดคือการหาวิธีเข้าร่วมFirstTableให้ดีที่สุดSecondTableเพื่อรับคะแนนเวลาขนาบข้างนั้น ต่อมาฉันสามารถใช้ข้อมูลนั้นได้ฉันสามารถคำนวณพิกัด GPS ระดับกลางโดยสมมติว่าการปรับตำแหน่งเชิงเส้นตามระบบพิกัดแบบคู่ขนาน (คำที่น่าประหลาดใจที่จะบอกว่าฉันไม่สนใจว่าโลกเป็นทรงกลมในระดับนี้)


คำถาม

  1. มีวิธีที่มีประสิทธิภาพมากกว่าในการสร้างการประทับเวลาก่อนและหลังที่ใกล้เคียงที่สุดหรือไม่
    • แก้ไขด้วยตัวเองโดยเพียงแค่คว้า "หลัง" แล้วรับ "ก่อน" เฉพาะเมื่อมันเกี่ยวข้องกับ "หลัง"
  2. มีวิธีที่ง่ายกว่าที่ไม่เกี่ยวข้องกับ(A<>B OR A=B)โครงสร้างหรือไม่
    • Byrdzeyeให้ทางเลือกพื้นฐาน แต่ประสบการณ์ "โลกแห่งความจริง" ของฉันไม่ตรงกับกลยุทธ์การเข้าร่วมทั้ง 4 ของเขาที่ทำแบบเดียวกัน แต่ให้เครดิตเต็มที่กับเขาสำหรับการระบุสไตล์การเข้าร่วมอื่น
  3. ความคิดเทคนิคและคำแนะนำอื่น ๆ ที่คุณมี
    • ดังนั้นไกลทั้งbyrdzeyeและPhrancisค่อนข้างมีประโยชน์ในเรื่องนี้ ฉันพบว่าคำแนะนำของ Phrancisถูกจัดวางอย่างยอดเยี่ยมและให้ความช่วยเหลือในช่วงวิกฤติดังนั้นฉันจะให้เขาได้เปรียบที่นี่

ฉันยังคงขอขอบคุณความช่วยเหลือเพิ่มเติมใด ๆ ที่ฉันสามารถรับได้ในเรื่องข้อ 3 Bulletpoints แสดงว่าฉันเชื่อว่าใครช่วยฉันมากที่สุดในคำถามแต่ละข้อ


คำจำกัดความของตาราง

การเป็นตัวแทนกึ่งภาพ

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

รับตารางรายละเอียด

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 แต่ตามที่ Phrancis ชี้ให้เห็นไม่มีสไตล์ซอ SQL สำหรับการเข้าถึง ดังนั้นคุณควรจะไปที่นี่เพื่อดูคำจำกัดความของตารางและรหัสของฉันตามคำตอบของวลี :
http://sqlfiddle.com/#!6/e9942/4 (ลิงก์ภายนอก)


เข้าร่วม: การเริ่มต้น

เข้าร่วมกลยุทธ์ "Inner guts" ปัจจุบันของฉัน

แรกสร้าง 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 (... )" เมื่อใช้วิธีการสืบค้นย่อย 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))

คำค้นหา "Inner guts" ก่อนหน้าเข้าร่วม

ครั้งแรก (Fastish ... แต่ไม่ดีพอ)

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; 

พื้นหลัง

ฉันมีตาราง telemetry (นามแฝงเป็น 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 (1=1)ฉันยังไม่สามารถดูเหมือนจะทิ้งประโยคเปิดหรือพูด เพียงใช้เครื่องหมายจุลภาคเพื่อเข้าร่วม (แทนที่จะเป็นINNERหรือLEFT OUTER JOIN) ผลลัพธ์ในCount(select * from A) * Count(select * from B)แถวที่ส่งคืนในแบบสอบถามนี้แทนที่จะเป็นเพียงหนึ่งบรรทัดต่อตาราง A เนื่องจากผลลัพธ์(A <> B OR A = B) JOINกลับมาอย่างชัดเจน เห็นได้ชัดว่าไม่เหมาะ FIRSTดูเหมือนจะไม่พร้อมใช้งานสำหรับประเภทคีย์หลักผสม

JOINรูปแบบที่สองแม้ว่าเนื้อหาที่ชัดเจนมากขึ้นทนทุกข์ทรมานจากการถูกช้าลง นี่อาจเป็นเพราะต้องการอีกสองJOINs ภายในกับตารางที่มีขนาดใหญ่กว่าและสองCROSS JOINs ที่พบในตัวเลือกทั้งสอง

นอกเหนือ: การแทนที่ส่วน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) สร้างค่า Null ซึ่งฟังก์ชันการรวม MIN และ MAX จะข้ามไป

ขั้นตอนถัดไป

การเพิ่มเติมนี้ฉันต้องการค้นหา timestamps ในตารางที่สองที่ขนาบข้าง timestamps ในตารางแรกโดยตรงและทำการแก้ไขเชิงเส้นตรงของค่าข้อมูลจากตารางที่สองตามระยะเวลาไปยังจุดเหล่านั้น (เช่นถ้า timestamp จาก ตารางแรกคือ 25% ของวิธีระหว่าง "ก่อน" และ "หลัง" ฉันต้องการ 25% ของค่าที่คำนวณมาจากข้อมูลค่าตารางที่ 2 ที่เกี่ยวข้องกับจุด "หลัง" และ 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, RAM 16GB, ไม่มี SSD, ระบบ Win 8.1 Pro ของฉัน


การอ้างอิง 1: MS Access สามารถจัดการกับค่าเวลามิลลิวินาที - ได้จริงและมาพร้อมกับไฟล์ต้นฉบับ [08080011.txt]

คำตอบ:


10

ก่อนอื่นฉันต้องชมเชยคุณเกี่ยวกับความกล้าหาญของคุณที่จะทำสิ่งนี้กับ Access DB ซึ่งจากประสบการณ์ของฉันยากมากที่จะทำสิ่งใดก็ตามที่คล้ายกับ SQL อย่างไรก็ตามเพื่อตรวจสอบ


เข้าร่วมครั้งแรก

การIIFเลือกฟิลด์ของคุณอาจได้รับประโยชน์จากการใช้คำสั่ง Switchแทน มันน่าจะเป็นบางครั้งกรณีนี้โดยเฉพาะกับสิ่งที่ SQL, ว่าSWITCH(ที่รู้จักกันทั่วไปว่าเป็นCASEใน SQL ทั่วไป) ค่อนข้างรวดเร็วเมื่อเพียงแค่การเปรียบเทียบง่าย ๆ SELECTในร่างกายของที่ ไวยากรณ์ในกรณีของคุณจะเหมือนกันเกือบถึงแม้ว่าสวิตช์สามารถขยายเพื่อครอบคลุมการเปรียบเทียบขนาดใหญ่ในหนึ่งฟิลด์ สิ่งที่ต้องพิจารณา

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

สวิตช์สามารถช่วยให้อ่านง่าย ในบริบท:

  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

สำหรับการเข้าร่วมนั้นฉันคิดว่า(A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)ดีพอ ๆ กับที่คุณจะได้รับจากสิ่งที่คุณพยายามทำ มันไม่เร็วขนาดนั้น แต่ฉันไม่คิดว่ามันจะเป็นเช่นนั้น


เข้าร่วมที่สอง

คุณพูดว่าช้ากว่านี้ นอกจากนี้ยังสามารถอ่านได้จากจุดยืนของโค้ด ให้ผลลัพธ์ที่น่าพอใจพอ ๆ กันระหว่าง 1 ถึง 2 ฉันจะบอกว่าไป 1 อย่างน้อยก็เห็นได้ชัดว่าคุณกำลังพยายามทำเช่นนั้น แบบสอบถามย่อยมักจะไม่เร็วมาก (แต่มักจะหลีกเลี่ยงไม่ได้) โดยเฉพาะอย่างยิ่งในกรณีนี้คุณกำลังเข้าร่วมเป็นพิเศษในแต่ละครั้งซึ่งจะต้องทำให้แผนการดำเนินการซับซ้อนขึ้นอย่างแน่นอน

ข้อสังเกตหนึ่งฉันเห็นว่าคุณใช้ไวยากรณ์การเข้าร่วม ANSI-89 เก่า เป็นการดีที่สุดที่จะหลีกเลี่ยงสิ่งนั้นการแสดงจะเหมือนหรือดีกว่าด้วยไวยากรณ์การเข้าร่วมที่ทันสมัยยิ่งขึ้นและจะไม่ชัดเจนหรือง่ายต่อการอ่านยากที่จะทำผิดพลาด

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)

การตั้งชื่อสิ่งต่าง ๆ

ฉันคิดว่าวิธีการตั้งชื่อของคุณนั้นไม่ดีเท่าที่ควรและเป็นความลับที่สุด A, B, A1, B1เป็นต้นชื่อแทนตารางฉันคิดว่าอาจจะดีกว่า นอกจากนี้ฉันคิดว่าชื่อฟิลด์ไม่ดีมาก แต่ฉันรู้ว่าคุณอาจไม่สามารถควบคุมสิ่งนี้ได้ ฉันจะอ้างอิงThe Codeless Codeอย่างรวดเร็วในหัวข้อการตั้งชื่อและปล่อยไว้ที่ ...

“ Invective!”ปุโรหิตตอบ “ คำกริยาคำนามที่เกินคำบรรยายของคุณ!”


แบบสอบถาม "ขั้นตอนถัดไป"

ฉันไม่สามารถเข้าใจได้อย่างชัดเจนว่ามันถูกเขียนขึ้นมาอย่างไรฉันต้องนำมันไปแก้ไขข้อความและเปลี่ยนแปลงสไตล์เพื่อให้อ่านได้ง่ายขึ้น ฉันรู้ว่าตัวแก้ไข SQL ของ Access นั้นเกินกว่า clunky ดังนั้นฉันมักจะเขียนคำสั่งของฉันในตัวแก้ไขที่ดีเช่น Notepad ++ หรือ Sublime Text การเปลี่ยนแปลงโวหารที่ฉันใช้เพื่อทำให้อ่านง่ายขึ้น:

  • เยื้อง 4 ช่องว่างแทน 2 ช่องว่าง
  • ช่องว่างรอบตัวดำเนินการทางคณิตศาสตร์และการเปรียบเทียบ
  • การจัดฟันและการเยื้องที่เป็นธรรมชาติมากขึ้น (ฉันไปกับเครื่องมือจัดฟันแบบ Java แต่อาจเป็นแบบ C ตามที่คุณต้องการ)

ดังนั้นจึงปรากฎว่านี่เป็นคำถามที่ซับซ้อนมากอย่างแน่นอน เพื่อให้เข้าใจถึงมันฉันต้องเริ่มต้นจากการสืบค้นด้านในสุดIDชุดข้อมูลของคุณซึ่งฉันเข้าใจเหมือนกับการเข้าร่วมครั้งแรกของคุณ ก็จะส่งกลับรหัสและ timestamps ของอุปกรณ์ที่ก่อน / หลังการประทับเวลาอยู่ที่ใกล้เคียงที่สุดภายในย่อยของอุปกรณ์ที่คุณมีความสนใจใน. ดังนั้นแทนที่จะทำไมไม่เรียกมันว่าIDClosestTimestampID

การDetเข้าร่วมของคุณใช้เพียงครั้งเดียว:

ป้อนคำอธิบายรูปภาพที่นี่

ClosestTimestampIDส่วนที่เหลือของเวลาก็เพียงร่วมค่าที่คุณมีอยู่แล้วจาก ดังนั้นเราควรทำสิ่งนี้แทน:

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

อาจไม่ใช่ประสิทธิภาพที่เพิ่มขึ้นมาก แต่สิ่งใดก็ตามที่เราสามารถทำได้เพื่อช่วยให้เครื่องมือเพิ่มประสิทธิภาพ Jet DB ที่ไม่ดีจะช่วยได้!


ฉันไม่สามารถสั่นคลอนความรู้สึกว่าการคำนวณ / อัลกอริทึมสำหรับBeforeWeightและAfterWeightที่คุณใช้ในการแก้ไขสามารถทำได้ดีกว่า แต่น่าเสียดายที่ฉันไม่ได้ดีกับสิ่งเหล่านั้น

หนึ่งข้อเสนอแนะเพื่อหลีกเลี่ยงการหยุดทำงาน (แม้ว่ามันจะไม่เหมาะขึ้นอยู่กับแอปพลิเคชั่นของคุณ) ก็คือการแบ่งย่อยข้อความค้นหาที่ซ้อนกันออกเป็นตารางของตนเองและอัปเดตข้อมูลเหล่านั้นเมื่อจำเป็น ฉันไม่แน่ใจว่าคุณต้องการให้มีการรีเฟรชแหล่งข้อมูลของคุณบ่อยเพียงใด แต่ถ้าไม่บ่อยเกินไปคุณอาจคิดว่าจะเขียนโค้ด VBA เพื่อกำหนดตารางการอัปเดตของตารางและตารางที่ได้รับมา จากตารางเหล่านั้นแทนแหล่งต้นฉบับ แค่ความคิดอย่างที่ฉันบอกว่าไม่เหมาะ แต่ให้เครื่องมือคุณอาจไม่มีทางเลือก


ทุกอย่างเข้าด้วยกัน:

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
  • เพิ่มคุณสมบัติเพิ่มเติมและเงื่อนไขการกรอง
  • รูปแบบใด ๆ ของการเข้าร่วมไขว้ถูกกำจัดโดยใช้การค้นหาขั้นต่ำและสูงสุดซ้อนกัน นี่คือการเพิ่มประสิทธิภาพที่ใหญ่ที่สุด
  • ค่าต่ำสุดและสูงสุดของสีข้างที่ส่งกลับโดยเคียวรีที่ซ้อนกันมากที่สุดภายในคือค่าคีย์หลัก (สแกน) ที่ใช้เพื่อดึงคุณลักษณะของปีกเพิ่มเติม (lat และ lon) โดยใช้การค้นหาการคำนวณขั้นสุดท้าย (การเข้าถึงมีการใช้เทียบเท่า
  • แอ็ตทริบิวต์ตารางหลักถูกดึงและกรองในเคียวรีด้านในสุดและควรช่วยประสิทธิภาพ
  • ไม่จำเป็นต้องจัดรูปแบบ (StrDateIso8601Msec) ค่าเวลาสำหรับการเรียงลำดับ การใช้ค่าวันที่และเวลาจากตารางนั้นเท่าเทียมกัน

SQL Server แผนดำเนินการ (เพราะ Access ไม่สามารถแสดงนี้)
โดยไม่มีคำสั่งถึงที่สุดโดยเพราะราคาแพง:
. คลัสเตอร์ดัชนีสแกน [ReceiverDetails] [PK_ReceiverDetails] ค่าใช้จ่าย 16%
คลัสเตอร์ดัชนีแสวงหา [FirstTable] [PK_FirstTable] ค่าใช้จ่าย 19%.
คลัสเตอร์ดัชนี แสวงหา [SecondTable] [PK_SecondTable] ค่าใช้จ่าย 16%
ดัชนีแบบกลุ่มแสวงหา [SecondTable] [PK_SecondTable] ค่าใช้จ่าย 16% แบบกลุ่ม
ดัชนีแสวงหา [SecondTable] [PK_SecondTable] ค่าใช้จ่าย 16% แบบกลุ่ม
[แสวงหาครั้งที่สอง] [PKTecondTable] [TL1] ค่าใช้จ่าย 16%

เมื่อสั่งสุดท้าย:
เรียงลำดับต้นทุน 36%
สแกนดัชนีแบบกลุ่ม [ผู้รับรายละเอียด] [PK_ReceiverDetails] ค่าใช้จ่าย 10%
ดัชนีแบบกลุ่มค้นหา [FirstTable] [PK_FirstTable] ต้นทุน 12%
ดัชนีแบบกลุ่มแสวงหา [SecondTable]. [PK_SecondTable] ค่าใช้จ่าย 10%
ดัชนีแบบกลุ่มแสวงหา [SecondTable]. [PK_SecondTable] ค่าใช้จ่าย 10%
ดัชนีแบบกลุ่มแสวงหา [SecondTable] [PK_SecondTable] [TL2]
ดัชนีแบบแสวงหา [SecondTable] PK_SecondTable] [TL1]

รหัสราคา 10% :

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;

ประสิทธิภาพการทดสอบแบบสอบถามของฉันกับแบบสอบถามที่มีการเข้าร่วม cross

FirstTable ถูกโหลดด้วย 13 บันทึกและ SecondTable ที่มี 1,000,000
แผนการดำเนินการสำหรับแบบสอบถามของฉันไม่ได้เปลี่ยนแปลงอะไรมากมายจากสิ่งที่โพสต์
แผนการดำเนินการสำหรับการเข้าร่วมไขว้:
ลูปซ้อนกันค่าใช้จ่าย 81% โดยใช้INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
ลูปซ้อนกันลดลงถึง 75% หากใช้การ สแกนCROSS JOIN SecondTable AS B' or ',SecondTable AS B
สตรีมรวมการ
สแกนดัชนี8% [ตารางที่สอง] [สหราชอาณาจักร] [B] 6%
ตาราง
ดัชนี5% (คล้ายกับข้อความค้นหาของฉันเมื่อโพสต์) ด้วยค่าใช้จ่าย 0%

เวลาดำเนินการคือ. 007 และ 8-9 วินาทีสำหรับการค้นหาของฉันและ CROSS JOIN
การเปรียบเทียบต้นทุน 0% และ 100%

ฉันโหลด FirstTable ด้วย 50,000 เรกคอร์ดและเร็กคอร์ดเดียวกับ ReceiverDetails สำหรับเงื่อนไขการเข้าร่วมและเรียกใช้คิวรีของฉัน
50,013 ส่งคืนระหว่าง 0.9 ถึง 1.0 วินาที

ฉันเรียกใช้แบบสอบถามที่สองด้วยการเข้าร่วมไขว้และอนุญาตให้เรียกใช้เป็นเวลาประมาณ 20 นาทีก่อนที่ฉันจะฆ่ามัน
หากตัวกรอง cross join ถูกกรองเพื่อส่งกลับเฉพาะ 13 ดั้งเดิมเวลาดำเนินการคืออีกครั้ง 8-9 วินาที
ตำแหน่งของเงื่อนไขตัวกรองอยู่ด้านในเลือกส่วนใหญ่ด้านนอกส่วนใหญ่เลือกและทั้งสอง ไม่แตกต่าง.

มีความแตกต่างระหว่างเงื่อนไขการเข้าร่วมทั้งสองนี้เนื่องจาก CROSS JOIN เป็นเงื่อนไขแรกที่ใช้คำกริยา CROSS JOIN ไม่ได้:
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B


การใช้งาน DestinationTimestampID ในระบบของฉันจะส่งกลับระเบียน 152928 ทันทีเมื่อใส่ใน Count (*) MSAccess ของฉันถูกล็อคเมื่อส่งคืนระเบียนจริงในขั้นตอนนั้น - อาจเป็นตารางชั่วคราวจากวิธีอื่นกำลังใช้หน่วยความจำทุกชนิด ฉันคิดว่าคำถามสุดท้ายที่ฉันทำจากวิธีการของคุณจะคล้ายกับสิ่งที่ฉันใช้อยู่ในปัจจุบัน ซึ่งผมคิดว่าเป็นสิ่งที่ดี :)
mpag

1
ในความคิดเห็นดั้งเดิมของคุณคุณระบุว่าคุณได้รับคืนระเบียนบางส่วนทันที สิ่งนี้มีความสำคัญเกี่ยวกับการทำงานของการเข้าถึงการกำหนดกลยุทธ์การเข้าถึงและการตั้งค่าความคาดหวังสำหรับเวลาดำเนินการ มันเรียกว่าการดำเนินการรอการตัดบัญชี (มันล้มเหลวเมื่อคุณกดบันทึกครั้งสุดท้าย) จำนวนระเบียนส่งคืนสูงสุดที่คาดไว้คืออะไรในแบบสอบถามสุดท้าย
byrdzeye

ฉันเชื่อว่า
152928

อะไรคือลักษณะของค่า DateTime ในทั้งสองตารางเมื่อมีการเพิ่มระเบียนใหม่ พวกเขาประทับเวลาปัจจุบันหรือค่าล่าสุดหรือสุ่มสมบูรณ์?
byrdzeye

ตารางแรกมีตราประทับ DateTime ที่เป็น 2013 หรือล่าสุด ตารางที่สองมีตราประทับ DateTime ที่อยู่ภายในไม่กี่เดือนในกลางปี ​​2558 หากมีการเพิ่มค่าใหม่พวกเขาอาจจะเป็น (แต่ไม่รับประกันว่าจะเป็น) หลังจากชุดที่มีอยู่ ค่าใหม่สามารถเพิ่มลงในตารางใดก็ได้
mpag

2

การเพิ่มคำตอบที่สองไม่ดีไปกว่าคำตอบแรก แต่ไม่เปลี่ยนแปลงข้อกำหนดใด ๆ ที่นำเสนอมีสองสามวิธีในการเอาชนะการเข้าสู่การส่งและปรากฏเร็ว 'ทำให้เป็นจริง' ความยุ่งเหยิงเล็กน้อยในเวลาหนึ่งโดยใช้ 'ทริกเกอร์' ตารางการเข้าถึงไม่มีทริกเกอร์ดังนั้นการสกัดกั้นและแทรกกระบวนการ crud

--*** 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;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.