将内部连接的结果限制为 1

Limit result of inner join to 1

我有这个架构:

create table ord(id int);
create table orderpos(id int, orderid int, descr varchar(255));

insert into ord(id) values (1);
insert into ord(id) values (2);
insert into orderpos(id, orderid, descr) values(1,1,'huba');
insert into orderpos(id, orderid, descr) values(2,1,'blub');
insert into orderpos(id, orderid, descr) values(3,2,'foo');

所以数据顺序如下:

id
1
2

按顺序pos:

id  orderid descr
1   1       huba
2   1       blub
3   2       foo

我想要:

oId opId    orderid   descr
1   1       1         huba
2   3       2         foo

但是对于这个查询:

select o.id as oId, op.id as opId, op.orderid, op.descr from ord o
inner join orderpos op on op.orderid = o.id;

我得到:

oId opId    orderid   descr
1   1       1         huba
1   2       1         blub
2   3       2         foo

所以我必须以某种方式限制 innerjoin,但是如何?我尝试了通过 google 找到的几种方法,但似乎没有任何效果。

这似乎适用于 SQL-W3-Schools 的编辑:

select o.id as oId, op.id as opId, op.orderid, op.descr 
from ord o
inner join (select * from orderpos op2 group by op2.orderid) op on op.orderid = o.id;

在我看来,只要您在 'descr' 中有不同的值,它就不起作用,这似乎合乎逻辑。不知道为什么这在 W3Schools 的 SQL-Editor 中有效。

Sybase 版本:Adaptive Server Enterprise/15.5/EBF 19902 SMP ESD#5.1/P/x86_64/Enterprise Linux/asear155/2594/64-bit/FBO/Wed 6 月 6 日 01:20:27 2012

你可以使用 exists

select
  o.id as oId,
  op.id as opId,
  op.orderid,
  op.descr
from
  ord o inner join
  orderpos op 
on
  op.orderid = o.id and
  not exists
    (select 1 from orderpos op2 where op2.orderid = o.id and op2.id < op.id)

您可以 select 第一行或第一行 row_number fucntion

WITH x
AS (
    SELECT o.id AS oId
        ,op.id AS opId
        ,op.orderid
        ,op.descr
        ,row_number() OVER (
            PARTITION BY orderid ORDER BY orderid
            ) rows
    FROM ord o
    INNER JOIN orderpos op
        ON op.orderid = o.id
    )
SELECT otd
    ,opid
    ,orderid
    ,DESC
FROM x
WHERE row = 1

由于没有提供逻辑来确定哪一行是 'first record'(请参阅@HoneyBadger 的评论),并且您评论说排序对您的业务案例不重要,我们将使用 max( ) 为每个唯一的 orderid 提取一行:

select  o.id        as oId,
        op.id       as opId,
        op.orderid,
        op.descr

from    ord      o,
        orderpos op,

        (select op2.orderid,
                max(op2.descr) as descr
        from    orderpos op2
        group by op2.orderid
        ) dt

where   op.orderid = o.id
and     op.orderid = dt.orderid
and     op.descr   = dt.descr
order by 1,2
go

 oId     opId    orderid  descr
 ------- ------- -------- ----------
       1       1        1 huba
       2       3        2 foo

ord(o) 和 orderpos(op) 之间的连接条件保持不变;添加派生的 table (dt) 允许我们进一步限制来自 orderpos(op).

感兴趣的行

在这种情况下,我们使用 max() 生成您正在寻找的输出只是巧合。 [提示:将 max() 替换为 min() 以显示“2/blub”而不是“1/huba”。]


相同的想法,但使用相关的子查询而不是派生的 table:

select  o.id        as oId,
        op.id       as opId,
        op.orderid,
        op.descr

from    ord      o,
        orderpos op

where   op.orderid = o.id
and     op.descr = (select      max(op2.descr)
                        from    orderpos op2
                        where   op2.orderid = op.orderid)
order by 1,2
go

 oId     opId    orderid  descr
 ------- ------- -------- ----------
       1       1        1 huba
       2       3        2 foo

或者我们可以将 max(op2.descr) 替换为 max(op2.id).

关键问题是选择某种方法……在这种情况下的任何方法……允许我们从 op2 中为给定的 orderid 选择一行(通过 group by op2.orderid).

注意:只要给定的 descr 值对于给定的 orderid 是唯一的,建议的解决方案就可以工作(例如,您不能在 orderpos 中有 2x 行具有相同的 orderid 和 descr)。如果这是一个无效的假设,那么我们将需要更多样本数据 and/or 更好地描述 orderpos table 中的数据(例如,pk 约束、唯一索引等)。

这就是 CROSS APPLY 的用途:

SELECT o.id as oId, op.id as opId, op.orderid, op.descr 
FROM ord AS o
CROSS APPLY (   SELECT TOP (1) *
                FROM orderpos AS op
                WHERE op.orderid = o.id
                ORDER BY op.id) AS op;

它是子查询和连接的混合体。它让您 return 多列和多行用于基于行的条件。

编辑。注意到它不是对 SQL 服务器的请求,因此 CROSS APPLY 将不起作用,但这通常被称为 LATERAL JOIN,Sybase 似乎支持它:http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.sqlanywhere.12.0.1/dbreference/from-statement.html

文档具有以下语法:

SELECT *
FROM A, LATERAL( SELECT * FROM B WHERE A.x = B.x ) LDT;

所以也许这应该有效:

SELECT *
FROM ord, LATERAL( SELECT TOP 1 * FROM orderpos WHERE orderpos.orderid = ord.id) LDT;