I have data:
CLIENT_NO , DATE , TYPE
CLIENT 1- 22/09/2014 -001
CLIENT 1- 19/09/2014 -002
CLIENT 1- 10/09/2014 -005
CLIENT 2- 15/09/2014 -012
CLIENT 2- 20/09/2014 -011
I want to have latest TYPE stored in this table over time for each client. How can I do that using PL/SQL?
Well, you don't really need PL/SQL; pure SQL will do.
One option is to sort them (analytic functions as row_number or rank help in this case), and then fetch rows with row number = 1 (because of order by clause which sorts them by date value in descending order).
SQL> with
2 test (client_no, datum, type) as
3 -- sample data
4 (select 'client 1', date '2014-09-22', '001' from dual union all
5 select 'client 1', date '2014-09-19', '002' from dual union all
6 select 'client 1', date '2014-09-10', '005' from dual union all
7 select 'client 2', date '2014-09-15', '012' from dual union all
8 select 'client 2', date '2014-09-20', '011' from dual
9 ),
10 -- sort them
11 sorted as
12 (select client_no, datum, type,
13 row_number() over (partition by client_no order by datum desc) rn
14 from test
15 )
16 -- select the one with RN = 1
17 select client_no, datum, type
18 from sorted
19 where rn = 1;
CLIENT_NO DATUM TYPE
---------- ---------- ----
client 1 22/09/2014 001
client 2 20/09/2014 011
SQL>
Related
I have a table like this:
Division
Region
Date of Last Visit
1
2
11/20/2021
1
2
11/18/2021
1
7
10/18/2021
1
7
11/19/2021
2
2
11/17/2021
2
3
09/20/2021
2
3
10/20/2021
I want to write a query that groups by the division and region columns and gives me the last 5 dates for each group separated by commas in a single column. Something like this:
Division
Region
Date of Last Visit
Today
Days since last visit
1
2
11/20/2021, 11/18/2021
sysdate
sysdate - max(date of last visit)
1
7
10/18/2021, 11/19/2021
sysdate
sysdate - max(date of last visit)
2
2
11/17/2021
sysdate
sysdate - max(date of last visit)
2
3
9/20/2021, 10/20/2021
sysdate
sysdate - max(date of last visit)
The last two columns are custom calculated columns that I also need for the final output table. Any help would be greatly appreciated as I have tried a lot of things but I keep getting errors about it not being grouped properly, possibly because of the two extra columns at the end. But even without that, I am not sure how to fetch only the last 5 dates per group in oracle.
Thanks!
You want to filter the greatest-n-per-group using the ROW_NUMBER analytic function and then aggregate:
SELECT division,
region,
LISTAGG(TO_CHAR(date_of_last_visit, 'DD/MM/YYYY'), ',')
WITHIN GROUP (ORDER BY date_of_last_visit DESC)
AS date_of_last_visit,
SYSDATE AS today,
TRUNC(SYSDATE - MAX(date_of_last_visit)) AS days_since_last_visit
FROM (
SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY division, region
ORDER BY date_of_last_visit DESC) AS rn
FROM table_name t
)
WHERE rn <= 5
GROUP BY
division,
region
Which, for the sample data:
CREATE TABLE table_name (division, region, date_of_last_visit) as
select 1, 2, date '2021-11-20' from dual union all
select 1, 2, date '2021-11-18' from dual union all
select 1, 7, date '2021-10-18' from dual union all
select 1, 7, date '2021-11-19' from dual union all
select 2, 2, date '2021-11-17' from dual union all
select 2, 3, date '2021-09-20' from dual union all
select 2, 3, date '2021-10-20' from dual;
Outputs:
DIVISION
REGION
DATE_OF_LAST_VISIT
TODAY
DAYS_SINCE_LAST_VISIT
1
2
20/11/2021,18/11/2021
30-NOV-21
10
1
7
19/11/2021,18/10/2021
30-NOV-21
11
2
2
17/11/2021
30-NOV-21
13
2
3
20/10/2021,20/09/2021
30-NOV-21
41
db<>fiddle here
Here you go; read comments within code.
SQL> with test (division, region, datum) as
2 -- sample data
3 (select 1, 2, date '2021-11-20' from dual union all
4 select 1, 2, date '2021-11-18' from dual union all
5 select 1, 7, date '2021-10-18' from dual union all
6 select 1, 7, date '2021-11-19' from dual union all
7 select 2, 2, date '2021-11-17' from dual union all
8 select 2, 3, date '2021-09-20' from dual union all
9 select 2, 3, date '2021-10-20' from dual
10 ),
11 temp as
12 -- rank rows per division/region, sorted by date column in descending order
13 (select t.*,
14 rank() over (partition by division, region order by datum desc) rnk
15 from test t
16 )
17 -- select up to 5 last rows per division/region
18 select division, region,
19 listagg(datum, ', ') within group (order by datum) dates,
20 trunc(sysdate) today,
21 --
22 (select trunc(sysdate) - a.datum
23 from temp a
24 where a.division = t.division
25 and a.region = t.region
26 and a.rnk = 1) days_since
27 from temp t
28 where rnk <= 5
29 group by division, region
30 order by division, region;
DIVISION REGION DATES TODAY DAYS_SINCE
---------- ---------- ------------------------------ ---------- ----------
1 2 11/18/2021, 11/20/2021 11/30/2021 10
1 7 10/18/2021, 11/19/2021 11/30/2021 11
2 2 11/17/2021 11/30/2021 13
2 3 09/20/2021, 10/20/2021 11/30/2021 41
SQL>
I have an Oracle stored procedure that takes input parameters as two date ranges.
e.g.
sp_periodic_data(p_from_date DATE, p_to_date DATE) // let's take p_from_date = 01-Jan-2021 and p_to_date = '03-31-2021'
I need to pick the latest record for each month from the table and add its corresponding value for the time period.
Table Value :
ID
Date
value
1
1-jan-2021
10
1
10-jan-2021
20
2
15-jan-2021
15
2
16-jan-2021
20
2
02-feb-2021
10
2
06-feb-2021
15
1
17-feb-2021
10
1
5-mar-2021
15
1
17-mar-2021
10
2
10-mar-2021
10
Expected output: Need to add the latest record (latest date) for each ID for every month between Jan to March
40 --> for ID 1 (20+10+10)
45 --> for ID 2 (20+15+10)
for a start :
SQL for Beginners
Aggregate Functions
Analytical SQL in Oracle Database 12c
example:
with
list_dates(id,dates,value) as
(
select 1,'1-jan-2021',10 from dual union all
select 1,'10-jan-2021',20 from dual union all
select 2,'15-jan-2021',15 from dual union all
select 2,'16-jan-2021',20 from dual union all
select 2,'02-feb-2021',10 from dual union all
select 2,'06-feb-2021',15 from dual union all
select 1,'17-feb-2021',10 from dual union all
select 1,'5-mar-2021',15 from dual union all
select 1,'17-mar-2021',10 from dual union all
select 2,'10-mar-2021',10 from dual
)
,step1 as (
select
id, trunc(to_date(dates,'dd-mon-yyyy'),'mm') mm,max(value) keep(dense_rank last order by to_date(dates,'dd-mon-yyyy')) value
from list_dates
group by id ,trunc(to_date(dates,'dd-mon-yyyy'),'mm')
)
select id,sum(value) val from step1
group by id;
I have an interactive report, which using Pivot, but I was trying to custom sort the report by row column of pivot "Age Group", but I couldn't.
I need this report in such this sorting:
How I can do that?
Thanks
Looks like you'd want to sort by the first digit(s), and then by age_group itself (as a string). Something like this (sample data in lines #1 - 10; query you might be interested in begins at line #11):
SQL> with test (age_group) as
2 (select 'wahine 45-64' from dual union all
3 select 'wahine 25-44' from dual union all
4 select 'rangtahi 15-24' from dual union all
5 select 'pepis 0_4' from dual union all
6 select 'pakeke 45-64' from dual union all
7 select 'pakeke 25-44' from dual union all
8 select 'N/A' from dual union all
9 select 'kuia 65+' from dual
10 )
11 select age_group
12 from test
13 order by to_number(regexp_substr(age_group, '\d+')), age_group;
AGE_GROUP
--------------
pepis 0_4
rangtahi 15-24
pakeke 25-44
wahine 25-44
pakeke 45-64
wahine 45-64
kuia 65+
N/A
8 rows selected.
SQL>
Imagine that you have a query and you want to only show that one who has ALL the values of a subquery. For example, we have the following table:
CREATE TABLE test
(
code VARCHAR2(4),
year VARCHAR2(4),
action VARCHAR2(50),
CONSTRAINT pk PRIMARY KEY (code, year)
);
And the following registers:
INSERT INTO test
VALUES ('1','2020','Departure');
INSERT INTO test
VALUES ('1','2021','Arrival');
INSERT INTO test
VALUES ('2','2020','Departure');
Imagine that a subquery returns me the following values:
('Departure','Arrival')
So I want to make a query that returns me only those codes and years which match both of the values that have been returned at the subquery. Looking at the registers, it should return only return ('1','2020') and ('1','2021') because they are the only ones whose actions are 'Arrival' and 'Departure'. How could I do it?
With a little bit expanded sample data, where CODEs 1 and 3 have both Arrival and Departure:
SQL> with test (code, year, action) as
2 (select 1, 2020, 'Departure' from dual union all
3 select 1, 2021, 'Arrival' from dual union all
4 select 2, 2020, 'Departure' from dual union all
5 --
6 select 3, 2018, 'Arrival' from dual union all
7 select 3, 2019, 'Departure' from dual
8 ),
9 subq as
10 (select distinct action,
11 count(distinct action) over () cnt_da
12 from test
13 )
14 select a.code, a.year
15 from test a join subq s on a.action = s.action
16 where s.cnt_da = (select count(distinct action)
17 from test b
18 where b.code = a.code
19 );
CODE YEAR
---------- ----------
1 2021
1 2020
3 2019
3 2018
SQL>
Yet another option, using the MINUS set operator:
SQL> with test (code, year, action) as
2 (select 1, 2020, 'Departure' from dual union all
3 select 1, 2021, 'Arrival' from dual union all
4 select 2, 2020, 'Departure' from dual union all
5 --
6 select 3, 2018, 'Arrival' from dual union all
7 select 3, 2019, 'Departure' from dual
8 ),
9 subq as
10 (select distinct action from test) --> this is your "subquery"
11 select code, year
12 from test a
13 where (select s.action from subq s
14 minus
15 select b.action from test b where b.code = a.code
16 ) is null;
CODE YEAR
---------- ----------
1 2020
1 2021
3 2018
3 2019
SQL>
Enclose your query inside a CTE like this:
with cte as (
<your query here>
)
select t.*
from test t
where
t.action in (select action from cte)
and
code in (
select code
from test
where action in (select action from cte)
group by code
having count(distinct action) = (select count(*) from cte)
)
The subquery of IN returns all the codes that contain all the actions that your query returns.
See the demo.
Results:
> CODE | YEAR | ACTION
> :--- | :--- | :--------
> 1 | 2021 | Arrival
> 1 | 2020 | Departure
As a general solution, I'd look for an opportunity to use HAVING count(*) = #. Roughly,
SELECT code FROM table WHERE action in (SUBSELECT) GROUP BY code HAVING count(*) = (SELECT count(*) from SUBSELECT)
Of course if you can have multiple Arrival for a single code, you have to include a DISTINCT as well. Roughly,
SELECT code FROM (SELECT code, distinct(action) FROM table) WHERE action in...
I worry about the performance of a query like this, but you'd have to check it in situ since most database engines have the ability to transform complex queries like this for efficiency.
===
I think a JOIN will also work if you're sure that there's only one action of each type per code. For example.
SELECT t.code FROM SUBQUERY as s RIGHT JOIN table as t on t.action = s.action GROUP by t.code HAVING count(*) = (SELECT COUNT(*) FROM SUBQUERY)
(and yes in either case, you should use CTE features like #forpas suggests if you're going to inline the subquery to avoid repeatedly executing it).
i have below data.
table A
id
1
2
3
table B
id name data1 data2 datetime
1 cash 12345.00 12/12/2012 11:10:12
1 quantity 222.12 14/12/2012 11:10:12
1 date 20/12/2012 12/12/2012 11:10:12
1 date 19/12/2012 13/12/2012 11:10:12
1 date 13/12/2012 14/12/2012 11:10:12
1 quantity 330.10 17/12/2012 11:10:12
I want to retrieve data in one row like below:
tableA.id tableB.cash tableB.date tableB.quantity
1 12345.00 13/12/2012 330.10
I want to retrieve based on max(datetime).
The data model appears to be insane-- it makes no sense to join an ORDER_ID to a CUSTOMER_ID. It makes no sense to store dates in a VARCHAR2 column. It makes no sense to have no relationship between a CUSTOMER and an ORDER. It makes no sense to have two rows in the ORDER table with the same ORDER_ID. ORDER is also a reserved word so you cannot use that as a table name. My best guess is that you want something like
select *
from customer c
join (select order_id,
rank() over (partition by order_id
order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
from order) o on (c.customer_id=o.order_id)
where o.rnk = 1
If that is not what you want, please (as I asked a few times in the comments) post the expected output.
These are the results I get with my query and your sample data (fixing the name of the ORDER table so that it is actually valid)
SQL> ed
Wrote file afiedt.buf
1 with orders as (
2 select 1 order_id, 'iphone' order_name, '20121201 12:20:23' order_time from dual union all
3 select 1, 'iphone', '20121201 12:22:23' from dual union all
4 select 2, 'nokia', '20110101 13:20:20' from dual ),
5 customer as (
6 select 1 customer_id, 'paul' customer_name from dual union all
7 select 2, 'stuart' from dual union all
8 select 3, 'mike' from dual
9 )
10 select *
11 from customer c
12 join (select order_id,
13 rank() over (partition by order_id
14 order by to_date( order_time, 'YYYYMMDD HH24:MI:SS' ) desc ) rnk
15 from orders) o on (c.customer_id=o.order_id)
16* where o.rnk = 1
SQL> /
CUSTOMER_ID CUSTOM ORDER_ID RNK
----------- ------ ---------- ----------
1 paul 1 1
2 stuart 2 1
Try something like
SELECT *
FROM CUSTOMER c
INNER JOIN ORDER o
ON (o.CUSTOMER_ID = c.CUSTOMER_ID)
WHERE TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS') =
(SELECT MAX(TO_DATE(o.ORDER_TIME, 'YYYYMMDD HH24:MI:SS')) FROM ORDER)
Share and enjoy.