Grouping_sets getting different output formats when group by changed - oracle

I have the following test CASE which is working fine.
When I try to group by customer_id, TO_CHAR (p.purchase_date, 'IYYY"W"IW') I'm not getting the same format as the working query. As a workaround I could limit the query to 1 customer_id each time with a where clause but I really don't want to do that.
Can someone please tell me what the problem is and how to rectify the issue.
ALTER SESSION SET NLS_TIMESTAMP_FORMAT = 'DD-MON-YYYY HH24:MI:SS.FF';
ALTER SESSION SET NLS_DATE_FORMAT = 'DD-MON-YYYY HH24:MI:SS';
CREATE TABLE customers
(CUSTOMER_ID, FIRST_NAME, LAST_NAME) AS
SELECT 1, 'Faith', 'Mazzarone' FROM DUAL UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM DUAL UNION ALL
SELECT 3, 'Micheal', 'Palmice' FROM DUAL UNION ALL
SELECT 4, 'Jerry', 'Torchiano' FROM DUAL;
CREATE TABLE items
(PRODUCT_ID, PRODUCT_NAME, PRICE) AS
SELECT 100, 'Black Shoes', 79.99 FROM DUAL UNION ALL
SELECT 101, 'Brown Pants', 111.99 FROM DUAL UNION ALL
SELECT 102, 'White Shirt', 10.99 FROM DUAL;
CREATE TABLE purchases(
PURCHASE_ID NUMBER GENERATED BY DEFAULT AS IDENTITY (START WITH 1) NOT NULL,
CUSTOMER_ID NUMBER,
PRODUCT_ID NUMBER,
QUANTITY NUMBER,
PURCHASE_DATE TIMESTAMP
);
INSERT INTO purchases
(CUSTOMER_ID, PRODUCT_ID, QUANTITY, PURCHASE_DATE)
SELECT 1, 101, 3, TIMESTAMP'2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 1, 100, 1, TIMESTAMP '2022-10-12 19:04:18' FROM DUAL UNION ALL
SELECT 2, 101,1, TIMESTAMP '2022-10-11 09:54:48' FROM DUAL UNION ALL
SELECT 2, 101, 3, TIMESTAMP '2022-10-17 19:34:58' FROM DUAL UNION ALL
SELECT 2, 102, 3,TIMESTAMP '2022-12-06 11:41:25' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual CONNECT BY LEVEL <= 6 UNION ALL
SELECT 2, 102, 3,TIMESTAMP '2022-12-26 11:41:25' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual CONNECT BY LEVEL <= 6 UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-12-21 09:54:48' FROM DUAL UNION ALL
SELECT 3, 102,1, TIMESTAMP '2022-12-27 19:04:18' FROM DUAL UNION ALL
SELECT 3, 102, 4,TIMESTAMP '2022-12-22 21:44:35' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual
CONNECT BY LEVEL <= 15 UNION ALL
SELECT 3, 101,1, TIMESTAMP '2022-12-11 09:54:48' FROM DUAL UNION ALL
SELECT 3, 102,1, TIMESTAMP '2022-12-17 19:04:18' FROM DUAL UNION ALL
SELECT 3, 102, 4,TIMESTAMP '2022-12-12 21:44:35' + NUMTODSINTERVAL ( LEVEL * 2, 'DAY') FROM dual
CONNECT BY LEVEL <= 5;
ALTER TABLE customers
ADD CONSTRAINT customers_pk PRIMARY KEY (customer_id);
ALTER TABLE items
ADD CONSTRAINT items_pk PRIMARY KEY (product_id);
ALTER TABLE purchases
ADD CONSTRAINT purchases_pk PRIMARY KEY (purchase_id);
ALTER TABLE purchases ADD CONSTRAINT customers_fk FOREIGN KEY (customer_id) REFERENCES customers(customer_id);
ALTER TABLE purchases ADD CONSTRAINT items_fk FOREIGN KEY (PRODUCT_ID) REFERENCES items(product_id);
/* works fine */
SELECT TO_CHAR (p.purchase_date, 'IYYY"W"IW') AS year_week
, p.customer_id
, c.first_name
, c.last_name
, SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS ( (TO_CHAR (p.purchase_date, 'IYYY"W"IW'), p.customer_id, c.first_name, c.last_name)
, (TO_CHAR (p.purchase_date, 'IYYY"W"IW'))
, ()
)
ORDER BY TO_CHAR (p.purchase_date, 'IYYY"W"IW'), p.customer_id;
YEAR_WEEK
CUSTOMER_ID
FIRST_NAME
LAST_NAME
TOTAL_AMT
2022W41
1
Faith
Mazzarone
415.96
2022W41
2
Lisa
Saladino
111.99
2022W41
-
-
-
527.95
2022W42
2
Lisa
Saladino
335.97
2022W42
-
-
-
335.97
2022W49
2
Lisa
Saladino
65.94
2022W49
3
Micheal
Palmice
111.99
2022W49
-
-
-
177.93
2022W50
2
Lisa
Saladino
131.88
2022W50
3
Micheal
Palmice
142.87
2022W50
-
-
-
274.75
2022W51
3
Micheal
Palmice
243.87
2022W51
-
-
-
243.87
2022W52
2
Lisa
Saladino
98.91
2022W52
3
Micheal
Palmice
186.83
2022W52
-
-
-
285.74
2023W01
2
Lisa
Saladino
98.91
2023W01
3
Micheal
Palmice
131.88
2023W01
-
-
-
230.79
2023W02
3
Micheal
Palmice
175.84
2023W02
-
-
-
175.84
2023W03
3
Micheal
Palmice
131.88
2023W03
-
-
-
131.88
-
-
-
-
2384.72
/* unexpected output */
SELECT p.customer_id,
c.first_name,
c.last_name,
TO_CHAR (p.purchase_date, 'IYYY"W"IW') AS year_week,
SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS (
(p.customer_id, c.first_name, c.last_name),
(TO_CHAR (p.purchase_date, 'IYYY"W"IW')),
(p.customer_id, c.first_name, c.last_name)
, ()
)
ORDER BY p.customer_id,
TO_CHAR (p.purchase_date, 'IYYY"W"IW');

