Oracle- count the number rows based on a condition - oracle

I want to create a 'ticket' which counts the number of passes for each ID. When we have a gold pass on any of the ID, this means the pass is applied to all those in booking. So for this example, we want to count 5. For the other pass_codes, we want to simply count the number of passes and exclude those that are nulls. I have an expected output below.
Say I have this data:
Passes
ID | GuestID | Pass_code
----------------------------
100 | 001 | Bronze
100 | 002 | Bronze
101 | 103 | Gold
101 | 104 | NULL
101 | 105 | NULL
101 | 106 | NULL
101 | 107 | NULL
102 | 208 | Silver
103 | 209 | Steel
103 | 210 | Steel
103 | 211 | NULL
Passengers
ID | Passengers
-----------------
100 | 2
101 | 5
102 | 1
103 | 3
I want to count then create a ticket in the output of:
ID 100 | 2 pass (bronze)
ID 101 | 5 pass (because it is gold, we count all passengers)
ID 102 | 1 pass (silver)
ID 103 | 2 pass (steel) (2 passes rather than than 3 as we just want to count only the passes for steel, bronze silver)
I want to do something like this, but as a combined query.
DECLARE #ID = 101; -- i will want to pass in IDs
-- for gold, we want to count all passengers when the gold pass is on
SELECT pp.Passengers
FROM passes
JOIN Passengers pp ON p.ID = pp.ID
WHERE p.pass_code IN'%gold%'
AND PP.id = #id
-- for bronze, silver and steel
SELECT
count(p.ID)
FROM Passes
WHERE p.ID = #id
AND P.pass_code IN ('Bronze', 'silver', 'steel') -- Dont want to check based on NUlls as this may chnage to something else.
)
Any help or advice would be much appreciated.

Does this work for you?
with Passes as (
select 100 as id, 001 as guestid, 'Bronze' as passcode from dual union all
select 100 as id, 002 as guestid, 'Bronze' as passcode from dual union all
select 101 as id, 103 as guestid,'Gold' as passcode from dual union all
select 101 as id, 104 as guestid, NULL as passcode from dual union all
select 101 as id, 105 as guestid, NULL as passcode from dual union all
select 101 as id, 106 as guestid, NULL as passcode from dual union all
select 101 as id, 107 as guestid, NULL as passcode from dual union all
select 102 as id, 208 as guestid, 'Silver' as passcode from dual union all
select 103 as id, 209 as guestid, 'Steel' as passcode from dual union all
select 103 as id, 210 as guestid, 'Steel' as passcode from dual union all
select 103 as id, 211 as guestid, NULL as passcode from dual
)
SELECT
id,passcode,count(ID)
FROM Passes
where passcode is not null and passcode<>'Gold'
group by id,passcode
union all
SELECT
id,'Gold',count(ID)
FROM Passes
where id in
(
select id from Passes where passcode='Gold'
)
group by id
order by id
result:
100 Bronze 2
101 Gold 5
102 Silver 1
103 Steel 2

If I understand your question right the query should be like the following
** The table
create table test (ID number, GuestId number, Pass_code varchar2(10));
insert into test values(100,001,'Bronze');
insert into test values(100,002,'Bronze');
insert into test values(101,103,'Gold');
insert into test values(101,104,NULL);
insert into test values(101,105,NULL);
insert into test values(101,106,NULL);
insert into test values(101,107,NULL);
insert into test values(102,208,'Silver');
insert into test values(103,209,'Steel');
insert into test values(103,210,'Steel');
insert into test values(103,211,NULL);
commit;
SQL> select * from test order by 1,2;
ID GUESTID PASS_CODE
---------- ---------- ------------------------------
100 1 Bronze
100 2 Bronze
101 103 Gold
101 104
101 105
101 106
101 107
102 208 Silver
103 209 Steel
103 210 Steel
103 211
11 rows selected.
** The query
WITH PASSES AS (
SELECT T1.ID,T1.PASS_CODE, COUNT(T2.ID) QUANTITY FROM TEST T1, TEST T2
WHERE T1.PASS_CODE='Gold' AND T1.ID=T2.ID
GROUP BY T1.ID,T1.PASS_CODE
UNION ALL
SELECT ID,PASS_CODE, COUNT(*) QUANTITY FROM TEST
WHERE PASS_CODE IS NOT NULL AND
PASS_CODE != 'Gold'
GROUP BY ID,PASS_CODE)
SELECT ID, QUANTITY || ' (' || PASS_CODE || ')' RESULT FROM PASSES
ORDER BY ID;
** Result
ID RESULT
---------- --------------------
100 2 (Bronze)
101 5 (Gold)
102 1 (Silver)
103 2 (Steel)

