Access(Jet)SQL:TableAの各DateTimeスタンプに隣接するTableBのDateTimeスタンプ


21

最初の言葉

あなたは安全に(を含む)以下のセクションを無視することができます結合します皮切りあなただけのコードの亀裂を取りたい場合。背景結果がちょうど文脈としての役割を果たす。最初にコードがどのように表示されたかを確認するには、2015年10月6日より前の編集履歴をご覧ください。


目的

最終的には、表の観測値に直接隣接する表の利用可能なGPSデータのDateTimeスタンプに基づいて、送信機(XまたはXmit)の補間GPS座標を計算SecondTableしますFirstTable

究極の目標を達成するための私の当面の目標は、これらの側面の時点を得るためにどのように参加FirstTableするのSecondTableが最善かを見つけることです。後で、その情報を使用して、正距円筒座標系に沿った線形近似を仮定して中間GPS座標を計算できます(このスケールでは、地球が球体であることを気にしないと言った派手な言葉)。


ご質問

  1. 最も近い前後のタイムスタンプを生成するより効率的な方法はありますか?
    • 「after」を取得し、「after」に関連する場合にのみ「before」を取得することで、自分で修正しました。
  2. (A<>B OR A=B)構造を含まない、より直感的な方法はありますか。
    • Byrdzeyeは基本的な選択肢を提供しましたが、私の「実世界」の経験は、同じことを実行する彼の4つの結合戦略すべてとは一致しませんでした。しかし、代替結合スタイルに対処したことに対する彼の完全な信用。
  3. あなたが持つかもしれない他の考え、トリックやアドバイス。
    • 両方Thusfar byrdzeyePhrancisはこの点で非常に役立っています。私は、ことがわかっPhrancis'アドバイスが、私はここに彼に端をあげるので良好、レイアウトおよび重要な段階での支援を提供しました。

質問3に関して私が受けることができる追加の助けをまだ感謝しています。 箇条書きは、個々の質問で私を最も助けたと思う人を反映しています。


テーブル定義

半視覚的表現

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

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

ValidXmittersテーブル

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

SQLフィドル...

...テーブルの定義とコードを試すことができるようにこの質問はMSAccess向けですが、Phrancisが指摘したように、AccessにはSQLフィドルスタイルはありません。だから、Phrancisの答えに基づいて私のテーブル定義とコードを見るためにここ行くことができるはずです:http : //sqlfiddle.com/#! 6 /e9942/4
(外部リンク)


JOINs:スタート

私の現在の「内臓」JOIN Strategy

最初に、列の順序で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秒程度で戻ります。

TOP 1サブクエリメソッドが使用されている場合、このメソッド全体が「SELECT Count(*)FROM(...)」でラップされると、次の処理は1〜2秒で完了します。

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

前の「内部ガッツ」JOINクエリ

最初(速い...しかし十分ではない)

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

2番目(遅い)

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; 

バックグラウンド

DateTimeスタンプ、トランスミッターID、および録音デバイスIDに基づく複合主キーを持つ100万件未満のエントリのテレメトリテーブル(エイリアスA)があります。私が制御できない状況のため、私のSQL言語はMicrosoft Accessの標準のJet DBです(ユーザーは2007以降のバージョンを使用します)。トランスミッタIDのため、これらのエントリのうち約200,000のみがクエリに関連しています。

1つのDateTime主キーを持つ約50,000のエントリを含む2番目のテレメトリテーブル(エイリアスB)があります

最初のステップでは、2番目のテーブルから最初のテーブルのスタンプに最も近いタイムスタンプを見つけることに焦点を当てました。


参加結果

私が発見した癖...

...デバッグ中