SELECT p.customer_id,
c.first_name,
c.last_name,
TO_CHAR (p.purchase_date, 'IYYY"W"IW') AS year_week,
SUM (p.quantity * i.price) AS total_amt
FROM purchases p
JOIN customers c ON p.customer_id = c.customer_id
JOIN items i ON p.product_id = i.product_id
GROUP BY GROUPING SETS (
(p.customer_id, c.first_name, c.last_name, (TO_CHAR (p.purchase_date, 'IYYY"W"IW')))
, p.customer_id
, ()
)
ORDER BY p.customer_id,
TO_CHAR (p.purchase_date, 'IYYY"W"IW');
CUSTOMER_ID FIRST_NAME LAST_NAME YEAR_WEEK TOTAL_AMT
1 Faith Mazzarone 2022W41 415.96
1 - - - 415.96
2 Lisa Saladino 2022W41 111.99
2 Lisa Saladino 2022W42 335.97
2 Lisa Saladino 2022W49 65.94
2 Lisa Saladino 2022W50 131.88
2 Lisa Saladino 2022W52 98.91
2 Lisa Saladino 2023W01 98.91
2 - - - 843.6
3 Micheal Palmice 2022W49 111.99
3 Micheal Palmice 2022W50 142.87
3 Micheal Palmice 2022W51 243.87
3 Micheal Palmice 2022W52 186.83
3 Micheal Palmice 2023W01 131.88
3 Micheal Palmice 2023W02 175.84
3 Micheal Palmice 2023W03 131.88
3 - - - 1125.16
- - - - 2384.72

Related

Timestamp to_char subquery

