Select statement with a dynamic group by clause - oracle

How possible to group records conditionally? For example I have records with FLOW_NUMBER column, values 1, 2, 3. When FLOW_NUMBER value is 1, I want to group this records, but when FLOW_NUMBER is 2 or 3, I don't want to group.
SELECT
LISTAGG(DISTINCT PRODORD, ',') PRODORD,
SUM(QTY) AS QTY
FROM (
SELECT
1 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
)
GROUP BY FLOW_NUMBER -- bad group by clause
There is example photo of group by which I need.

SELECT SUBSTR(flow,1,INSTR(flow,'/')-1) AS flow, LISTAGG(prodorder,',') WITHIN GROUP(ORDER BY prodorder) AS prodorders, SUM(qty) AS qty
FROM (
SELECT CASE WHEN flow=1 THEN '1/' ELSE to_CHAR(flow)||'/'||ROWNUM END AS flow,
prodorder, qty FROM DATA
)
GROUP BY flow;

I don't think there is anyway to achieve it except using 2 query combined with UNION ALL clause -
SELECT FLOW_NUMBER, LISTAGG(DISTINCT PRODORD, ',') PRODORD,
SUM(QTY) AS QTY
FROM temp
WHERE FLOW_NUMBER = 1
GROUP BY FLOW_NUMBER
UNION ALL
SELECT FLOW_NUMBER, PRODORD,
QTY
FROM temp
WHERE FLOW_NUMBER <> 1;
Demo.

You can try this:
select
substr(fn,1,decode(instr(fn,','),0,1000,instr(fn,','))-1) flow_number,
prodord, qty
from (SELECT
case when FLOW_NUMBER=1 then to_char(flow_number) else to_char(flow_number)||','||rownum end fn,
LISTAGG(DISTINCT PRODORD, ',') PRODORD,
SUM(QTY) AS QTY
FROM (
SELECT
1 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
)
GROUP BY case when FLOW_NUMBER=1 then to_char(flow_number) else to_char(flow_number)||','||rownum end);
I used rownum here, but you can probably replace it by other things (e.g. rowid, if the data is from a table, just be sure to have a unique value)

Related

IDENTITY full-time and part-time students

have a query and test CASE that shows the number of full-time and part-time students. A full-time student is enrolled in at least 4 courses. A part-time student is enrolled in at least 1 course, but no more than 3.
Although the query appears to work it seems a bit verbose. I was wondering if there is a more succinct way to rewrite the query. In addition, I can would like to display the students first/last names with each row that meets the criteria
Perhaps with something like this?
, LISTAGG(
NVL2(s.student_id, s.last_name || ', ' || s.first_name, NULL),
'; '
) WITHIN GROUP (ORDER BY s.last_name, s.first_name) AS students
Below are my tables, data and query I would like to shorten if possible. Thanks to all who answer and for your expertise.
CREATE TABLE students(student_id, first_name, last_name) AS
SELECT 1, 'Faith', 'Aaron' FROM dual UNION ALL
SELECT 2, 'Lisa', 'Saladino' FROM dual UNION ALL
SELECT 3, 'Leslee', 'Altman' FROM dual UNION ALL
SELECT 4, 'Patty', 'Kern' FROM dual UNION ALL
SELECT 5, 'Beth', 'Cooper' FROM dual UNION ALL
SELECT 95, 'Zak', 'Despart' FROM dual UNION ALL
SELECT 96, 'Owen', 'Balbert' FROM dual UNION ALL
SELECT 97, 'Jack', 'Aprile' FROM dual UNION ALL
SELECT 98, 'Nicole', 'Kramer' FROM dual UNION ALL
SELECT 99, 'Jill', 'Coralnick' FROM dual;
CREATE TABLE student_courses (student_id,course_id) AS
SELECT 1, 1 FROM dual UNION ALL
SELECT 2, 1 FROM dual UNION ALL
SELECT 3, 1 FROM dual UNION ALL
SELECT 4, 1 FROM dual UNION ALL
SELECT 5, 1 FROM dual UNION ALL
SELECT 1, 2 FROM dual UNION ALL
SELECT 2, 2 FROM dual UNION ALL
SELECT 3, 2 FROM dual UNION ALL
SELECT 4, 2 FROM dual UNION ALL
SELECT 5, 2 FROM dual UNION ALL
SELECT 1, 3 FROM dual UNION ALL
SELECT 2, 3 FROM dual UNION ALL
SELECT 3, 3 FROM dual UNION ALL
SELECT 4, 3 FROM dual UNION ALL
SELECT 5, 3 FROM dual UNION ALL
SELECT 97, 1 FROM dual UNION ALL
SELECT 97, 3 FROM dual UNION ALL
SELECT 97, 5 FROM dual UNION ALL
SELECT 97, 6 FROM dual UNION ALL
SELECT 98, 3 FROM dual UNION ALL
SELECT 98, 4 FROM dual UNION ALL
SELECT 98, 5 FROM dual UNION ALL
SELECT 99, 2 FROM dual UNION ALL
SELECT 99, 4 FROM dual UNION ALL
SELECT 99, 5 FROM dual UNION ALL
SELECT 99, 6 FROM dual;
WITH enrolled_student_course_counts AS (
SELECT
s.student_id
, s.first_name
, s.last_name
, COUNT(sc.course_id) AS course_count
FROM students s
LEFT JOIN student_courses sc
ON s.student_id = sc.student_id
GROUP BY
s.student_id
, s.first_name
, s.last_name
HAVING COUNT(sc.course_id) > 0
)
, student_enrollment_statuses AS (
SELECT
student_id
, first_name
, last_name
, CASE WHEN course_count >= 4 THEN 'full-time'
WHEN course_count BETWEEN 1 AND 3 THEN 'part-time'
END AS student_enrollment_status
FROM enrolled_student_course_counts
)
SELECT
UPPER(student_enrollment_status) AS student_enrollment_status
, COUNT(student_enrollment_status) AS student_enrollment_status_count
FROM student_enrollment_statuses
GROUP BY student_enrollment_status;
As you only need the numbers (and not any other data), shorten the query so that it searches only the student_courses table:
SQL> with temp as
2 (select student_id,
3 count(course_id) cnt
4 from student_courses
5 group by student_id
6 )
7 select
8 sum(case when cnt < 4 then 1 else 0 end) part_time,
9 sum(case when cnt >= 4 then 1 else 0 end) full_time
10 from temp;
PART_TIME FULL_TIME
---------- ----------
6 2
SQL>

