rank() a group of items by count(*) - oracle

I have some problems with Oracle analytic functions and need help.
Here's a generic example:
create table test (item varchar2(10), value varchar2(10));
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value1');
insert into test values ('item1','value2');
insert into test values ('item1','value2');
insert into test values ('item3','value2');
insert into test values ('item3','value2');
insert into test values ('item3','value2');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value1');
insert into test values ('item5','value2');
insert into test values ('item5','value2');
insert into test values ('item5','value2');
select item, value, count(*) c,
sum(count(*)) over () total,
sum(count(*)) over (partition by item) total_by_item,
dense_rank() over (order by count(*) desc) dense_rank
from test
group by item, value
order by 5 desc;
The result of the query is:
ITEM VALUE C TOTAL TOTAL_BY_ITEM DENSE_RANK
---------- ---------- -- ---------- -------------- ----------
item5 value1 7 20 10 1
item5 value2 3 20 10 3
item1 value2 2 20 7 4
item1 value1 5 20 7 2
item3 value2 3 20 3 3
How can I get the items ranked by TOTAL_BY_ITEM? So it would look like this:
ITEM VALUE C TOTAL TOTAL_BY_ITEM WHAT_I_NEED
---------- ---------- -- ---------- -------------- -----------
item5 value1 7 20 10 1
item5 value2 3 20 10 1
item1 value2 2 20 7 2
item1 value1 5 20 7 2
item3 value2 3 20 3 3
Is it possible to achieve this without another join or sub-query? I have a feeling that it is possible. I naturally think that it has to be something like this: dense_rank(count(*)) over (partition by item), like with analytic SUM that I use to get the 5th column, but it doesn't work.

I don't think this is what you are searching for but just for reference without a subquery you can achieve the same result using MODEL clause:
select item, value, c, total, total_by_item, what_i_need
from test
group by item, value
model
dimension by (row_number() over (order by null) d)
measures (
item, value,
count(*) c,
sum(count(*)) over () total,
sum(count(*)) over (partition by item) total_by_item,
1 what_i_need
)
rules (
what_i_need[any] = dense_rank() over (order by total_by_item[cv()] desc)
)
order by 5 desc;
I don't think you can achieve it without subquery otherwise.

Related

Insert record from one table to another table - Oracle

