如何加入子查询的第一行?

Posted

技术标签:

【中文标题】如何加入子查询的第一行?【英文标题】:How do I join the first row of a subquery? 【发布时间】:2011-06-09 05:02:25 【问题描述】:

我有一个发票表和一个键相关的相关数据的子表。特别是,对于每张发票,我只对子表中的第一个相关行感兴趣。鉴于我希望每个发票键都有一个相关行 - 我该如何完成?

Select i.[Invoice Number],
       c.[Carrier Name]
From Invoice i
    Left Join Carriers c on i.[InvoiceKey] = c.[InvoiceKey]
Where -- what?

我猜从语义上讲,我正在寻找类似于 Top 1 c.CarrierName Group by InvoiceKey 概念的东西(或者如果在 T-SQL 中可能的话,那将是什么概念。)

我考虑过对子查询进行左连接,但这似乎效率不高。有没有人有任何 T-SQL 技巧来有效地实现这一目标?

编辑:对不起,伙计们,我忘了提到这是 SQL Server 2000,所以虽然我要为当前可行的 SQL Server 2005/2008 响应投赞成票,但我不能恐怕不接受他们。

【问题讨论】:

第二个表是否有任何属性说明哪一行是第一秒等。 @Cyber​​nate 没有,除了索引的顺序 【参考方案1】:

假设Carriers 有一个名为PRIMARY KEYid

SELECT  i.[Invoice Number],
        c.[Carrier Name]
FROM    Invoice i
JOIN    Carriers c
ON      c.id = 
        (
        SELECT  TOP 1 ID
        FROM    Carriers ci
        WHERE   ci.InvoiceKey = i.InvoiceKey
        ORDER BY
                id -- or whatever
        )

【讨论】:

与 group by 和 having 子句相比,this 的性能可能很差。您正在针对 Invoice 中的每一行的 Carriers 表执行相关子查询。 @Chris:与什么相比?你没有提供一个有效的例子。 @Quassnoi 太棒了,太棒了,太棒了,太完美了,救了我的命。【参考方案2】:

这对我有用:

select ir.[Invoice Number], c.[Carrier Name]
from 
    (select ROW_NUMBER() over (order by i.[Invoice Number] asc) AS RowNumber, i.[Invoice Number], i.InvoiceKey
    from Invoice i) AS ir
left join Carriers c
on ir.InvoiceKey = c.InvoiceKey
where RowNumber = 1
union all
select ir.[Invoice Number], NULL as [Carrier Name]
from 
    (select ROW_NUMBER() over (order by i.[Invoice Number] asc) AS RowNumber, i.[Invoice Number]
    from Invoice i) AS ir
where RowNumber > 1

select TOP 1 i.[Invoice Number], c.[Carrier Name]
from Invoice i
left join Carriers c
on i.InvoiceKey = c.InvoiceKey
union all
select ir.[Invoice Number], NULL as [Carrier Name]
from 
    (select ROW_NUMBER() over (order by i.[Invoice Number] asc) AS RowNumber, i.[Invoice Number]
    from Invoice i) AS ir
where RowNumber > 1

【讨论】:

+1 如果我在 SQL Server 2005+ 上就可以完成这项工作,但我忘了提到我需要在 SQL Server 2000 机器上运行。【参考方案3】:

我就是这样做的,使用的语法与你的稍有不同(mysql 风格),但我想你也可以将它应用到你的解决方案中:

SELECT i.invoiceNumber, c.carrierName
FROM Invoice as i
LEFT JOIN Carriers as c ON (c.id = (SELECT id FROM Carriers WHERE invoiceKey = i.invoiceKey ORDER BY id LIMIT 1))

这将从 Invoice 中获取所有记录,并将其与 Carriers 中的一个(或零个)记录连接起来,特别是具有相同 invoiceKey 且只有第一个的记录。

只要您在 Carriers.invoiceKey 上有一个索引,这个查询的性能应该是可以接受的。

