如何从左表中仅获取一条记录与右表中的每条记录

Posted

技术标签:

【中文标题】如何从左表中仅获取一条记录与右表中的每条记录【英文标题】:How to get only one record from left table against each record from right table 【发布时间】:2019-01-14 07:39:41 【问题描述】:

我有两张表,一张用于某种设备,另一张用于它们的位置(使用此表来记录位置历史记录)。表的架构是:

    tbl_Devices:

    DeviceSrNumber (bigint, not null, pk) 其他一些列

    tbl_DeviceLocation

    SrNumber(bigint,not null,pk,identity) DeviceSrNumber(bigint,非空) 位置(varchar(300),非空) AddDateTime(日期时间,非空)

我需要的是DeviceSrNumber,最后一个Location,最后一个位置集AddDateTime

我现在在做什么:

SELECT
    DeviceSrNumber,
    (
        SELECT TOP (1) Location
        FROM tbl_DeviceLocation
        WHERE (DeviceSrNumber = d.DeviceSrNumber)
        ORDER BY AddDateTime DESC
    ) AS 'Location',
    (
        SELECT TOP (1) AddDateTime
        FROM tbl_DeviceLocation
        WHERE (DeviceSrNumber = d.DeviceSrNumber)
        ORDER BY AddDateTime DESC
    ) AS 'AddDateTime '
FROM
    tbl_Devices AS d

但我需要以更简洁的方式使用连接,因为据我所知,子查询效率不高,不推荐在大型数据库中使用。

注意:它只是大型项目的一小部分,将处理数百万条记录。

【问题讨论】:

你有Indexestbl_DeviceLoation吗? 是的。我在 SrNumber 上有聚集索引(bigint,not null,identity,PK)并且还检查了“Location”列。但这和我们的问题有关系吗? CHECK约束 而不是 INDEX,因此您只有一个索引。 子查询性能取决于查询和优化器。与每个优化问题一样,应该在实践中衡量它和其他选项。当然,对于特定的实现,有记录和观察/假设的趋势——在这里,我们可以怀疑某些重复的代码可能没有得到很好的优化,并可能通过连接来寻求 tbl_DeviceLocation 的单一提及。你是怎么尝试的? PS 通过后期编辑进行澄清,而不是 cmets。另外对于代码问题,请提供minimal reproducible example。 【参考方案1】:

你可以从这个开始:

SELECT *
FROM (
    SELECT        
      d.DeviceSrNumber
    , dl.Location    LastLocation
    , dl.AddDateTime LastLocationSetDateTime
    , ROW_NUMBER() OVER(PARTITION BY d.DeviceSrNumber ORDER BY AddDateTime DESC) RN 
    FROM 
        tbl_Devices d
    JOIN tbl_DeviceLocation dl ON dl.DeviceSrNumber = d.DeviceSrNumber 
) D 
WHERE 
    RN = 1

更新

SELECT        
  d.DeviceSrNumber
, MAX(dl.Location)  LastLocation
, MAX(dl.AddDateTime) LastLocationSetDateTime
FROM 
    tbl_Devices d
JOIN tbl_DeviceLocation dl ON dl.DeviceSrNumber = d.DeviceSrNumber 
GROUP BY d.DeviceSrNumber
ORDER BY AddDateTime DESC

【讨论】:

亲爱的@iSR5,我不希望在过去 24 小时内添加位置,我需要每个设备的位置及其时间,而不用担心它们是否在过去 24 小时内更新。 @RaiArslan 然后删除 WHERE 子句,这将获取所有设备及其位置和时间。 再次澄清一下,我想要最后一个位置,而不是分配给设备的所有位置。 @RaiArslan 重新检查查询,我已更新。这是你需要的吗? 亲爱的@iSR5,我不想使用子查询。它产生的结果与我的初始查询相同。请让我知道是否有任何方法可以使用连接。如果是,请告诉我。【参考方案2】:

这样你的查询会更有效率:

SELECT D.DeviceSrNumber, 
       L.Location, 
       L.AddDateTime 
FROM        tbl_Devices D
OUTER APPLY
   (SELECT TOP (1) DL.Location, DL.AddDateTime
    FROM tbl_DeviceLocation DL
    WHERE D.DeviceSrNumber = DL.DeviceSrNumber
    ORDER BY DL.AddDateTime DESC
   ) L;

【讨论】:

你最后错过了什么。 您还没有错过任何其他事情吗?例如:L.Location ?? L?? 仍然需要别名@yper-crazyhat-c​​ubeᵀᴹ 我冒昧地纠正了 cmets 中提到的问题,但请自行查看。该方法本身是有道理的,它只是有一些问题,我相信现在所有的问题都已经解决了。如有必要,请随时进一步编辑 非常感谢!!!是的,我同时在几个项目中,并没有足够专注于正确回复。很高兴有几个人意识到并改进了响应!!!【参考方案3】:

试试这个,它将使用最新的 AddDateTime 选择一个数据

SELECT top(1) D.DeviceSrNumber, L.Location,L.AddDateTime 
FROM tbl_Devices AS D 
JOIN tbl_DeviceLoation AS L ON D.DeviceSrNumber  = L.DeviceSrNumber 
WHERE D.DeviceSrNumber  = L.DeviceSrNumber  order by L.AddDateTime desc

更新

我试过这个,它根据最新记录选择不同的记录。希望这能解决它

WITH CTE AS 
(
SELECT D.DeviceSrNumber, L.Location,L.AddDateTime , RN= ROW_NUMBER() OVER (PARTITION BY  D.DeviceSrNumber ORDER BY  D.DeviceSrNumber)
FROM  tbl_Devices AS D LEFT JOIN tbl_DeviceLoation AS L ON D.DeviceSrNumber  = L.DeviceSrNumber
WHERE D.DeviceSrNumber  = L.DeviceSrNumber group by  D.DeviceSrNumber, L.Location,L.AddDateTime )
SELECT * FROM CTE where RN=1 order by CTE.AddDateTime desc

【讨论】:

亲爱的@muhdam​​ean,这行不通,因为我们需要列出所有设备及其位置。 我尝试使用另一种方法,它对我有用,请尝试一下。 @RaiArslan

以上是关于如何从左表中仅获取一条记录与右表中的每条记录的主要内容,如果未能解决你的问题,请参考以下文章

oracle左连接与右连接

如何使用 Java 在 MySQL 表中仅搜索一条特定记录

左外连接和右外连接的区别

当我进行左连接时,我仍然无法从左表中获取数据?

mysql连接查询

每组右表的左外连接?