I have a table TABLE1 which has 5 columns (ROLL_NO, NAME, UNITS, CODE, AMOUNT);
CREATE TABLE TABLE1 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), UNITS NUMBER, AMOUNT NUMBER, CODE VARCHAR2(3));
------------------------------------------------------------------------------------------
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 1, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('101', 'JOHN', 2, 6, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 1, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('102', 'TOMS', 6, 7, 'ABC');
INSERT INTO TABLE1 VALUES ('103', 'FINN', 1, 1, 'BCD');
ROLL_NO NAME UNITS AMOUNT CODE
-------------------------------------------------------
101 JOHN 1 6 ABC
101 JOHN 2 6 ABC
-------------------------------------------
102 TOMS 1 7 ABC
102 TOMS 6 7 ABC
103 FINN 1 1 BCD
There is second table TABLE2 where we need to insert data from TABLE1
CREATE TABLE TABLE2 (ROLL_NO VARCHAR2(3), NAME VARCHAR2(4), RESULT VARCHAR2(3));
There are three conditions to insert data into TABLE2
1st case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is equal to AMOUNT then don't insert data into TABLE2
2nd case : If CODE is 'ABC' and SUM(UNITS) of particular ROLL_NO is not equal to AMOUNT then insert data with RESULT column value as 'YES'
3rd case : If CODE is not 'ABC' then RESULT column will be 'YES'.
Note: NAME, CODE and AMOUNT will be same for particular ROLL_NO though ROLL_NO has multiple UNITS.
Example :
ROLL_NO 102 CODE 'ABC' and two lines with SUM(UNITS) as 7 and its equal to AMOUNT i.e. 7 and (1st case)
ROLL_NO 101 has CODE 'ABC' and two lines with SUM(UNITS) as 3 and its not equal to AMOUNT i.e. 6 (2nd case)
ROLL_NO 103 has CODE 'BCD' which is not equal to 'ABC'(3rd case)
At the end TABLE2 should have
ROLL_NO NAME RESULT
-----------------------------
101 JOHN YES
103 FINN YES
I have tried this oracle query but it is inserting data related to 102 ROLL_NO which I don't need
SELECT T1.ROLL_NO, T1.NAME,
CASE
WHEN T1.CODE <> 'ABC' THEN 'YES'
WHEN T1.CODE = 'ABC' AND T2.TOT_UNITS <> T1.AMOUNT THEN 'YES'
END RESULT
FROM (SELECT DISTINCT ROLL_NO, NAME, AMOUNT, CODE
FROM TABLE1 ) T1
JOIN (SELECT ROLL_NO, SUM(UNITS) AS TOT_UNITS
FROM TABLE1
GROUP BY ROLL_NO) T2 ON T1.ROLL_NO = T2.ROLL_NO
I am not able to figure out how to not insert ROLL_NO 102 record into TABLE2..Can anyone provide better query than this if possible? Thank you
A "better" option is to scan table1 only once.
SQL> insert into table2 (roll_no, name, result)
2 with temp as
3 (select roll_no, name, sum(units) sum_units, amount, code,
4 case when code = 'ABC' and sum(units) = amount then 'NO'
5 when code = 'ABC' and sum(units) <> amount then 'YES'
6 else 'YES'
7 end as result
8 from table1
9 group by roll_no, name, amount, code
10 )
11 select roll_no, name, result
12 from temp
13 where result = 'YES';
2 rows created.
SQL> select * from table2;
ROL NAME RES
--- ---- ---
101 JOHN YES
103 FINN YES
SQL>

Retrieving based on specific condition

I have a little complex requirement on couple of tables which I am finding hard to crack.
There are 2 tables. TableA and TableB
TableA has a structure like:
-------------------------------------
ID COL1 COL2 CAT
-------------------------------------
1 RecAA RecAB 3
2 RecBA RecBB 3
3 RecCA RecCB 2
4 RecDA RecDB 2
5 RecEA RecEB 1
-------------------------------------
TableB has a structure like:
-----------------
COL3 TYPE
-----------------
RecAA 10
RecAA 11
RecAA 12
RecAB 10
RecAB 11
RecAB 12
RecAB 13
RecAB 14
RecBA 10
RecBA 11
RecBA 14
RecBA 15
RecBB 10
-----------------
Requirements:
Records in TableA should have CAT = 3.
Either COL1 or COL2 of TableA should be available in COL3 of TableB.
COL3 should definitely have TYPE in 10,11,12 and should have only that TYPE.
i.e As per the above requirements,
Of the records available in TableA, records with ID 1 and 2 have CAT = 3 in TableA
Both the records have atleast only value in COL3 of TableB. (Record with ID 1 in TableA has both COL1 and COL2 in TableB and record with ID 2 in TableA has COL1 in TableB)
RecAA record has Type 10,11,12 and only 10,11,12. So doesnt matter if RecAB has 10,11,12 or not. But RecBA and RecBB both does not have 10,11,12 types.
Therefore the result should be:
-------------------------------------
ID COL1 COL2 CAT
-------------------------------------
1 RecAA RecAB 3
-------------------------------------
What I tried:
WITH TEMP AS (SELECT COL3 FROM TableB GROUP BY COL3 HAVING SUM(CASE WHEN TYPE IN ('10','11','12') THEN 1 ELSE 0 END) = 0)
SELECT S.ID, S.COL1, S.COL2, S.CAT FROM TableA S
INNER JOIN TEMP T ON S.COL1 = T.COL3
WHERE S.CAT = 3;
Can someone please help on achieving this?
I think you're almost there, it's just your row selection in the CTE that seems problematic, and I think you need an OR:
WITH TEMP AS (
SELECT COL3
FROM TableB
GROUP BY COL3
HAVING SUM(POWER(2, TYPE - 10)) = 7 AND COUNT(*) = 3
)
SELECT
S.ID, S.COL1, S.COL2, S.CAT
FROM
TableA S
INNER JOIN TEMP T ON S.COL1 = T.COL3 OR S.COL2 = T.COL3
WHERE
S.CAT = 3;
I've subtracted 10 from each of your TYPEs to turn your 10,11,12 into 0,1,2 and then used POWER to turn them into 1, 2 and 4 which uniquely sum to 7 - (in other words your 10,11,12 became 2^(10-10), 2^(11-10) and 2^(12-10) which are 1, 2 and 4.. Which must then sum to 7).
I also mandate that there be a count of 3; the only way to get to 7 with three numbers that are powers of 2 is to have 1+2+4 which guarantees that 10,11,12 are present initially. If anything was missing, extra or repeated it wouldn't be 3 numbers that sum to 7
I think RecAB is excluded because even though it has 10,11,12 it also has 13,14 which cause it to be excluded..
You also seemed to be saying that COL3 should be present in either COL1 or COL2 of table A
You can use listagg analytic version to turn TYPE column into type_in_list column like below :
With temp_TableB (COL3, type_in_list) as (
SELECT distinct COL3, listagg(TYPE, ',') within group (order by TYPE)over(partition by COL3)
FROM TableB
)
select tA.*
--, tb.*
from tableA tA
INNER JOIN temp_TableB tB on (tA.COL1 = tB.COL3 or tA.COL2 = tB.COL3)
Where tA.CAT = 3
AND tB.TYPE_IN_LIST = '10,11,12'
;

Sum 2 columns from multiple rows with same ID

Currently, I have a table MY_TABLE like below:
ID ACCT_TYPE CREDIT_AMT DEBIT_AMT
-- --------- ---------- ---------
1 CDT_01 4 (null)
1 DBT_01 (null) 6
One ID can have multiple ACCT_TYPE like above, and each type has its own amount.
I want to just select the row which has ACCT_TYPE like 'CDT_%' but also the total_amount column which is the total of credit_amt and debit_amt column for the same ID.
My expected output like below:
ID ACCT_TYPE TOTAL_AMT
-- --------- ---------
1 CDT_01 10
I tried with this select statement below but it's no use, I think it's because of different ACCT_TYPE:
Select ID, ACCT_TYPE, SUM(NVL(CREDIT_AMT, 0) + NVL(DEBIT_AMT, 0)) TOTAL_AMT
FROM MY_TABLE WHERE ACCT_TYPE LIKE 'CDT_%' GROUP BY ID, ACCT_TYPE;
Here is the output of the select statement above:
ID ACCT_TYPE TOTAL_AMT
-- --------- ---------
1 CDT_01 4
I just begin to learn some query so I don't know is it really possible to get my expected output.
One way to do it is like below:
with inputs (ID, ACCT_TYPE, CREDIT_AMT, DEBIT_AMT) as
(
select 1, 'CDT_01', 4, null from dual union all
select 1, 'DBT_01', null, 6 from dual
),
prep as
(
select t.*, sum(nvl(credit_amt,0)) over (partition by id) + sum(nvl(debit_amt,0)) over (partition by id) as sum_per_id
from inputs t
)
select id, acct_type, sum_per_id
from prep
where acct_type like 'CDT_%';
Output:
A correlated subquery might be one option; sample data (thank you, #Ranagal) in lines #1 - 5; query that does the job begins at line #6.
SQL> with inputs (ID, ACCT_TYPE, CREDIT_AMT, DEBIT_AMT) as
2 (
3 select 1, 'CDT_01', 4, null from dual union all
4 select 1, 'DBT_01', null, 6 from dual
5 )
6 select a.id,
7 a.acct_type,
8 (select sum(nvl(b.credit_amt, 0)) +
9 sum(nvl(b.debit_amt , 0))
10 from inputs b
11 where b.id = a.id
12 ) total_amt
13 from inputs a
14 where acct_type like 'CDT%';
ID ACCT_T TOTAL_AMT
---------- ------ ----------
1 CDT_01 10
SQL>

oracle calculate the average of the current and previous row values

I have the following table which contains a single column VALUE1, I would like to calculate the average between the previous row and current row in VALUE1 and represent it in a second column VALUE2 starting from the second row i.e. The first row value will not be averaged.
The result should look like
ID VALUE1 VALUE2
1 3 3
2 4 3.5
3 5 4.5
4 5 5
5 6 5.5
6 2 4
NOTE: For first row (ID = 1) I average the first row with it self.
Any help appreciated. Thanks in advance.
You can also use the AVG() analytic function with a window of the previous and current row:
WITH practicenew AS (SELECT 1 ID, 3 value1 FROM dual UNION ALL
SELECT 2 ID, 4 value1 FROM dual UNION ALL
SELECT 3 ID, 5 value1 FROM dual UNION ALL
SELECT 4 ID, 5 value1 FROM dual UNION ALL
SELECT 5 ID, 6 value1 FROM dual UNION ALL
SELECT 6 ID, 2 value1 FROM dual)
SELECT ID,
value1,
AVG(value1) OVER (ORDER BY ID
ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) value2
FROM practicenew;
ID VALUE1 VALUE2
---------- ---------- ----------
1 3 3
2 4 3.5
3 5 4.5
4 5 5
5 6 5.5
6 2 4
I think you can use this query, it gives the same output you have mentioned.
Create table script:
create table practicenew (ID number, Value1 number) ;
insert into practicenew (ID, Value1) values (1, 3) ;
insert into practicenew (ID, Value1) values (2,4) ;
insert into practicenew (ID, Value1) values (3,5);
insert into practicenew (ID, Value1) values (4,5);
insert into practicenew (ID, Value1) values (5,6);
insert into practicenew (ID, Value1) values (6,2 );
Then use NVL and Lag function. Lag will bring your previous value to current row and nvl is being used for the first row, as you will have null value in the first row while using lag.
Query: select ID, value1,nvl(((lag(value1) over (order by ID) + value1)/2),value1) as Value2
from practicenew;
Output:
ID Value1 Value2
1 3 3
2 4 3.5
3 5 4.5
4 5 5
5 6 5.5
6 2 4
I hope it helps!

Find completely non-distinct rows

I have an Oracle table I've compiled using an Informatica workflow. It's failing an integrity check because the following queries return a different number of rows:
SELECT DISTINCT * FROM table // 4,000 rows
SELECT * FROM table // 4,006 rows
The table consists of 17 fields, none of which are unique keys (obviously). How can I find the 6 duplicate rows?
For returning duplicate rows.
select * from
(SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY column1,column2...column2
ORDER BY column_names)
seq_no
FROM table cd)
where seq_no>1;
For example i have create one sample_table below for your better understanding.
create table sample_table
(
id1 number,
id2 number
)
i have inserted below data into table
ID1 ID2
1 2
1 2
1 2
2 3
2 3
2 3
In above data set we have 6 rows but only two rows are distinct.
By using below queries we can get distinct rows and non-distinct rows.
SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY id1
ORDER BY id1)
seq_no
FROM sample_table cd
after partition the table with the help of id1 we will get the below results
ID1 ID2 SEQ_NO
1 2 1
1 2 2
1 2 3
2 3 1
2 3 2
2 3 3
Then if you want to see the distinct rows use below query
select * from
(SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY id1
ORDER BY id1)
seq_no
FROM sample_table cd)
where seq_no=1;
if you want to see duplicate set use below query
select * from
(SELECT cd.*,
ROW_NUMBER ()
OVER (PARTITION BY id1
ORDER BY id1)
seq_no
FROM sample_table cd)
where seq_no>1;
A posibiliy is to use a analytical function to count the rows in the same group and I don't see how you can write the query without writing all the columns in some clause:
select *
from (
Select a.*, count(*) over (partition by column1, column2, ..., column17) as cnt
from your_table a
)
where cnt>1
This should get 12 rows, because 6 are duplicated.
A basic sql query would be:
select col1, col2, ..., col17
from table
group by col1, col2, ..., col17
having count(*) > 1;

Resources