从 Oracle 中选择最新的两条不同记录

Posted

技术标签:

【中文标题】从 Oracle 中选择最新的两条不同记录【英文标题】:Selecting the latest two distinct records from Oracle 【发布时间】:2018-01-10 18:03:52 【问题描述】:

我有一个表,其中包含 INT (CUSTOMER) 类型的多条记录,并且需要获取帐户名称的最后两个不同事务。因此,在我的 SQL 中,我有以下提供最新的 4 个事务,这些事务都是正确的(我必须选择最新的 4 个记录,因为如您所见,选择最新的两个会导致两个记录都是 NAME 'JOHNSON') :

SQL> SELECT ID, NAME, DATE, CUSTOMER
    FROM (select * from ORDER_TABLE ORDER BY DATE DESC) ORDER_TABLE
    WHERE rownum <= 4
    and (CUSTOMER = 1002) 
    ORDER BY rownum DESC;

        ID NAME                DATE         CUSTOMER
---------- ------------------- ---------    ----------
        90 SMITH               26-DEC-17    1002
       135 JOHNSON             09-DEC-17    1002
       235 JOHNSON             01-JAN-18    1002
       322 JOHNSON             04-JAN-18    1002

但是,我需要返回的只是最新的 DISTINCT NAME 订单,所以我希望只看到以下输出,而不是上面的输出:

        90 SMITH               26-DEC-17    1002
       322 JOHNSON             04-JAN-18    1002

是否有可以在单个语句中执行的查询以获得所需的输出?任何帮助将不胜感激!!!!

【问题讨论】:

您的 Oracle 版本是多少?不同的解决方案有不同的版本。 【参考方案1】:

在 Oracle 12.1 及更高版本中,match_recognize 可以快速处理此类要求(并且通常比分析函数方法快很多)。

我不会在这个答案中写一个关于match_recognize 的完整教程;让我指出PATTERN 语法中的- ... - 意味着这些行是匹配定义的一部分,但这些行被排除在输出之外。剩下的就是match_recognize的基本应用。

我展示了一个通用解决方案 - 如果需要,您当然可以过滤单个客户编号。 (或者,如果您一次总是需要一个客户,您可以稍微简化查询。)

with
  tbl ( id, name, dt, customer ) as (
    select  90, 'SMITH'  , to_date('26-DEC-17', 'dd-MON-rr'), 1002 from dual union all
    select 135, 'JOHNSON', to_date('09-DEC-17', 'dd-MON-rr'), 1002 from dual union all
    select 235, 'JOHNSON', to_date('01-JAN-18', 'dd-MON-rr'), 1002 from dual union all
    select 322, 'JOHNSON', to_date('04-JAN-18', 'dd-MON-rr'), 1002 from dual
  )
select *
from   tbl
match_recognize(
  partition by customer
  order by dt desc
  all rows per match
  pattern ( ^ a - b* - c )
  define b as name = a.name
);

  CUSTOMER DT                ID NAME  
---------- --------- ---------- -------
      1002 04-JAN-18        322 JOHNSON
      1002 26-DEC-17         90 SMITH  

【讨论】:

【参考方案2】:

这是一种方法 - 使用分析函数。这有点复杂;不幸的是,count(distinct ...) 可以用作分析函数,但只能与 partition by 子句一起使用 - 也不能与 order by 子句一起使用。所以我们必须自己手动创建它,使用起始组技术。

注意 WITH 子句中的第一个子查询仅用于生成测试数据;如果您对实际表使用它,请将其从代码中删除并确保第二个子查询中的表名(以及列名!)是正确的。我将列名date 替换为dtdate 是保留字,不应用作列名。

我展示了一个通用解决方案 - 如果需要,您当然可以过滤单个客户编号。 (或者,如果您一次总是需要一个客户,您可以稍微简化查询。)

