简体   繁体   中英

Selecting the latest two distinct records from Oracle

I have a table that consists of multiple records for type INT (CUSTOMER) and need to get the last two distinct transactions for the NAME of the account. So, in my SQL, I have the following which provides the latest 4 transactions which are all correct (I have to select the latest 4 records, because, as you see selecting the latest two would result in both records being 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

However, what I need returned are only the latest DISTINCT NAME orders, so rather than the output above, I would like to see only:

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

Is there a query that I could perform in a single statement to get the required output? Any help would be greatly appreciated!!!!

Here is one way - using analytic functions. It is a bit complicated; unfortunately, count(distinct ...) can be used as analytic function, but only with a partition by clause - not with an order by clause as well. So we have to manually create that ourselves, using the start-of-group technique.

Note that the first subquery in the WITH clause is there only to generate the test data; if you use this against an actual table, remove it from the code and make sure the table name in the second subquery (and the column names!) is/are correct. I replaced the column name date with dt ; date is a reserved word and should not be used as a column name.

I am showing a general solution - you can certainly filter for a single customer number, if needed. (Or, if you always need this with a single customer at a time, you can simplify the query somewhat.)

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

In Oracle 12.1 and higher, match_recognize does quick work of such requirements (and is usually quite a bit faster than analytic function approaches).

I will not write a whole tutorial on match_recognize in this Answer; let me just point out that {- ... -} in the PATTERN syntax means those rows are part of the match definition, but those rows are excluded from the output. The rest is an elementary application of match_recognize .

I am showing a general solution - you can certainly filter for a single customer number, if needed. (Or, if you always need this with a single customer at a time, you can simplify the query somewhat.)

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  
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
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)
;

Of course using IN is not the best in huge tables, and you can use a inner join.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM