Oracle self join starting with minimum value for each partition - sql

I have this table:
COL1 COL2 COL3
--------------------
A 1 VAL1
A 2 VAL2
A 4 VAL3
B 2 VAL4
B 4 VAL5
B 5 VAL6
And I would like to obtain this output:
COL1 COL2 COL3
--------------------
A 1 VAL1
A 2 VAL2
A 3 NULL
B 2 VAL4
B 3 NULL
B 4 VAL6
Logic:
with the smallest COL2 value for each partition of COL1, take the following 3 numbers and, if the combination COL1 and COL2 present in the first table, show COL3 and NULL otherwise.

Your question is a good example of what PARTITIONED OUTER JOIN was created for: DBFiddle
with top3 as (
select *
from (
select
col1, col2, col3
,min(col2)over(partition by col1) min_col2
,col2 - min(col2)over(partition by col1) + 1 as rn
from t
)
where col2 < min_col2 + 3
)
select
top3.col1
,r3.n as col2
,top3.col3
from
top3
partition by (col1)
right join
(select level n from dual connect by level<=3) r3
on r3.n=top3.rn;
As you can see, the first step is to get top3 and then just use partition by (col1) right join r3, where r3 is just generator of 3 rows.
Results:
COL1 COL2 COL3
----- ---------- ----
A 1 VAL1
A 2 VAL2
A 3
B 1 VAL4
B 2
B 3 VAL5
6 rows selected.
Note, this approach allows you to scan your table just once!

Let's see. Here is the table
select * from t order by col1, col2;
COL1 COL2 COL3
----- ---------- -----
A 1 VAL1
A 2 VAL2
A 4 VAL3
B 2 VAL4
B 4 VAL5
B 5 VAL6
6 rows selected
and now let's try to apply the described logic
with offsets as
(select level - 1 offset from dual connect by level <= 3),
smallest_col2 as
(select col1, min(col2) min_col2 from t group by col1)
select sc2.col1, sc2.min_col2 + o.offset col2, t.col3
from smallest_col2 sc2
cross join offsets o
left join t
on t.col1 = sc2.col1
and t.col2 = sc2.min_col2 + o.offset
order by 1, 2;
COL1 COL2 COL3
----- ---------- -----
A 1 VAL1
A 2 VAL2
A 3
B 2 VAL4
B 3
B 4 VAL5
6 rows selected

Use a recursive CTE to get the COL2s from the min of each COL1 up to the next 2 and then a left join to the table:
WITH cte(COL1, COL2, max_col2) AS (
SELECT COL1, MIN(COL2), MIN(COL2) + 2
FROM tablename
GROUP BY COL1
UNION ALL
SELECT COL1, COL2 + 1, max_col2
FROM cte
WHERE COL2 < max_col2
)
SELECT c.COL1, c.COL2, t.COL3
FROM cte c LEFT JOIN tablename t
ON t.COL1 = c.COL1 AND t.COL2 = c.COL2
ORDER BY c.COL1, c.COL2
See the demo.

The partitioned outer join, already demonstrated in Sayan's answer, is probably the best approach for that part of the assignment (data densification).
For the first part, in Oracle 12.1 and higher you can use the match_recognize clause:
select col1, col2, col3
from this_table
match_recognize(
partition by col1
order by col2
measures col2 - a.col2 + 1 as rn
all rows per match
pattern ( ^ a b* )
define b as col2 <= a.col2 + 2
)
partition by (col1)
right outer join
(select level as rn from dual connect by level <= 3) using (rn)
;

Another solution with the "recursive WITH clause"
With rws_numbered (COL1, COL2, COL3, rn) as (
select COL1, COL2, COL3
, row_number()over(order by col1, col3) rn
from Your_table
)
, cte ( COL1, COL2, COL3, rn ) as (
select COL1, COL2, COL3, rn
from rws_numbered
where rn = 1
union all
select
t.COL1
, case when t.col1 = c.col1 then c.col2 + 1 else t.col2 end COL2
, t.COL3
, t.rn
from rws_numbered t
join cte c
on c.rn + 1 = t.rn
)
select COL1, COL2, case when exists (select null from Your_table t where t.COL1 = cte.COL1 and t.COL2 = cte.COL2) then COL3 else null end COL3
from cte
order by 1, 2
;
db<>fiddle

Related

How <> works when compared with multiple values?