それは書くことが本当に奇妙な感じJOINとしてロジックFROM FirstTable as A INNER JOIN SecondTable as B ON (A.RecTStamp<>B.XTStamp OR A.RecTStamp=B.XTStamp)としてどの@byrdzeyeが(消失以来持っている)のコメントで指摘をクロス参加の形です。上記のコードをに置き換えLEFT OUTER JOININNER JOINも、返される行の量やアイデンティティには影響がないように見えることに注意してください。私もON句を省略することも言うこともできませんON (1=1)。コンマを使用して(INNERまたはではなくLEFT OUTER JOIN)結合するCount(select * from A) * Count(select * from B)と、(A <> B OR A = B)明示的にJOIN返されるように、テーブルAごとに1行だけではなく、このクエリで行が返されます。これは明らかに適切ではありません。FIRST複合主キータイプが指定されている場合は使用できないようです。

2番目のJOINスタイルは、間違いなく読みやすいとはいえ、速度が遅いという問題があります。これは、両方のオプションにJOINある2つCROSS JOINのs と同様に、より大きなテーブルに対して2つの内部がさらに必要になるためです。

余談:IIF句をMIN/に置き換えるとMAX、同じ数のエントリが返されます。
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
"Before"(MAX)タイムスタンプでは機能しますが、MIN次のように"After"()では直接機能しません。条件の
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
最小値は常に0であるFALSEためです。この0は、ポストエポックDOUBLEDateTimeフィールドはAccessのサブセットであり、この計算によってフィールドが変換される)よりも小さくなります。IIF及びMIN/ MAXゼロによる除算ためAfterXTStamp値作業のために提案された方法交互に(FALSE)集計関数MINとMAXはスキップnull値を生成します。

次のステップ

これをさらに進めると、最初のテーブルのタイムスタンプに直接隣接する2番目のテーブルのタイムスタンプを見つけ、それらのポイントまでの時間距離に基づいて2番目のテーブルのデータ値の線形補間を実行します(つまり、最初のテーブルは「前」と「後」の間の道の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レコードを返します。i7-4790、16GB RAM、SSDなし、Win 8.1 Proシステムでの実行時間はおそらく5〜10分です。


参照1:MS Accessはミリ秒の時間値を処理できる-本当に付随するソースファイル[08080011.txt]

回答:


10

Access DBを使用してこのようなことを行う勇気を称賛する必要があります。私の経験からすると、SQLのようなことを行うのは非常に困難です。とにかく、レビューに。


最初の参加

あなたのIIFフィールドの選択を使用しての恩恵を受ける可能性があるSwitchステートメントを代わりに。特にSQLの場合、a の本体で単純な比較を行うだけでa SWITCH(一般CASE的なSQLで一般的に知られている)が非常に高速である場合がありSELECTます。あなたの場合の構文はほぼ同じですが、1つのフィールドで比較の大部分をカバーするようにスイッチを拡張できます。考慮すべきこと。

  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)については、あなたがやろうとしていることを考えると、あなたが手に入れるのと同じくらい良いと思います。それほど高速ではありませんが、そうなるとは思いません。


2回目の参加

あなたはこれが遅いと言いました。また、コードの観点からは読みにくくなります。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など。テーブルエイリアスとしては、もっと良いと思います。また、フィールド名はあまりよくないと思いますが、これを制御できない可能性があることを理解しています。命名のトピックに関するコードレスコードをすぐに引用し、そのままにしておきます...

「Invective!」は答えました。「let辞を動詞に!」


「次のステップ」クエリ

私はそれがどのように書かれているのかあまり理解できませんでした。テキストエディターに持って行き、読みやすくするためにスタイルを変更しなければなりませんでした。AccessのSQLエディターは不格好なものではないので、通常はNotepad ++やSublime Textなどの優れたエディターでクエリを記述します。より読みやすくするために私が適用したいくつかのスタイルの変更:

  • 2スペースではなく4スペースインデント
  • 数学演算子と比較演算子の周りのスペース
  • ブレースとインデントのより自然な配置(Javaスタイルのブレースを使用しましたが、お好みでCスタイルにすることもできます)

結局のところ、これは非常に複雑なクエリです。IDそれを理解するには、最も内側のクエリであるデータセットから開始する必要があります。これは、最初の結合と同じであると理解しています。タイムスタンプは、あなたがに興味を持っているデバイスのサブセット内、最も近いの後にそれは/前のデバイスのIDとタイムスタンプを返します。だから、代わりにIDそれを呼び出すことはありませ理由ClosestTimestampID