Duplicated rows numbering

I need to number the rows so that the row number with the same ID is the same. For example:
Oracle database. Any ideas?
Use the DENSE_RANK analytic function:
SELECT DENSE_RANK() OVER (ORDER BY id) AS row_number,
id
FROM your_table
Which, for the sample data:
CREATE TABLE your_table ( id ) AS
SELECT 86325 FROM DUAL UNION ALL
SELECT 86325 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86326 FROM DUAL UNION ALL
SELECT 86352 FROM DUAL UNION ALL
SELECT 86353 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL UNION ALL
SELECT 86354 FROM DUAL;
Outputs:
ROW_NUMBER
ID
1
86325
1
86325
2
86326
2
86326
3
86352
4
86353
5
86354
5
86354
db<>fiddle here

Grouping and aggregation based on a specific condition

My result set from a query looks like this
trackingnumber type price
------------------------------------------
12799467 AVRM 674.0536
12799467 AVRM 860.7415
12799467 PRICESTD 200.00
12799468 PRICESTD 590.85
12799469 PRICESTD 800
12799470 PRICESTD 640
12799471 PRICESTD 160
12799472 PRICESTD 2080
12799473 PRICESTD 354.3779
I want to group this by the trackingnumber and in cases where the count of grouped result set is greater than 1 return the SUM of all the price which has type as AVRM else return the individual price as it is. If the count
is more that zero and the none of them has type AVRM then it's total price would be null
The expected result would be this
trackingnumber Total Price
-----------------------------------------
12799467 1534.7951 --sum of price excluding 200
12799468 590.85
12799469 800
12799470 640
12799471 160
12799472 2080
12799473 354.3779
I couldn't think of a way to get this done except for grouping by trackingnumber and checking for type by using case statement in the select part but that I believe would not work since we do not group by type
I'm not sure if this can be achieved using a single query.
Yes, it can be done in a single query.
with sample_data ( tracking_Number, "TYPE", price ) as
(
SELECT 12799467,'AVRM',674.0536 FROM DUAL UNION ALL
SELECT 12799467,'AVRM',860.7415 FROM DUAL UNION ALL
SELECT 12799467,'PRICESTD',200.00 FROM DUAL UNION ALL
SELECT 12799468,'PRICESTD',590.85 FROM DUAL UNION ALL
SELECT 12799469,'PRICESTD',800 FROM DUAL UNION ALL
SELECT 12799470,'PRICESTD',640 FROM DUAL UNION ALL
SELECT 12799471,'PRICESTD',160 FROM DUAL UNION ALL
SELECT 12799472,'PRICESTD',2080 FROM DUAL UNION ALL
SELECT 12799473,'PRICESTD',354.3779 FROM DUAL )
SELECT tracking_number,
case when count(*) > 1 THEN
sum(decode("TYPE",'AVRM',price,null)) ELSE
sum(price) END price
from sample_data
group by tracking_number
order by tracking_Number;
For example next solution. I add condition to exlude rows with type not equla 'AVRM' if rows with 'AVRM' exests
with s (trackingnumber ,type ,price)
as (
select 12799467,'AVRM',674.0536 from dual union all
select 12799467 ,'AVRM', 860.7415 from dual union all
select 12799467 ,'PRICESTD', 200.00 from dual union all
select 12799468 ,'PRICESTD', 590.85 from dual union all
select 12799469 ,'PRICESTD', 800 from dual union all
select 12799470 ,'PRICESTD', 640 from dual union all
select 12799471 ,'PRICESTD', 160 from dual union all
select 12799472 ,'PRICESTD', 2080 from dual union all
select 12799473 ,'PRICESTD', 354.3779 from dual )
select trackingnumber,
sum(price)
from (select s.*,rownum as rn from s
where not exists (select null
from s subs
where s.trackingnumber = subs.trackingnumber
and s.type != 'AVRM'
and subs.type = 'AVRM')
)
group by trackingnumber,
case when type = 'AVRM' then 0 else rn end;