I have a table and sample data as below.
create table MyTable
(
Col1 NUMBER,
Col2 VARCHAR2(30)
)
MyTable
Col1 Col2
1 | Val1
2 | Val2
3 | Val3
4 | Val4
Below is the query which is already written and deployed to the application by some one else.
SELECT Col2
FROM MyTable A WHERE Col1 IN (2,3,4)
AND NOT EXISTS
(SELECT 1
FROM MyTable B
WHERE B.Col1 <> A.Col1)
How does <> compare multiple values in this case?
Does it just compare with value 2? Or randomly compares with any value amoung 2,3 or 4?
The values are compare one by one.
If you have the sample data:
CREATE TABLE MyTable(col1, col2) AS
SELECT 1, 'Val1' FROM DUAL UNION ALL
SELECT 2, 'Val2' FROM DUAL UNION ALL
SELECT 3, 'Val3' FROM DUAL UNION ALL
SELECT 4, 'Val4' FROM DUAL;
Then:
SELECT *
FROM MyTable A
WHERE Col1 IN (2,3,4)
Will return 3 rows:
COL1
COL2
2
Val2
3
Val3
4
Val4
For your full query:
SELECT Col2
FROM MyTable A
WHERE Col1 IN (2,3,4)
AND NOT EXISTS(
SELECT 1
FROM MyTable B
WHERE B.Col1 <> A.Col1
)
Then for each of the rows it will check that a row does NOT EXISTS in the MyTable table where B.Col1 <> A.Col1. In your case, there are 3 rows that exist in the sub-query for each of the matched rows in the main query. You can see this with the query:
SELECT Col2,
(SELECT LISTAGG(col1, ',') WITHIN GROUP (ORDER BY col1)
FROM MyTable B
WHERE B.Col1 = A.Col1) AS equal,
(SELECT LISTAGG(col1, ',') WITHIN GROUP (ORDER BY col1)
FROM MyTable B
WHERE B.Col1 <> A.Col1) AS not_equal
FROM MyTable A
WHERE Col1 IN (2,3,4)
Which outputs:
COL2
EQUAL
NOT_EQUAL
Val2
2
1,3,4
Val3
3
1,2,4
Val4
4
1,2,3
Given that there is always (more than) one row that exists then the NOT EXISTS condition will exclude every row and your result set will be empty.
db<>fiddle here

how to extract the rows where a group appears more than a certain number of times

I have the following table
col1 col2 col3 key
A B C 1
A B B 2
A B B 3
A B D 4
B D C 5
I would like to extract the rows where the group col1, col2, col3 appears more than once in the table.
A B B 2
A B B 3
So far, I have:
SELECT col1, col2, col3, count(*)
FROM db.table
GROUP BY col1, col2, col3
HAVING count(*) > 1
col1 col2 col3 count(*)
A B B 2
Is there a way to extract those rows with A B B without having to join the final table with the initial table?
You could use exists logic:
SELECT col1, col2, col3, "key"
FROM yourTable t1
WHERE EXISTS (SELECT 1 FROM yourTable t2
WHERE t2.col1 = t1.col1 AND t2.col2 = t1.col2 AND
t2.col3 = t1.col3 AND
t2."key" <> t1."key");
Try below query with CTE
with MyCTE
as
(
select col1,col2,col3,Key,COUNT(*) over(PARTITION BY col1,col2,col3 order
by col1,col2,col3) as Duplicate from yourtable
)
select col1,col2,col3,key from MyCTE where Duplicate>1

If two rows have same id but different col2, how can you keep only the ones that have max col3?

I have a table with three columns (id, col2, col3, col4) where col2 is A or B and col3 and col4 are integers. My problem is, there are many columns that have the same id and a different col2 value, and I want to select ONLY the rows that have a maximum value in col3.
For instance, if we have:
id | col2 | col3 | col4
1 | A | 3 | 2
1 | B | 5 | 3
2 | A | 6 | 2
...
I want to keep only the tuple (1, B, 5, 3). How can I achieve this?
I've tried this:
SELECT id, col2, MAX(col3), col4 FROM t GROUP BY id;
but I get an error saying that this is not a valid GROUP BY statement.
You can use keep:
SELECT id,
MAX(col2) KEEP (DENSE_RANK FIRST ORDER BY col3 DESC) as col2
MAX(col3),
MAX(col4) KEEP (DENSE_RANK FIRST ORDER BY col3 DESC) as col4
FROM t
GROUP BY id;
Or:
SELECT id, col2, col3, col4
FROM (SELECT t.*,
ROW_NUMBER() OVER (PARTITION BY id ORDER BY col3 DESC) as seqnum
FROM t
) t
WHERE seqnum = 1;
This query:
select t.*
from tablename t inner join (
select id, max(col3) col3
from tablename
group by id
having count(distinct col2) > 1
) g on g.id = t.id and g.col3 = t.col3
returns for each id that has different values in col2 only 1 row: the one containing the maximum value of col3.
If you also want the other rows where each id does not have different values in col2, then use UNION ALL:
select t.*
from tablename t inner join (
select id, max(col3) col3
from tablename
group by id
having count(distinct col2) > 1
) g on g.id = t.id and g.col3 = t.col3
union all
select t.* from tablename t
where not exists (
select 1 from tablename
where id = t.id and col2 <> t.col2
)
select * from TableName where col3 = (select max(col3) from TableName)

