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.