Oracle Pivot Column in group query

I have the following working query:
WITH pivot_data AS (
select PSGROUP,
PSCOLUMN as PSCOLUMN
FROM LOG_PS_STATUS
)
SELECT *
FROM pivot_data
PIVOT (
MAX(NULL) --<-- pivot_clause
FOR PSCOLUMN--<-- pivot_for_clause
IN (&PS_COLUMNS.) --<-- pivot_in_clause
);
It shows results as expected:
Values:
PSGroup PSColumn
A 1
A 2
A 3
B 1
B 2
B 3
C 3
Result is giving like:
PSGroup(Column vertically) PSColoumn(Horizontally)
1 2 3
A
B
C
Now I want to make PSGroup column as group of PSColumn and output should be like:
A
1 2 3
B
1 2 3
C
3
You can use listagg:
WITH pivot_data(PSGroup,PSColumn) AS (
select 'A', 1 FROM dual UNION all
select 'A', 2 FROM dual UNION all
select 'A', 3 FROM dual UNION all
select 'B', 1 FROM dual UNION all
select 'B', 2 FROM dual UNION all
select 'B', 3 FROM dual UNION all
select 'C', 3 FROM DUAL),
res(PSGroup,PSColumns) as(
SELECT PSGroup, LISTAGG(PSColumn, ' ') WITHIN GROUP (order by PSColumn)
FROM pivot_data
GROUP BY PSGroup)
select DECODE(PSColumns,NULL,PSGroup,NULL) AS PSGroup, PSColumns from(
select PSGroup, NULL as PSColumns from res
union all
select PSGroup, PSColumns from res)t
ORDER BY t.PSGroup, t.PSColumns NULLS first
Also note that LISTAGG(PSColumn, ' ') WITHIN GROUP (order by PSColumn) is limited to 4000 characters

Oracle 9.2 pivot distinct value

The pivot function is available from Oracle 11 and i will need similar result using Oracle 9.2.
The main argument is that i need to pivot some values with a distinct result in a table like this:
id col3
1 a
1 b
--
2 a
2 a
2 b
--
3 a
3 b
3 c
My result sould be something like this
id a b c
1 1 1 0
2 1 1 0
3 1 1 1
To create a "manual" pivot i'm using a case/when but I am not able to understand how to get distinct value.
Right now the query is this:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select t.id,
count(case when t.col1 = 'a' then 1 end) a,
count(case when t.col1 = 'b' then 1 end) b,
count(case when t.col1 = 'c' then 1 end) c
This produce correct value but obviously it just "count" the total a/b/c value and not the distinct.
thanks for the support
If I correctly understand your need, you can try something like the following; it aggregates by id and counts the distinct values of col3:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select id,
count(distinct decode (col1, 'a', id, null)) a,
count(distinct decode (col1, 'b', id, null)) b,
count(distinct decode (col1, 'c', id, null)) c
from t
group by id
Of course the query depends on the number of different values of col3, but this is the same problem than pivot.

Resources