Related

Tracking missing records between migrated tables in oracle

I have a table called TABLE1 as follow
ID | SP_NUMBER |CATEGORY
------------------------
1 101 A
2 101 B
3 101 C
4 102 A
5 102 B
6 103 A
7 103 C
suppose I migrated above table data to new table called TABLE2
ID | SP_NUMBER |CATEGORY
------------------------
1 101 A
2 101 C
3 102 A
4 102 B
5 103 C
Note that , after the migration TABLE2 missing some records. I want genarelise way to track those missing data
as a example I need to show
101 B
103 A
are not migrated.
Use MINUS
select sp_number,category FROM TABLE1 MINUS
SELECT sp_number,category from TABLE2;
Demo

PL-SQL - First record in a One-to-Many relation left join

I'm trying to join two tables with LEFT JOIN in Oracle. I need to include only the first record from the "right" joined table.
See the example below:
Table A
code | emp_no
101 | 11111
102 | 22222
103 | 33333
104 | 44444
105 | 55555
Table B
code | city | county
101 | City1 | Country1
101 | City2 | Country1
101 | City3 | Country1
102 | City4 | Country2
103 | City5 | Country3
Expected Output:
code | emp_no | city | county
101 | 11111 | City1 | Country1
102 | 22222 | City4 | Country2
103 | 33333 | City5 | Country3
104 | 44444 | NULL | NULL
105 | 55555 | NULL | NULL
I need to pick the first matched record from table B and ignore all other rows.
The query above suppose to works:
SELECT *
FROM TABLE_A a
LEFT JOIN TABLE_B b ON b.CODE = a.CODE
AND b.CODE =
(
SELECT CODE
FROM TABLE_B
WHERE ROWNUM = 1
)
But I'm getting the error:
ORA-01799: a column may not be outer-joined to a subquery
How can I do this?
Thanks
On Oracle 12c you can use OUTER APPLY and FETCH FIRST clauses:
SELECT *
FROM tableA a
OUTER APPLY (
SELECT * FROM tableB b
WHERE a.code = b.code
ORDER BY city, county
FETCH FIRST ROW ONLY
)
CODE EMP_NO CODE CITY COUNTY
---------- ---------- ---------- ----- --------
101 11111 101 City1 Country1
102 22222 102 City4 Country2
103 33333 103 City5 Country3
104 44444
105 55555
You can use the min() aggrenate function with the keep (dense_rank first ...) syntax to get the 'first' matching data from the outer-joined table:
select a.code, a.emp_no,
min(b.city) keep (dense_rank first order by city, county) as city,
min(b.county) keep (dense_rank first order by city, county) as county
from table_a a
left join table_b b on b.code = a.code
group by a.code, a.emp_no
order by a.code, a.emp_no;
CODE EMP_NO CITY COUNTY
---------- ---------- ----- --------
101 11111 City1 Country1
102 22222 City4 Country2
103 33333 City5 Country3
104 44444
105 55555
You have to define what 'first' means though - I've gone with order by city, county inside the keep clause, but you may have another column you haven't shown that should dictate the order.
(You can order by null to make it somewhat arbitrary, but that's not generally a good idea, not least as running the same query later could give different results for the same data.)
using row_number() function and get records where row_number() = 1
SELECT select a.code,
a.emp_no,
b.city,
b.county
FROM table_a a
left join (SELECT code,
city,
county,
row_number()
over (
PARTITION BY code
ORDER BY city, county ) rn
FROM table_b) b
ON b.code = a.code
WHERE rn = 1 OR rn IS NULL;
Note: It is still unclear from the question what actually this means.
first record from the "right" joined table

How do I get both the child and one upper level parent information by using oracle connect by prior?

I want to get both the child and one upper level parent information by using oracle connect by prior?
For example the folowing query retrieve child info and parent id,
SELECT last_name, employee_id, manager_id, LEVEL
FROM employees
START WITH employee_id = 100
CONNECT BY PRIOR employee_id = manager_id
but I want to get parent info also like
LAST_NAME EMPLOYEE_ID MANAGER_ID LEVEL MANAGER_NAME
------------------------- ----------- ---------- ----------------------------
King 100 1 ?
Cambrault 148 100 2 ?
Bates 172 148 3 ?
Bloom 169 148 3 .
Fox 170 148 3 .
How can I handle this problem, when I applied left join after selecting childs by connect by prior,The objects order is mixing.
You can refer to prior values in the select list:
SELECT last_name, employee_id, manager_id, LEVEL, prior last_name as manager_name
FROM employees
START WITH employee_id = 100
CONNECT BY PRIOR employee_id = manager_id;
LAST_NAME EMPLOYEE_ID MANAGER_ID LEVEL MANAGER_NAME
------------------------- ----------- ---------- ---------- -------------------------
King 100 1
Kochhar 101 100 2 King
Greenberg 108 101 3 Kochhar
Faviet 109 108 4 Greenberg
...
Cambrault 148 100 2 King
Bates 172 148 3 Cambrault
Bloom 169 148 3 Cambrault
Fox 170 148 3 Cambrault
...

Get subobject of object in hierarchy

with sample_data as (select '26.03.2015 14:10' as adate, 4 as type, 40 as object, 111 as barcode from dual union all
select '26.03.2015 14:09' as adate, 1 as type, 55 as object, 222 as barcode from dual union all
select '26.03.2015 14:08' as adate, 2 as type, 33 as object, 777 as barcode from dual union all
select '26.03.2015 14:08' as adate, 2 as type, 34 as object, null as barcode from dual union all
select '26.03.2015 13:20' as adate, 3 as type, 41 as object, null as barcode from dual union all
select '26.03.2015 12:00' as adate, 1 as type, 56 as object, 444 as barcode from dual union all
select '26.03.2015 11:59' as adate, 2 as type, 37 as object, 555 as barcode from dual union all
select '26.03.2015 11:59' as adate, 2 as type, 48 as object, null as barcode from dual)
select
adate, type, object, barcode
from sample_data
where type in (1, 2);
type 1 is finished building, type 2 is part of assembly
i need next result
55 | 222 | 26.03.2015 14:08 | 33
55 | 222 | 26.03.2015 14:08 | 34
56 | 444 | 26.03.2015 11:59 | 37
56 | 444 | 26.03.2015 11:59 | 38
so we see what object 55 contain subobject 33, 34 and two date = 26.03.2015 14:08
and object 56 contain subobject 37, 38 and two date = 26.03.2015 11:59
in begin i know two parameters - date and barcode of finished building, for example on my data - date 26.03.2015 14:09 and barcode 222 or date 26.03.2015 12:00 and barcode 444
This query does the job. Subquery T1 connects "types 1" with "types 2". T2 enumerates them using function row_number().
Last query filters only rows with rn = 1.
with t1 as (
select t.*, prior object pob, prior barcode pbc,
prior adate pad, level, sys_connect_by_path(object, '-') pth
from sample_data t where level=2
connect by prior adate > adate and prior type = 1 and type = 2),
t2 as (select t1.*,
row_number() over (partition by object order by pad) rn from t1 )
select pob, pbc, adate, object from t2 where rn = 1
Result:
POB PBC ADATE OBJECT
---------- ---------- ------------------- ----------
55 222 2015-03-26 14:08:00 33
55 222 2015-03-26 14:08:00 34
56 444 2015-03-26 11:59:00 37
56 444 2015-03-26 11:59:00 48
you have to define groups and then you can assign the items to the groups
with sample_data as (select '26.03.2015 14:10' as adate, 4 as type, 40 as object, 111 as barcode from dual union all
select '26.03.2015 14:09' as adate, 1 as type, 55 as object, 222 as barcode from dual union all
select '26.03.2015 14:08' as adate, 2 as type, 33 as object, 777 as barcode from dual union all
select '26.03.2015 14:08' as adate, 2 as type, 34 as object, null as barcode from dual union all
select '26.03.2015 13:20' as adate, 3 as type, 41 as object, null as barcode from dual union all
select '26.03.2015 12:00' as adate, 1 as type, 56 as object, 444 as barcode from dual union all
select '26.03.2015 11:59' as adate, 2 as type, 37 as object, 555 as barcode from dual union all
select '26.03.2015 11:59' as adate, 2 as type, 48 as object, null as barcode from dual),
sd_groups as (select adate, lag(adate, 1) over (order by adate) adate_last, object, barcode from (
select * from sample_data where type = 1
union all select min(adate), null, null, null from sample_data))
select sg.object, sg.barcode, sd.adate, sd.object
from sample_data sd, sd_groups sg
where sd.type = 2 and sd.adate between sg.adate_last and sg.adate;

Convert rows to columns like pivot

I have a data like:
formid formownerid approverid
1 100 102
1 100 103
1 100 104
2 200 107
2 200 103
2 200 109
2 200 105
3 400 201
3 400 210
I want to convert it to:
formid formownerid approverid approverid approverid approverid
1 100 102 103 104 NULL
2 200 107 103 109 105
3 400 201 202 NULL NULL
Wherever I looked at I saw pivot/unpivot but it looks unrelated since we don't need aggregation.
The aggregate is a necessary part of the pivot, but it's simple to apply here; you don't want sum but a max aggregate works fine:
select *
from (
select t.*,
row_number() over (partition by formid, formownerid
order by approverid) as rn
from t42 t
)
pivot (max(approverid) as approverid for (rn) in (1, 2, 3, 4));
FORMID FORMOWNERID 1_APPROVERID 2_APPROVERID 3_APPROVERID 4_APPROVERID
---------- ----------- ------------ ------------ ------------ ------------
1 100 102 103 104
2 200 103 105 107 109
3 400 201 210
Or you can specify the column name prefix explicitly to make them valid identifiers:
pivot (max(approverid) as approverid
for (rn) in (1 as a, 2 as b, 3 as c, 4 as d));
The inner query is adding a pseudocolumn rn to the table results to give you a fixed value to pivot against, since the actual approver IDs aren't going to be known in advance.
The manual approach might make this a bit clearer:
select formid, formownerid,
max(case when rn = 1 then approverid end) as approverid_1,
max(case when rn = 2 then approverid end) as approverid_2,
max(case when rn = 3 then approverid end) as approverid_3,
max(case when rn = 4 then approverid end) as approverid_4
from (
select t.*,
row_number() over (partition by formid, formownerid
order by approverid) as rn
from t42 t
)
group by formid, formownerid
order by formid, formownerid;
FORMID FORMOWNERID APPROVERID_1 APPROVERID_2 APPROVERID_3 APPROVERID_4
---------- ----------- ------------ ------------ ------------ ------------
1 100 102 103 104
2 200 103 105 107 109
3 400 201 210
The inner query is the same. The case statement produces each column as above, but without the max and grouping you get multiple rows with lots of extra blanks:
select formid, formownerid,
case when rn = 1 then approverid end as approverid_1,
case when rn = 2 then approverid end as approverid_2,
case when rn = 3 then approverid end as approverid_3,
case when rn = 4 then approverid end as approverid_4
from (
select t.*,
row_number() over (partition by formid, formownerid
order by approverid) as rn
from t42 t
);
FORMID FORMOWNERID APPROVERID_1 APPROVERID_2 APPROVERID_3 APPROVERID_4
---------- ----------- ------------ ------------ ------------ ------------
1 100 102
1 100 103
1 100 104
2 200 103
2 200 105
2 200 107
2 200 109
3 400 201
3 400 210
Notice that there's only a value in (at most) one column for each formid/formownerid combination, but that they appear in different rows. The max suppresses those multiple rows; and the pivot version does something similar under the hood.
SQL Fiddle showing the manual approach with the intermediate step, and the pivot version.
One possible Approch:
SELECT FROMID, FROMOWNERID, APPROVERID, NULL APPROVERID, NULL APPROVERID, NULL APPROVERID
FROM yourtable
WHERE FROMID = 100
AND APPROVERID = 102
UNION ALL
SELECT FROMID, FROMOWNERID, NULL APPROVERID, APPROVERID APPROVERID, NULL APPROVERID, NULL APPROVERID
FROM yourtable
WHERE FROMID = 100
AND APPROVERID = 103
UNION ALL
SELECT FROMID, FROMOWNERID, NULL APPROVERID, NULL APPROVERID, APPROVERID APPROVERID, NULL APPROVERID
FROM yourtable
WHERE FROMID = 100
AND APPROVERID = 104
----
-------
And So On

Resources