塞巴斯蒂安

【讨论】:

【参考方案4】:
;with cteRowNumber as (
    select c.InvoiceKey, c.[Carrier Name], ROW_NUMBER() over (partition by c.InvoiceKey order by c.[Carrier Name]) as RowNum
        from Carriers c
)
select i.[Invoice Number],
       rn.[Carrier Name]
    from Invoice i
        left join cteRowNumber rn
            on i.InvoiceKey = rn.InvoiceKey
                and rn.RowNum = 1

【讨论】:

@abatishchev:我没看到。如果我将rn.RowNum = 1 包含在我的连接条件中,那应该只连接“第一个”(由窗口函数的顺序定义)。 +1 技术上正确,但我忘了提到我正在寻找 SQL Server 2000,所以 CTE 不是一个选项。【参考方案5】:

在这种情况下,我经常使用一种设备,我在此将其应用于您的示例并在下面进行描述:

SELECT
  i.[Invoice Number],
  c.[Carrier Name]
FROM Invoice i
  INNER JOIN Carriers c ON i.InvoiceKey = c.InvoiceKey
  INNER JOIN (
    SELECT MIN(ID) AS ID
    FROM Carriers
    GROUP BY InvoiceKey
  ) c_top ON c.ID = c_top.ID

我认为,这大致就是 Quassnoi 发布的内容,只是我尽量避免使用这样的 SELECT TOP。

InvoiceCarriers 基于它们的链接表达式(在本例中为InvoiceKey)连接。现在,Carriers 可以有多个相同的InvoiceKey 行,所以我们需要限制输出。这是使用派生表完成的。

派生表根据用于链接两个表 (InvoiceKey) 的相同表达式对 Carrier 中的行进行分组。

还有另一种方法:您可以使用IN (subquery) 而不是加入派生表,效果相同。也就是说,完整的查询将如下所示:

SELECT
  i.[Invoice Number],
  c.[Carrier Name]
FROM Invoice i
  INNER JOIN Carriers c ON i.InvoiceKey = c.InvoiceKey
    AND c.ID IN (SELECT MIN(ID) FROM Carriers GROUP BY InvoiceKey)

【讨论】:

【参考方案6】:
group by carriername having max(invoicenumber)

获取每张发票的第一个承运人:

group by invoicenumber having max(carriername)
-- substitute the column you want to order by for carrier name to change which is 'first'

【讨论】:

这不起作用 - 这只会给我发票编号最高的承运人的承运人名称。我需要的是每张发票的第一个承运人。 不,这应该为您提供每个承运人的最高发票编号。通过撤销分组依据/拥有,您可以获得每张发票的第一个承运人。 请记住,在 group by 子句之后应用有子句:-) HAVING MAX(invoicenumber) 甚至不会解析。 MAX(invoicenumber) 不是谓词。 @Chris:这也不会解析。您不能在HAVING 子句中使用未聚合的表达式。【参考方案7】:

您也可以使用OUTER APPLY。 请注意对未知字段名称使用尖括号:

Select i.[Invoice Number], c.[Carrier Name], x.<Carrier_field1>
From Invoice i
OUTER APPLY 
(
    SELECT TOP 1
    FROM Carriers c 
    WHERE c.[InvoiceKey] = i.[InvoiceKey]
    ORDER BY <order _clause>
) x

【讨论】:

以上是关于如何加入子查询的第一行?的主要内容,如果未能解决你的问题,请参考以下文章

Oracle join 子查询的第一行

sql 通过返回子查询的第一行来获取员工的最新活动配置文件,该子查询将获取所提供的e的所有员工配置文件

加入查询和子查询

mysql - 对有序子查询中的第一个结果进行分组

Oracle:如何通过将子查询删除到条件或加入同一张表来提高查询?

Laravel中的MySQL子查询,如何加入查询并将第二个查询的结果作为新列添加到第一个查询?