Retrieving profiles from a table based on specific logic - oracle

I have a requirement to extract profiles from a table for the below criteria:
There are 5 IDs in total. 100, 200, 300, 400 and 500
The profiles to have atleast one ID of each value 100, 300 and 400. IDs with value 200 and 500 should not be present even once.
Profile can have multiple number of IDs of value 300 and 400 but will have only one ID of value 100.
The profiles will have IDs 300 and 400 equally. i.e For every ID will the value 300, there will be an ID of value 400.
Eg:
TABLE A:
-------------------------
PROFILE_ID ID
-------------------------
12345 100
12345 300
12345 400
23456 100
23456 300
23456 400
23456 300
23456 400
34567 100
34567 200
-------------------------
The result should fetch PROFILE_IDs 12345 and 23456 and not 34567.
I am pretty stuck and blank in getting a clear idea on how to frame a query for this. Please help.

For sample data you posted:
SQL> select * From test;
PROFILE_ID ID
---------- ----------
12345 100
12345 300
12345 400
23456 100
23456 300
23456 400
23456 300
23456 400
34567 100
34567 200
10 rows selected.
one option is to do it rule-by-rule, each of CTEs retrieving data which satisfy certain rule. The final result is intersection of these PROFILE_IDs.
SQL> with
2 rule2 as -- don't allow IDs 200 nor 500
3 (select profile_id
4 from test
5 where profile_id not in (select profile_id
6 from test
7 where id in (200, 500)
8 )
9 ),
10 rule3 as -- there can be only one ID = 100 for each PROFILE_ID
11 (select profile_id
12 from (select profile_id,
13 sum(case when id = 100 then 1 else 0 end) cnt_100
14 from test
15 group by profile_id
16 )
17 where cnt_100 = 1
18 ),
19 rule4 as -- number of IDs 300 and 400 has to be equal and greater than 0
20 (select profile_id
21 from (select profile_id,
22 sum(case when id = 300 then 1 else 0 end) cnt_300,
23 sum(case when id = 400 then 1 else 0 end) cnt_400
24 from test
25 group by profile_id
26 )
27 where cnt_300 = cnt_400
28 and cnt_300 > 0
29 )
30 select profile_id from rule2
31 intersect
32 select profile_id from rule3
33 intersect
34 select profile_id from rule4;
PROFILE_ID
----------
12345
23456
SQL>

Personally, since all your rules rely on counting the different ids, I'd do it more like this. Some of this is redundant, but it makes explicit which part comes from which of your criteria.
with rules as (
select profile_id,
sum(case when id = 100 then 1 else 0 end) as cnt_100,
sum(case when id = 200 then 1 else 0 end) as cnt_200,
sum(case when id = 300 then 1 else 0 end) as cnt_300,
sum(case when id = 400 then 1 else 0 end) as cnt_400,
sum(case when id = 500 then 1 else 0 end) as cnt_500
from table1
group by profile_id
)
select profile_id
from rules
-- rule 1
where cnt_100 > 0
and cnt_300 > 0
and cnt_400 > 0
-- rule 2
and cnt_200 = 0
and cnt_500 = 0
-- rule 3
and cnt_100 = 1
-- rule 4
and cnt_300 = cnt_400
SQL Fiddle

Related

Query to find before and after values of a given value

If we have table Employees
EMP_ID ENAME SALARY DEPT_ID
1 abc 1000 10
2 bca 1050 10
3 dsa 2000 20
4 zxc 3000 30
5 bnm 5000 30
6 rty 5050 30
I want to get the rank of the salary with before 2 values and after 2 values including the given rank
Like if I give rank 4 it should give ranks 2,3,4,5,6 details.
output should be
5 bnm 5000 30
4 zxc 3000 30
3 dsa 2000 20
3 dsa 2000 20
2 bca 1050 10
1 abc 1000 10
I have a query
WITH dept_count AS (
SELECT
e.*,
dense_rank() over( ORDER BY salary DESC) AS rk
FROM employees e
)
SELECT
*
FROM dept_count dc
WHERE dc.rk BETWEEN (
SELECT
c.rk-2
FROM dept_count c
WHERE c.rk =4
)
AND (
SELECT
c.rk + 2
FROM dept_count c
WHERE c.rk = 4
)
but I need a query which can be simplified.
Could someone help me with this query?
You just need to use ROW_NUMBER() along with a substitution parameter :
WITH dept_count AS (
SELECT
e.*,
ROW_NUMBER() OVER( ORDER BY salary DESC) AS rk
FROM employees e
)
SELECT *
FROM dept_count
WHERE rk BETWEEN &prm - 2 AND &prm + 2