with
  tbl ( id, name, dt, customer ) as (
    select  90, 'SMITH'  , to_date('26-DEC-17', 'dd-MON-rr'), 1002 from dual union all
    select 135, 'JOHNSON', to_date('09-DEC-17', 'dd-MON-rr'), 1002 from dual union all
    select 235, 'JOHNSON', to_date('01-JAN-18', 'dd-MON-rr'), 1002 from dual union all
    select 322, 'JOHNSON', to_date('04-JAN-18', 'dd-MON-rr'), 1002 from dual
  ),
  flg ( id, name, dt, customer, flag ) as (
    select id, name, dt, customer,
           case when lag(name) over (partition by customer order by dt desc) = name
                then null else 1 end
    from   tbl
  ),
  prep ( id, name, dt, customer, sm ) as (
    select id, name, dt, customer,
           sum(flag) over (partition by customer order by dt desc)
    from   flg
  )
select max(id) keep (dense_rank first order by dt desc) as id,
       name, max(dt) as dt, customer
from   prep
where  sm <= 2
group by customer, name
;

        ID NAME    DT          CUSTOMER
---------- ------- --------- ----------
        90 SMITH   26-DEC-17       1002
       322 JOHNSON 04-JAN-18       1002

【讨论】:

【参考方案3】:
CREATE TABLE SANORD ( ID INT, NAME VARCHAR2(20), ODATE DATE, CUST INT);
Table created.

INSERT INTO SANORD VALUES (90, 'SMITH','26-DEC-17',1002);
1 row created.

INSERT INTO SANORD VALUES (135, 'JOHNSON','09-DEC-17',1002);
1 row created.

INSERT INTO SANORD VALUES (235, 'JOHNSON','01-JAN-18',1002);
1 row created.

INSERT INTO SANORD VALUES (322, 'JOHNSON','04-JAN-18',1002);
1 row created.

COMMIT;

SELECT id, name, odate, cust
FROM   sanord
WHERE  (name, odate) IN
       ( select name, max(odate) from sanord where cust = 1002 group by name)
;

        ID NAME                 ODATE           CUST
---------- -------------------- --------- ----------
        90 SMITH                26-DEC-17       1002
       322 JOHNSON              04-JAN-18       1002

【讨论】:

现在在您创建的表中再添加一行,为同一客户添加第三个不同的名称,然后查看您的查询产生的结果。 您可能会遇到一些更友好,更少被动攻击性:) 这是我的第一篇文章,负面标记非常受欢迎,谢谢。我不像你这样的专家。如果只有专家回答问题,这里会有点寂寞!我插入了另一行名为 MARK 的行,得到了三行。我可以看到您的论点将走向何方,但是 OP 的请求可以有不同的解释。 2 个令人困惑的请求“需要获取帐户名称的最后两个不同的交易”。和“我需要返回的只是最新的 DISTINCT NAME 订单” 对不起,如果您感到被冒犯;我严格根​​据我是否相信他们是否有帮助来支持和反对答案,而不考虑发布者是谁或他们的声誉。过去,我对初学者的答案投了赞成票,对专家的答案投了反对票(有时是错误的 - 它确实发生了);这就是该网站应该如何工作的方式。不要因为我的反对而气馁,这不是对你的个人意见——它只是一个孤立的答案。【参考方案4】:
SELECT id, name, date, customer
FROM   order_table
WHERE  (name, date) IN 
       (SELECT name, max(date)
        FROM   order_table
        WHERE  customer = 1002
        GROUP BY name)
;

当然在大表中使用IN并不是最好的,你可以使用内连接。

【讨论】:

您似乎没有理解这个问题。您在查询中的哪个位置为同一客户选择具有两个最近的 DISTINCT NAMES 的行?请参阅 OP 所需的输出。此外,您可以使用我的答案中的测试数据来测试您的查询。 感谢您的负面标记。我创建了一个表,插入了数据,在发布答案之前测试了查询并且它有效。 现在在您创建的表中再添加一行,为同一客户添加第三个不同的名称,然后查看您的查询产生的结果。

以上是关于从 Oracle 中选择最新的两条不同记录的主要内容,如果未能解决你的问题,请参考以下文章

获取group by platform和semver的两条最新记录

Netezza SQL 比较同一表中的两条记录

Oracle 选择查询不获取值为空的记录

Oracle 特定选择

Oracle - 选择一个布尔值?从 MYTABLE 中选择 COUNT(*) > 0

oracle记录中选择出第一条记录