I have the following query but I need a particular format (HH24,MI,SS) for the results that I get in column "elapsed". Can I do that with a subquery?
SELECT
trunc(dstamp) "DATE",
to_char(dstamp, 'HH24:MI:SS') "Time"
, user_id
, dstamp - lag(dstamp) OVER (
PARTITION BY user_id ORDER BY dstamp
) AS elapsed
, COUNT(CODE) "Lines_Picked"
, ROUND(sum(update_qty / cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer))) "CASES_PICKED"
FROM v_inventory_transaction
WHERE client_id = 'USKIDS2CA'
AND code = 'Pick'
AND list_id IS NOT NULL
AND STATION_ID LIKE 'R%'
AND reference_id NOT LIKE '%-FK%'
AND trunc(dstamp) = to_date('03/23/2022','mm/dd/yyyy')
GROUP BY
user_id,
dstamp
, cast(substr(sku_id, instr(sku_id, '-', 1, 1) + 1
, instr(sku_id, '-', 1, 2) - 1 - instr(sku_id, '-', 1, 1)) as integer)
What is "timestamp" from the title? Which datatype is it? DATE or TIMESTAMP?
If it is TIMESTAMP, extract helps a lot:
SQL> with temp (user_id, dstamp) as
2 (select 1, to_timestamp('27.03.2022 08:08', 'dd.mm.yyyy hh24:mi') from dual union all
3 select 1, to_timestamp('27.03.2022 05:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 1, to_timestamp('27.03.2022 02:28', 'dd.mm.yyyy hh24:mi') from dual
5 )
6 select user_id,
7 dstamp,
8 --
9 to_char(extract (hour from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') ||':'||
10 to_char(extract (minute from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') ||':'||
11 to_char(extract (second from dstamp - lag(dstamp) over (partition by user_id order by dstamp)), 'FM00') result
12 from temp
13 order by user_id, dstamp;
USER_ID DSTAMP RESULT
---------- ----------------------------------- -----------
1 27-MAR-22 02.28.00.000000000 AM ::
1 27-MAR-22 05.30.00.000000000 AM 03:02:00
1 27-MAR-22 08.08.00.000000000 AM 02:38:00
SQL>
If it is DATE, then ELAPSED currently represents number of days between dstamp and its previous row's value per user_id. As it seems that that number is always less than 1 day (as you want result in hh24:mi:ss format), a simple option is to apply combination of
to_char(to_date(elapsed, 'sssss'), 'hh24:mi:ss')
and get the result. Step-by-step (so that you'd see which result you get for each step):
SQL> with temp (user_id, dstamp) as
2 (select 1, to_date('27.03.2022 08:08', 'dd.mm.yyyy hh24:mi') from dual union all
3 select 1, to_date('27.03.2022 05:30', 'dd.mm.yyyy hh24:mi') from dual union all
4 select 1, to_date('27.03.2022 02:28', 'dd.mm.yyyy hh24:mi') from dual
5 )
6 select user_id,
7 dstamp,
8 --
9 dstamp - lag(dstamp) over (partition by user_id order by dstamp) as elapsed_days,
10 --
11 round((dstamp - lag(dstamp) over (partition by user_id order by dstamp)) *
12 24 * 60 * 60) elapsed_seconds,
13 --
14 to_char(to_date(round((dstamp - lag(dstamp) over (partition by user_id
15 order by dstamp)) *
16 24 * 60 * 60), 'sssss'),
17 'hh24:mi:ss') result
18 from temp
19 order by user_id, dstamp;
USER_ID DSTAMP ELAPSED_DAYS ELAPSED_SECONDS RESULT
---------- ------------------- ------------ --------------- --------
1 27.03.2022 02:28:00
1 27.03.2022 05:30:00 .126388889 10920 03:02:00
1 27.03.2022 08:08:00 .109722222 9480 02:38:00
SQL>
However, if elapsed can be larger than 1 day so that it exceeds number of seconds in a day (that's 24 * 60 * 60 = 86400), that won't work so you'll have to actually calculate the result. However, query then becomes really ugly so you'd rather use another CTE (or a subquery) to first extract elapsed and work with it, instead of using dstamp - lag(...) all the time.
Therefore, I hope it is the timestamp after all.

How can we get multiple rows data as single row in oracle

In image I have given table structure and sample data and I need output result as mentioned
With sample data you provided (lines #1 - 8), this returns desired result. Will it work for all other cases, I have no idea as the question lacks in quite a lot of information so YMMV.
SQL> with employee (id, name, type, visit_date) as
2 (select 1, 'Mohan', '01', date '2010-09-09' from dual union all
3 select 1, 'Mohan', '02', date '2010-09-10' from dual union all
4 --
5 select 1, 'Gani' , '01', date '2010-09-01' from dual union all
6 select 1, 'Gani' , '01', date '2010-09-02' from dual union all
7 select 1, 'Gani' , '01', date '2010-09-03' from dual
8 ),
9 --
10 type1 as
11 (select id, name, visit_date
12 from employee
13 where type = '01'
14 ),
15 type2 as
16 (select id, name, visit_date
17 from employee
18 where type = '02'
19 )
20 select
21 a.id,
22 a.name,
23 a.visit_date type1date,
24 b.visit_date type2date
25 from type1 a left join type2 b on a.id = b.id and a.name = b.name
26 order by a.id, a.name desc, a.visit_date;
ID NAME TYPE1DATE TYPE2DATE
---------- ----- ---------- ----------
1 Mohan 09/09/2010 10/09/2010
1 Gani 01/09/2010
1 Gani 02/09/2010
1 Gani 03/09/2010
SQL>

oracle- JOIN 2 tables with 2 ID's in common

table "team1" :
id country
1 India
2 Pakistan
3 srilanka
4 England
table "team2" :
id name name2
1 2 4
2 1 3
i have to combine two tables
another table when retrieve the data that time in place of 2 , 4 Pakistan,England
It is about the self join of team1 table (lines #15 and 16):
SQL> with
2 team1 (id, country) as
3 (select 1, 'India' from dual union all
4 select 2, 'Pakistan' from dual union all
5 select 3, 'Sri Lanka' from dual union all
6 select 4, 'England' from dual
7 ),
8 team2 (id, name, name2) as
9 (select 1, 2, 4 from dual union all
10 select 2, 1, 3 from dual
11 )
12 select b.id,
13 t1.country,
14 t2.country
15 from team2 b join team1 t1 on t1.id = b.name
16 join team1 t2 on t2.id = b.name2
17 order by b.id;
ID COUNTRY COUNTRY
---------- --------- ---------
1 Pakistan England
2 India Sri Lanka
SQL>
Just showing another way of writing the same query with aggregate functions and grouping.
with
team1 (id, country) as
(select 1, 'India' from dual union all
select 2, 'Pakistan' from dual union all
select 3, 'Sri Lanka' from dual union all
select 4, 'England' from dual
),
team2 (id, name, name2) as
(select 1, 2, 4 from dual union all
select 2, 1, 3 from dual
)
SELECT
T2.ID,
MAX(CASE
WHEN T2.NAME = T1.ID THEN T1.COUNTRY
END) AS TEAM1,
MAX(CASE
WHEN T2.NAME2 = T1.ID THEN T1.COUNTRY
END) AS TEAM2
FROM
TEAM2 T2
JOIN TEAM1 T1 ON T1.ID IN (
T2.NAME,
T2.NAME2
)
GROUP BY
T2.ID
ORDER BY
T2.ID;
Output:
ID TEAM1 TEAM2
---------- --------- ---------
1 Pakistan England
2 India Sri Lanka
Cheers!!

Trouble with Oracle Connect By and Date Ranges

Let's say I have a table with data ranges
create table ranges (id number, date_from date, date_to date);
insert into ranges values (1, to_date('01.01.2017', 'dd.mm.rrrr'), to_date('03.01.2017', 'dd.mm.rrrr'));
insert into ranges values (2, to_date('05.02.2017', 'dd.mm.rrrr'), to_date('08.02.2017', 'dd.mm.rrrr'));
and my output should by one row for every date in these ranges
id | the_date
----------------
1 | 01.01.2017
1 | 02.01.2017
1 | 03.01.2017
2 | 05.02.2017
2 | 06.02.2017
2 | 07.02.2017
2 | 08.02.2017
But connect by gives me ORA-01436 Connect by Loop
SELECT connect_by_root(id), Trunc(date_from, 'dd') + LEVEL - 1 AS the_date
FROM ranges
CONNECT BY PRIOR id = id AND Trunc(date_from, 'dd') + LEVEL - 1 <= Trunc(date_to, 'dd')
ORDER BY id, the_date
What's wrong?
You can add a call to a non-deterministic function, e.g.
AND PRIOR dbms_random.value IS NOT NULL
So it becomes:
SELECT connect_by_root(id), Trunc(date_from, 'dd') + LEVEL - 1 AS the_date
FROM ranges
CONNECT BY PRIOR id = id
AND PRIOR dbms_random.value IS NOT NULL
AND Trunc(date_from, 'dd') + LEVEL - 1 <= Trunc(date_to, 'dd')
ORDER BY id, the_date;
CONNECT_BY_ROOT(ID) THE_DATE
------------------- ---------
1 01-JAN-17
1 02-JAN-17
1 03-JAN-17
2 05-FEB-17
2 06-FEB-17
2 07-FEB-17
2 08-FEB-17
7 rows selected.
There's an explanation of why it is necessary in this Oracle Community post; that uses sys_guid() instead of dbms_random.value, but the principle is the same.
If you're on 11gR2 or higher you could use recursive subquery factoring instead:
WITH rcte (root_id, the_date, date_to) AS (
SELECT id, date_from, date_to
FROM ranges
UNION ALL
SELECT root_id, the_date + 1, date_to
FROM rcte
WHERE the_date < date_to
)
SELECT root_id, the_date
FROM rcte
ORDER BY root_id, the_date;
ROOT_ID THE_DATE
---------- ---------
1 01-JAN-17
1 02-JAN-17
1 03-JAN-17
2 05-FEB-17
2 06-FEB-17
2 07-FEB-17
2 08-FEB-17
7 rows selected.

Subselect in oracle

I'm struggling with a subselect in oracle. I want to include the latest price from another table.
Here is my current attempt:
SELECT tab1.*
(select price from
old_prices
where part_no=tab1.article_no
order by valid_from desc) as old_price,
FROM articles tab1
order by article_no
The sub select returns several rows which I think is the problem. But I do not know how to limit the number of rows in Oracle.
SQL> create table articles (article_no,name)
2 as
3 select 1, 'PEN' from dual union all
4 select 2, 'PAPER' from dual
5 /
Table created.
SQL> create table old_prices (part_no,valid_from,price)
2 as
3 select 1, date '2008-01-01', 10 from dual union all
4 select 1, date '2009-01-01', 11 from dual union all
5 select 1, date '2010-01-01', 12 from dual union all
6 select 1, date '2011-01-01', 13 from dual union all
7 select 2, date '2010-01-01', 89.95 from dual union all
8 select 2, date '2011-01-01', 94.95 from dual union all
9 select 2, date '2012-01-01', 99.95 from dual
10 /
Table created.
SQL> select a.article_no
2 , max(a.name) keep (dense_rank last order by p.valid_from) name
3 , max(p.price) keep (dense_rank last order by p.valid_from) price
4 from articles a
5 , old_prices p
6 where a.article_no = p.part_no
7 group by a.article_no
8 /
ARTICLE_NO NAME PRICE
---------- ----- ----------
1 PEN 13
2 PAPER 99.95
2 rows selected.
Regards,
Rob.
If it's the latest price you're after:
SELECT tab1.*, p.price old_price
FROM articles tab1
, old_prices p
where p.part_no = tab1.article_no
and valid_from = (
select MAX(valid_from)
from old_prices p2
where p2.part_no = p.part_no
)
order by article_no
I want to include the lastest price
I presume you mean latest.
OK, well that's a bit of a problem to start with, there are several ways of doing this:
SELECT o.price
FROM old_prices o
WHERE o.part_no=&part_no
AND o.ondate=(SELECT MAX(o2.ondate)
FROM old_prices o2
WHERE o2.part_no=&part_no);
Seems the most obvious choice but its rather innefficient.
You could try....
SELECT ilv.price
FROM (SELECT o.price
FROM old_price o
WHERE o.part_no=&part_no
ORDER BY ondate DESC) ilv
WHERE rownum=1;
Or....
SELECT TO_NUMBER(
SUBSTR(
MAX(TO_CHAR(o.ondate, 'YYYYMMDDHH24MISS') || price)
, 15)
) as latest_price
FROM old_price o
WHERE o.part_no=&part_no;
To limit rows use ROWNUM < 10. This is a pseudocolumn returning the row number of each line of your resultset.
EDIT:
You need to add another subselect query (hope this is the right place for your need)
SELECT tab1.*
select (
(select price from old_prices
where part_no=tab1.article_no order by valid_from desc
) as x
where rownum = 1
) as old_price
FROM articles tab1
order by article_no
SELECT tab1.*
(select
price
from (
SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
where rn =1
and tab1.article_no = P.part_no
) as old_price
FROM articles tab1
order by article_no
more efficient would be
SELECT
tab1.*
, P.price
FROM
articles tab1
, ( SELECT
part_no
, price
, row_number () over (partition by part_no order by valid_from desc ) rn
FROM
old_prices
) P
WHERE
P.part_no(+) = tab1.article_no
P.rn(+) = 1
;
with old_prices as(
select level * 15 price ,
mod (level ,5) part_no , --this is just to create a grouping type partno
(sysdate - level ) valid_from
from dual
connect by level < 100)
,
articles as(
select level ,
mod(level , 5 ) article_no ,
(sysdate + level) someOtherDateField
From dual
connect by level < 5
)
SELECT tab1.* ,
old_price.*
from articles tab1
left join
(
select price,
part_no ,
valid_from ,
rank() over(partition by part_no order by valid_from desc) rk
from old_prices
) old_price
on tab1.article_no = old_price.part_no
and old_price.rk = 1
order by article_no ;
Here's another way!
LEVEL ARTICLE_NO SOMEOTHERDATEFIELD PRICE PART_NO VALID_FROM RK
---------------------- ---------------------- ------------------------- ---------------------- ---------------------- ------------------------- ----------------------
1 1 25/05/11 07:30:54 15 1 23/05/11 07:30:54 1
2 2 26/05/11 07:30:54 30 2 22/05/11 07:30:54 1
3 3 27/05/11 07:30:54 45 3 21/05/11 07:30:54 1
4 4 28/05/11 07:30:54 60 4 20/05/11 07:30:54 1

Resources