Oracle: Concatenate sequence number for repeated values

My situation similar to this but I just want to create sequence number for repeated values only.
Table: MyTable
-----------------------
ID CODE
1 100
2 100
3 200
4 200
5 200
6 300
Below is my query:
SELECT ID, CODE, (row_number() over (partition by CODE order by ID)) as SEQ from MyTable
And this is my current result:
ID CODE SEQ
1 100 1
2 100 2
3 200 1
4 200 2
5 200 3
6 300 1
But my expected result is:
ID CODE SEQ
1 100 1
2 100 2
3 200 1
4 200 2
5 200 3
6 300
Eventually, I do some coding to modify my current result. But I want to ask is there any way to generate the expected result via query only?
You can add CASE COUNT(1) over (partition by CODE) in your query, see sample query below.
WITH MyTable
as (
select 1 id, 100 code from dual union all
select 2 id, 100 code from dual union all
select 3 id, 200 code from dual union all
select 4 id, 200 code from dual union all
select 5 id, 200 code from dual union all
select 6 id, 300 code from dual)
SELECT ID, CODE, CASE COUNT(1) over (partition by CODE)
WHEN 1 THEN NULL
ELSE row_number() over (partition by CODE order by ID)
END as SEQ
from MyTable;
ID CODE SEQ
---------- ---------- ----------
1 100 1
2 100 2
3 200 1
4 200 2
5 200 3
6 300
6 rows selected

How to replace preceding values with zeros in oracle

Table is like:-
ID AMOUNT PAID
1 500 100
1 500 50
1 500 200
2 1000 300
3 800 500
--I want to print ID 1 values should be zeros except first value like:-
ID AMOUNT PAID
1 500 100
1 0 50
1 0 200
2 1000 300
3 800 500
--Instead of repeating values have to be replaced with zeros
This is better done at the application layer. Assuming you have an ordering column, you can do it in SQL:
select id,
(case when row_number() over (partition by id order by ??) = 1
then amount
else 0
end) as amount,
paid
from t
order by id, ??;
The ?? represents the column used for ordering.

PIVOT table in Oracle

Can you help me to pivot the details in my Oracle table PAY_DETAILS
PAY_NO NOT NULL NUMBER
EMP_NO NOT NULL VARCHAR2(10)
EMP_ERN_DDCT_NO NOT NULL VARCHAR2(21)
ERN_DDCT_CATNO NOT NULL VARCHAR2(10)
ERN_DDCT_CATNAME NOT NULL VARCHAR2(1000)
PAY_MONTH NOT NULL DATE
AMOUNT NOT NULL NUMBER(10,2)
EARN_DEDUCT NOT NULL VARCHAR2(2)
select EMP_NO,EMP_ERN_DDCT_NO,AMOUNT,EARN_DEDUCT, ERN_DDCT_CATNO from pay_details
EMP_NO EMP_ERN_DDCT_NO AMOUNT EA ERN_DDCT_C
---------- --------------------- ---------- -- ----------
219 10 175 A 001
219 1 5000 A 002
794 7 50000 A 001
769 6 35000 A 001
465 4 5000 A 002
289 2 5000 A 002
435 3 5000 A 002
816 38 5 D 201
737 30 5 D 201
Is it possible to make this output into a cross tab?
So, lets assume you want to pivot your salary data for each month. You can use the following query.
SELECT * FROM
(
SELECT emp_no,
emp_ern_ddct_no,
pay_month,
amount
FROM pay_details
)
PIVOT
(
SUM(amount)
FOR pay_month IN ('01/01/2016', '02/01/2016', '03/01/2016','04/01/2016','05/01/2016','06/01/2016','07/01/2016','08/01/2016','09/01/2016','10/01/2016','11/01/2016','12/01/2016')
)
ORDER BY emp_no;
This is just an example, you can PIVOT your data based on different columns. For more details refer to the following link.
http://www.techonthenet.com/oracle/pivot.php
http://www.oracle-developer.net/display.php?id=506
Since you are on Oracle 10g PIVOT wont work. Try using something similar to the below query.
SELECT emp_no,
SUM(CASE WHEN pay_month ='01/01/2016' THEN AMOUNT ELSE 0 END) jan_pay,
SUM(CASE WHEN pay_month ='02/01/2016' THEN AMOUNT ELSE 0 END) feb_pay,
SUM(CASE WHEN pay_month ='03/01/2016' THEN AMOUNT ELSE 0 END) march_pay
.........
FROM pay_details
GROUP BY emp_no;

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