Access (Jet) SQL:表 B 中的日期时间戳位于表 A 中的每个日期时间戳的两侧

mpa*_*pag 21 join ms-access aggregate datetime

第一句话

如果您只是想破解代码,您可以放心地忽略以下(包括)JOINs:Starting Off 部分。的背景结果只是作为背景。如果您想查看代码最初的样子,请查看 2015-10-06 之前的编辑历史记录。


客观的

最终,我想根据表中可用 GPS 数据的日期时间戳计算发射机(XXmit)的内插 GPS 坐标,这些数据SecondTable直接位于表中的观测值的两侧FirstTable

我的近期目标实现的最终目标是要弄清楚如何最好地加入FirstTableSecondTable得到这些侧翼的时间点。稍后我可以使用该信息我可以计算中间 GPS 坐标,假设沿等距柱状坐标系进行线性拟合(花哨的话说我不在乎地球是这个比例的球体)。


问题

  1. 有没有更有效的方法来生成最接近的前后时间戳?
    • 我自己通过抓取“之后”来修复,然后仅获取与“之后”相关的“之前”。
  2. 有没有更直观的方式不涉及(A<>B OR A=B)结构。
    • Byrdzeye提供了基本的替代方案,但是我的“现实世界”经验与他的所有 4 个加入策略的表现都不一致。但是完全归功于他解决了替代连接样式。
  3. 您可能有的任何其他想法、技巧和建议。
    • 到目前为止,byrdzeyePhrancis在这方面都非常有帮助。我发现Phrancis 的建议非常好,并在关键阶段提供了帮助,所以我会在这里给他优势。

对于问题 3,我仍然希望得到任何额外的帮助。 要点反映了我认为在个别问题上对我帮助最大的人。


表定义

半视觉表现

第一表

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
Run Code Online (Sandbox Code Playgroud)

第二表

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

ValidXmitters

Field (and primary key)
  XmitID    | TEXT(25) -- primary, unique, no null, simple
Run Code Online (Sandbox Code Playgroud)

SQL小提琴...

...以便您可以使用表定义和代码 这个问题是针对 MSAccess 的,但正如 Phrancis 指出的那样,Access 没有 SQL 小提琴风格。因此,您应该可以根据Phrancis 的回答这里查看我的表定义和代码:http : //sqlfiddle.com/#! 6/ e9942 /4
(外部链接)


JOIN:开始

我目前的“内在胆量”加入策略

首先创建一个 FirstTable_rekeyed 与列顺序和复合主键(RecTStamp, ReceivID, XmitID)all indexed/sorted 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;
Run Code Online (Sandbox Code Playgroud)

上面的查询用 153006 条记录填充新表,并在 10 秒左右的时间内返回。

当使用 TOP 1 子查询方法时,将整个方法包装在“SELECT Count(*) 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_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))
Run Code Online (Sandbox Code Playgroud)

以前的“内在胆量”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)))
Run Code Online (Sandbox Code Playgroud)

第二个(较慢)

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; 
Run Code Online (Sandbox Code Playgroud)

背景

我有一个遥测表(别名为 A),其中包含不到 100 万个条目,其中包含基于DateTime戳记、发射器 ID 和记录设备 ID的复合主键。由于我无法控制的情况,我的 SQL 语言是 Microsoft Access 中的标准 Jet DB(用户将使用 2007 及更高版本)。由于 Transmitter ID,这些条目中只有大约 200,000 个与查询相关。

有第二个遥测表(别名 B),它涉及大约 50,000 个条目和一个DateTime主键

第一步,我专注于从第二个表中找到与第一个表中的邮票最接近的时间戳。


加入结果

我发现的怪癖...

...在调试过程中

@byrdzeye在评论中指出的那样(此后已消失)编写JOIN逻辑是一种交叉连接的形式,这感觉真的很奇怪。请注意,取代了在上面显示出来的代码,以使返回的行的数量或身份没有影响。我似乎也不能省略 ON 子句或说. 仅使用逗号连接(而不是or )会导致此查询中返回的行,而不是每个表 A 仅一行,因为 (A<>B OR A=B) 显式返回。这显然不合适。给定复合主键类型,似乎无法使用。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

第二种JOIN风格虽然可以说更清晰,但速度较慢。这可能是因为JOIN对于更大的表以及CROSS JOIN在两个选项中找到的两个s都需要额外的两个内部s 。

旁白:IIFMIN/替换子句MAX似乎返回相同数量的条目。
MAX(-(B.XTStamp<=A.RecTStamp)*B.XTStamp)
适用于“Before” ( MAX) 时间戳,但不适用于“After ” ( ) 时间戳,MIN如下所示:
MIN(-(B.XTStamp>A.RecTStamp)*B.XTStamp)
因为条件的最小值始终为 0 FALSE。此 0 小于任何后时代DOUBLE(其中一个DateTime字段是 Access 中的子集,并且此计算将该字段转换为该字段)。的IIFMIN/MAX方法的通过零提出了AfterXTStamp值工作,因为分割(候补FALSE)产生空值,其中,集合函数MIN和MAX跳过。

下一步