Count per category

have a table as below -
COL1 | COL2 | COL3
1 1 1
1 1 2
1 2 0
1 2 1
2 3 1
2 3 2
2 4 0
2 4 1
3 1 0
3 2 0
.
.
.
I want to select COL1 where all COL2 have sum(COL3) is > 0. If I am sure there are 20 distinct values in COL2, Then how can i pull all COL1 values that have all 20 COL2 filled with COL3 > 0. So the end result should be
COL1 | COL2 | COL3
1 1 3
1 2 1
2 3 3
2 4 1
I have tried a lot of ways to do this but no success.
Just use group by and having.
select col1,col2,sum(col3)
from tbl
group by col1,col2
having sum(col3)>0
select t1.*
from yourTable t1
inner join
(
select t.col1
from
(
select col1, col2, sum(col3) as col_sum
from yourTable
group by col1, col2
) t
group by t.col1
having sum(case when t.col_sum = 0 then 1 else 0 end) = 0
) t2
on t1.col1 = t2.col1
I use a CTE and a Group by with a where condition
;WITH CTE as (
select COL1,COL2,SUM(COL3) as COL3 FROM table1
Group By
COL1,COL2
)
select * from CTE
where COL3>0
Just group col2 and check if it's bigger then 0
select col1,col2,sum(col3)
from tbl
group by col2
having sum(col3)>0
http://sqlfiddle.com/#!9/537f8c/1
See if the below gives you the result that you are after. It is selecting the col1, col2 and a sum of col3 from a derived(?) table that is excluding the col3's that are 0:
select col1, col2, sum(col3)
from
(
select col1, col2, col3 from tbl where col3 <> 0
) as ds
group by col3

SQL Server : get max of the column2 and column3 value must be 1

I have an output of some part of my stored proedure like this:
col1 col2 col3 col4
--------------------------
2016-05-05 1 2 2
2016-05-05 1 3 32
2016-05-12 2 1 11
2016-05-12 3 1 31
Now I need to get result based on this condition
col2 = 1 and col3 = max or col3 = 1
and col2 = max
The final result should be
col1 col2 col3 col4
-------------------------
2016-05-05 1 3 32
2016-05-12 3 1 31
Not sure if thats the most efficient way , but you can use ROW_NUMBER() :
SELECT * FROM (
SELECT t.*,
ROW_NUMBER() OVER(PARTITION BY t.col1 ORDER BY t.col3 DESC) as rnk,
WHERE t.col2 = 1
UNION ALL
SELECT t.*,
ROW_NUMBER() OVER(PARTITION BY t.col1 ORDER BY t.col2 DESC) as rnk,
WHERE t.col3 = 1) tt
WHERE rnk = 1
This will give you all the records with
(col2=1 and col3=max) or (col3=1 and col2=max)
This is a bit tricky. Your data has no ambiguities, such as duplicate maximuma in col4 or "1" values in both col2 and col3.
The following is a direct translation of the logic in your question:
select t.*
from t
where t.col4 = (select max(t2.col4)
from t t2
where t2.col1 = t.col1 and (t2.col2 = 1 or t2.col3 = 1)
);
Try this. Note if there are more than 1 same max value, then you need all of those in output. And it will work for all scenarios, even when col1 is not in sync with col2 and col3.
I am first finding highest values of col2 and col3 and assigning them value as 1. Then in outer query, I am using your join condition. Demo created for Postgres DB as SQLServer wasn't available.
SQLFiddle Demo
select col1,col2,col3,col4
from
(
select t.*,
RANK() OVER(ORDER BY col3 DESC) as col3_max,
RANK() OVER(ORDER BY col2 DESC) as col2_max
from your_table t
) t1
where
(col2=1 and col3_max=1)
OR
(col3=1 and col2_max=1)
Alternative way:
SELECT * FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY col1 ORDER BY iif(col2 = 1, col3, col2) DESC) as r
FROM tbl) t
WHERE r = 1