あなたの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オプティマイザーの性能を低下させるためにできることは何でも役に立ちます!


私はのための感覚その計算/アルゴリズムを振り払うことができないBeforeWeightAfterWeightされますが、補間に使用し、より良い行うことができるが、残念ながら、私はそれらと非常によくありませんよ。

クラッシュを回避するための1つの提案は(アプリケーションによっては理想的ではありませんが)、ネストされたサブクエリを独自のテーブルに分割し、必要に応じて更新することです。ソースデータを更新する必要がある頻度はわかりませんが、あまり頻繁でない場合は、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] [TL2]コスト16%
クラスター化インデックスシーク[SecondTable]。[PK_SecondTable] [TL1]コスト16%

最終注文で:
ソートコスト36%
クラスター化インデックススキャン[ReceiverDetails]。[PK_ReceiverDetails]コスト10%
クラスター化インデックスシーク[FirstTable]。[PK_FirstTable]コスト12%
クラスター化インデックスシーク[SecondTable]。[PK_SecondTable]コスト10%
クラスター化インデックスシーク[SecondTable]。[PK_SecondTable]コスト10%
クラスター化インデックスシーク[SecondTable]。[PK_SecondTable] [TL2]コスト10%
クラスター化インデックスシーク[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;

クロス結合を含むクエリに対するクエリのパフォーマンステスト。

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%
ランキングSCAN [SecondTable] [UK_ID] [B] 6%
表スプール5%
、いくつかの他のクラスタ化インデックスは、シークとインデックスシーク(投稿されたクエリと同様)、コストは0%です。

クエリとCROSS JOINの実行時間は.007と8〜9秒です。
コスト比較0%と100%。

FirstTableに50,000件のレコードと1つのレコードを結合条件としてReceiverDetailsにロードし、クエリを実行しました。
50,013は0.9〜1.0秒を返しました。

クロスジョインで2番目のクエリを実行し、約20分間実行してから、強制終了しました。
クロスジョインクエリが元の13のみを返すようにフィルタリングされた場合、実行時間は8〜9秒になります。
フィルタ条件の配置は、最も内側の選択、最も外側の選択、およびその両方でした。変わりはない。

これら2つの結合条件には違いがあり、CROSS JOINが優先されます。最初の条件は述語を使用しますが、CROSS JOINは使用しません。
INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp) CROSS JOIN SecondTable AS B


システムでClosestTimestampID部分を実行すると、Count(*)にカプセル化されるとすぐに152928レコードが返されます。その段階で実際のレコードを返すときにMSAccessがロックされました。他のメソッドの一時テーブルがあらゆる種類のメモリを占有している可能性があります。あなたの方法論から作成する最終的なクエリは、現在使用しているものと非常に似ていると思います。良いことだと思う:)
mpag

1
元のコメントでは、すぐにいくつかのレコードを取得したことを示しました。これは、アクセスの仕組み、アクセス戦略の考案、実行時間への期待の設定に関して重要です。呼び出された遅延実行。(最後のレコードをヒットするとクラッシュしました。)最終クエリに含まれると予想されるリターンレコードの上限数はどのくらいですか?
-byrdzeye

152928を信じる
mpag

新しいレコードが追加されるときの両方のテーブルのDateTime値の性質は何ですか。それらは現在のタイムスタンプまたは最近の値、または完全にランダムですか?
-byrdzeye

最初のテーブルには、2013年以降のDateTimeスタンプがあります。2番目のテーブルには、2015年半ばの数か月以内のDateTimeスタンプがあります。新しい値が追加された場合、既存のセットより後になる可能性があります(保証はされません)。どちらのテーブルにも新しい値を追加できます。
mpag

2

2番目の回答を追加します。最初の回答よりも優れているわけではありませんが、提示されている要件を変更せずに、Accessを打ち負かして提出する方法はいくつかあります。「トリガー」を使用して、複雑さを少しずつ「具体化」します。アクセステーブルにはトリガーがないため、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.