更进一步,我希望找到第二个表中的时间戳,它直接位于第一个表中的时间戳的两侧,并根据到这些点的时间距离对第二个表中的数据值执行线性插值(即,如果时间戳来自第一个表是“之前”和“之后”之间的 25%,我希望 25% 的计算值来自与“之后”点相关的第二个表值数据,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;
Run Code Online (Sandbox Code Playgroud)

...返回 152928 条记录,符合(至少近似)预期记录的最终数量。在我的 i7-4790、16GB RAM、没有 SSD、Win 8.1 Pro 系统上,运行时间可能是 5-10 分钟。


参考文献1:MS Access Can 处理毫秒时间值--真实随附的源文件[08080011.txt]

Phr*_*cis 10

首先,我必须赞扬你用 Access DB 做这样的事情的勇气,根据我的经验,这很难做任何类似 SQL 的事情。无论如何,进入审查。


第一次加入

您的IIF字段选择可能会受益于使用Switch 语句。这似乎是有时的情况下,尤其是与事情SQL,一个SWITCH(通常被称为CASE典型的SQL)是相当快的时候只是一个身体做简单比较SELECT。您的情况的语法几乎相同,尽管可以扩展开关以涵盖一个字段中的大量比较。需要考虑的事情。

  SWITCH (
    expr1, val1,
    expr2, val2,
    val3        -- default value or "else"
  )
Run Code Online (Sandbox Code Playgroud)

在较大的语句中,开关还有助于提高可读性。在上下文中:

  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
Run Code Online (Sandbox Code Playgroud)

至于加入本身,我认为(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)
Run Code Online (Sandbox Code Playgroud)

命名事物

我认为你的东西的命名方式充其量是无用的,最糟糕的是神秘的。A, B, A1, B1等作为表别名我认为可能会更好。另外,我认为字段名称不是很好,但我意识到您可能无法控制这一点。我将在命名事物的主题上快速引用《无代码代码》,然后将其保留...

“骂人!” 女祭司回答。“动词你的咒骂名词!”


“后续步骤”查询

我无法理解它是如何编写的,我不得不将它带到文本编辑器并进行一些样式更改以使其更具可读性。我知道 Access 的 SQL 编辑器非常笨拙,所以我通常在 Notepad++ 或 Sublime Text 等优秀的编辑器中编写查询。我应用了一些样式更改以使其更具可读性:

  • 缩进 4 个空格而不是 2 个空格
  • 数学和比较运算符周围的空格
  • 更自然地放置大括号和缩进(我使用的是 Java 风格的大括号,但也可以是 C 风格的,根据您的喜好)

事实证明,这确实是一个非常复杂的查询。为了理解它,我必须从最里面的查询开始,你的ID数据集,我理解它与你的 First Join 相同。它返回在您感兴趣的设备子集中之前/之后时间戳最接近的设备的 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>)
Run Code Online (Sandbox Code Playgroud)

也许不是一个巨大的性能提升,但是我们可以做的任何事情来帮助可怜的 Jet DB 优化器都会有所帮助!


我不能动摇的感觉是,计算/算法BeforeWeightAfterWeight您使用插值可以做得更好,但不幸的是,我不是很好的那些。

避免崩溃的一个建议(尽管它并不理想,具体取决于您的应用程序)是将嵌套的子查询分解为它们自己的表并在需要时更新它们。我不确定您需要多久刷新一次源数据,但如果不是太频繁,您可能会考虑编写一些 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;
Run Code Online (Sandbox Code Playgroud)


byr*_*eye 5

  • 添加了额外的属性和过滤条件。
  • 使用最小和最大嵌套查询消除了任何形式的交叉连接。这是最大的性能提升。
  • 最内层嵌套查询返回的最小和最大侧翼值是主键值(扫描),用于使用最终计算的查找检索附加侧翼属性(纬度和经度)(访问确实具有应用等效项)。
  • 在最内部的查询中检索和过滤主表属性,应该有助于提高性能。
  • 无需格式化(StrDateIso8601Msec)时间值进行排序。使用表中的日期时间值是等效的。

SQL Server 执行计划(因为 Access 不能显示这个)
没有最终顺序,因为它很昂贵:
Clustered Index Scan [ReceiverDetails].[PK_ReceiverDetails] Cost 16%
Clustered Index Seek [FirstTable].[PK_FirstTable] Cost 19%
Clustered Index查找 [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;
Run Code Online (Sandbox Code Playgroud)

针对包含交叉联接的查询对我的查询进行性能测试。

FirstTable 加载了 13 条记录,SecondTable 加载了 1,000,000。
我的查询的执行计划与发布的内容没有太大变化。
交叉连接的执行计划:
使用INNER JOIN SecondTable AS B ON (A.RecTStamp <> B.XTStamp OR A.RecTStamp = B.XTStamp
嵌套循环的嵌套循环成本 81%如果使用CROSS JOIN SecondTable AS B' or ',SecondTable AS B
流聚合 8%
索引扫描 [SecondTable][UK_ID][B] 6%
表假脱机 5%
其他几个聚集索引查找和索引查找(类似于我发布的查询)成本为 0%。

我的查询和 CROSS JOIN 的执行时间为 0.007 秒和 8-9 秒。
成本比较 0% 和 100%。

我将 50,000 条记录和一条记录加载到 ReceiverDetails 的 FirstTable 以作为连接条件并运行我的查询。
50,013 在 0.9 到 1.0 秒之间返回。

我使用交叉连接运行了第二个查询,并在我杀死它之前让它运行了大约 20 分钟。
如果过滤交叉连接查询只返回原来的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