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

I have this table:
COL1 COL2 COL3
--------------------
A 202011 VAL1
A 202012 VAL2
A 202205 VAL3
B 202111 VAL4
B 202201 VAL5
B 202202 VAL6
COL1 COL2 COL3
--------------------
A 202011 VAL1
A 202012 VAL2
A 202101 NULL
B 202111 VAL4
B 202112 NULL
B 202201 VAL5
EDIT: I have the dates too if that's easier to play with than the ISO weeks.
Logic:
with the smallest COL2 value for each partition of COL1, take the following 3 yearmonths (YYYYMM) and, if the combination COL1 and COL2 present in the first table, show COL3 and NULL otherwise.
I have attempted to create this query to replicate what I would need in terms of adding up months, but not sure if it will be useful though.
WITH level_aux ( lev ) AS (
SELECT
to_number('2020'
|| lpad(level, 2, 0)) + 7 - 1
FROM
dual
CONNECT BY
level <= 12
), level_final AS (
SELECT
lev,
substr(lev, 1, 4) +
CASE
WHEN mod(substr(lev, 5, 2), 13) = 0 THEN
1
ELSE
0
END
||
CASE
WHEN substr(lev, 5, 2) < 13 THEN
lpad(mod(substr(lev, 5, 2), 13), 2, 0)
ELSE
lpad(mod(substr(lev, 5, 2), 13) + 1, 2, 0)
END
h
FROM
level_aux
)
SELECT
*
FROM
level_final;

Use MIN() as a window function:
select t.*,
(case when col2 < add_months(min(col2) over (partition by col1), 3)
then col3
end) as imputed_col3
from t;
Note: If col2 is not a date, you can convert it:
select t.*,
(case when to_date(col2, 'YYYYMM') < add_months(min(to_date(col2, 'YYYYMM')) over (partition by col1), 3)
then col3
end) as imputed_col3
from t;

Related

SQL Postgres union data with missed values

I have two results of queries:
id | col1 | col2 | col3
1 1 null 3j
2 2 12 35
3 null 32 31
4 null 43 33
5 null 44 4
id | col1 | col2 | col3
6 1 null 3j
7 2 null 35
8 3 null 31
9 4 null 33
10 5 null null
I need to do union:
id | col1 | col2 | col3
6 1 null 3j
7 2 12 35
8 3 32 31
9 4 43 33
10 5 null null
5 null 44 4
The problem is some values are missing
I wrote this big sql query to solve this problem:
select *
from (
select max(id) as id,
max(col1) as col1,
max(col2) as col2,
max(col3) as col3
from (
select max(id) as id,
max(col1) as col1,
max(col2) as col2,
max(col3) as col3
from (
select max(id) as id,
max(col1) as col1,
max(col2) as col2,
max(col3) as col3
from (
select *
from t1
where id = 1
union
select *
from t2
where id = 2
) t
group by case
when col1 is null
or
length(col1) =
0 then id
else col1 end
) t1
group by case
when col2 is null
or length(col2) = 0
then id
else col2 end
) t2
group by case
when col3 is null
or length(col3) = 0 then id
else col3 end
) t3
may be are there some ideas to simplify it? Or are there other approaches to enrich data efficiently, because I also need to do intersection, right, left, inner union and I don't want to build so monsters queries
well you cat try something like this:
union
select max(col1),
max(col2),
max(col3)
from t1
where id = 1
or id = 2
group by coalesce(nullif(col1, ''),
nullif(col2, ''),
nullif(col3, ''));
upd:
outer union
select max(col1),
max(col2),
max(col3)
from t1
where id = 1
or id = 2
group by coalesce(nullif(col1, ''),
nullif(col2, ''),
nullif(col3, ''))
having count = 1;
inner union
select max(col1),
max(col2),
max(col3)
from t1
where id = 1
or id = 2
group by coalesce(nullif(col1, ''),
nullif(col2, ''),
nullif(col3, ''))
having count > 1;
left and right are outer intersect with common query with 'where'

Oracle self join starting with minimum value for each partition

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

How to find out below output using oracle query

Input:
col1 col2
------------
1 A
2 1
3 B
4 2
5 C
6 Null
Output i want
Col1 Col2
__________
A 1
B 2
C Null
Oh, I see. Assuming there are no gaps in col1, you can use aggregation using arithmetic:
select max(case when mod(id, 2) = 0 then col2 end),
max(case when mod(id, 2) = 1 then col2 end)
from t
group by floor((id - 1) / 2);
Another method uses lead():
select col2, next_col2
from (select t.*, lead(col2) over (order by id) as next_col2
from t
) t
where mod(id, 2) = 1;
Use join to the next row of the same table and limit to every other row.
select t1.col2 col1, t2.col2
from tab t1
join tab t2 on t1.col1 = t2.col1 -1
where mod(t1.col1,2) = 1
C C
- -
A 1
B 2
C
Again, it assumes that your sequence in col1is without gaps.
This is an application of PIVOT, available since Oracle 11.1. (A much shorter solution is possible in Oracle 12.1 and higher, using MATCH_RECOGNIZE, but the question is explicitly tagged oracle11g.)
I use the analytic function ROW_NUMBER() to prepare the data, so that we don't need to assume anything about the ordering column - it may have gaps, and/or it doesn't even have to be a number column, it can be date or string or anything else that can be ordered.
Setup:
create table sample_data (col1, col2) as
select 1, 'A' from dual union all
select 2, '1' from dual union all
select 3, 'B' from dual union all
select 4, '2' from dual union all
select 5, 'C' from dual union all
select 6, null from dual
;
Query and output:
select col1, col2
from (
select ceil(row_number() over (order by col1) / 2) as r,
mod (row_number() over (order by col1) , 2) as c, col2
from sample_data
)
pivot (min(col2) for c in (1 as col1, 0 as col2))
order by r
;
COL1 COL2
---- ----
A 1
B 2
C
Just for fun, and for whoever may have this problem in Oracle 12 or higher, here is the match_recognize solution:
select col1, col2
from sample_data
match_recognize(
order by col1
measures x.col2 as col1, y.col2 as col2
pattern ( x y? )
define x as null is null
);

Get Top N row from each set from table with 4 column in SQL Server

Assume I have a table with 4 columns:
Col1 Col2 Col3 Col4
My initial query is :
SELECT Col1, Col2, Col3, Col4
FROM myTable
ORDER BY Col1, Col2, Col3 DESC, Col4
My desired result is all 4 columns, but with this condition that Top N Col3 different row when Col1, Col2 is equal.
Example with N=2 :
Table sample data:
Col1 Col2 Col3 Col4
---------------------
1 a 2000 s
1 a 2002 c
1 a 2001 b
2 b 1998 s
2 b 2002 c
2 b 2000 b
3 c 2000 b
1 f 1998 n
1 g 1999 e
Desired result:
1 a 2002 c
1 a 2001 b
1 f 1998 n
1 g 1999 e
2 b 2002 c
2 b 2000 b
3 c 2000 b
In another description, when (col1, col2) is repeated in multiple records, just export top N rows of those records when order by Col3 descending.
Can I do this with SQL script, without hard coding?
declare #t table (Col1 int, Col2 char, Col3 int, Col4 char);
insert into #t values
(1, 'a', 2000, 's'),
(1, 'a', 2002, 'c'),
(1, 'a', 2001, 'b'),
(2, 'b', 1998, 's'),
(2, 'b', 2002, 'c'),
(2, 'b', 2000, 'b'),
(3, 'c', 2000, 'b'),
(1, 'f', 1998, 'n'),
(1, 'g', 1999, 'e');
declare #N int = 2; -- number per "top"
with cte as
(
select *,
row_number() over(partition by col1, col2 order by col3 desc) as rn
from #t
)
select *
from cte c
where rn <= #N;
I think below code was as expected
declare #tab table (Col1 int, Col2 char(1), Col3 int, Col4 char(1))
declare #N int
insert into #tab
select 1, 'a' , 2000, 's'
union all
select 1 , 'a' , 2002 , 'c'
union all
select 1 , 'a' , 2001 , 'b'
union all
select 2 , 'b' , 1998 , 's'
union all
select 2 , 'b' , 2002 ,'c'
union all
select 2 , 'b' , 2000 ,'b'
union all
select 3 , 'c' , 2000 ,'b'
union all
select 1 , 'f' , 1998 ,'n'
union all
select 1 , 'g' , 1999 ,'e'
;with tab as
(
select ROW_NUMBER() over(partition by t.col1,t.col2 order by t.col3 desc) as row,t.*
from #tab t
)
select Col1,Col2,Col3,Col4
from tab
where row < 3
output
Col1 Col2 Col3 Col4
1 a 2002 c
1 a 2001 b
1 f 1998 n
1 g 1999 e
2 b 2002 c
2 b 2000 b
3 c 2000 b
METHOD 1- FOR MSSQL
http://sqlfiddle.com/#!6/4bda39/6
with a as (
select ROW_NUMBER() over(partition by t.col1,t.col2 order by t.col3 desc) as row,t.*
from myTable as t)
select * from a where a.row <= 2
Replace a.row <= 2 (2 with your N)
METHOD 2- FOR MYSQL
http://sqlfiddle.com/#!9/79e81a/63
SELECT myTable.Col1, myTable.Col2, myTable.Col3, myTable.Col4
FROM (
Select Col1 as Col1, Col2 as Col2, count(Col1) as cc, AVG(Col3) as aa
From myTable
group by Col1, Col2) as tt
join myTable on myTable.Col1 = tt.Col1 and myTable.Col2 = tt.Col2
where myTable.Col3 >= tt.aa
Order by Col1 ,Col2 ,Col3 Desc,Col4
METHOD 3- FOR MYSQL
http://sqlfiddle.com/#!9/79e81a/79
SELECT * FROM (
SELECT CASE Col1
WHEN #Col1 THEN
CASE Col2
WHEN #Col2 THEN #curRow := #curRow + 1
ELSE #curRow := 1
END
ELSE #curRow :=1
END AS rank,
#Col1 := Col1 AS Col1,
#Col2 := Col2 AS Col2,
Col3, Col4
FROM myTable p
JOIN (SELECT #curRow := 0, #Col1 := 0, #Col2 := '') r
ORDER BY Col1, Col2, Col3 DESC) as tt
WHERE tt.rank <= 2
Replace tt.rank <= 2 replace 2 by your desired index

How to get the minimum value in a set of columns while excluding a certain value?

I have been trying a query to select the minimum value in a row but also exclude a certain value (-998).
The table looks like this:
col1 col2 col3
----------------------------------
1 1 -998
2 -998 2
3 2 1
-998 1 3
So in the first row, the minimum value would be 1; in the second row, it would be 2; and in the third row, it would be 1 again.
I tried using a case statement and excluding -998 in each condition, but it keeps grabbing -998 for some reason.
SELECT
CASE
WHERE (col1 <= col2 and col1 <= col3) and col1 != -998 THEN col1
WHERE (col2 <= col1 and col2 <= col3) and col2 != -998 THEN col2
WHERE (col3 <= col1 and col3 <= col2) and col3 != -998 THEN col3
END AS [MIN_VAL]
FROM myTable
If anyone can point me in the right direction that would ge awesome.
Use the table value constructor to unpivot your column values and exclude values from there.
SQL Fiddle
MS SQL Server 2012 Schema Setup:
create table YourTable
(
col1 int,
col2 int,
col3 int
);
insert into YourTable values
(1 , 1 , -998),
(2 , -998 , 2 ),
(3 , 2 , 1 ),
(-998 , 1 , 3 );
Query 1:
select (
select min(R.Value)
from (values(T.col1),
(T.col2),
(T.col3)) as R(Value)
where R.Value <> -998
) as min_val
from YourTable as T;
Results:
| MIN_VAL |
|---------|
| 1 |
| 2 |
| 1 |
| 1 |
How about this:
use tempdb
create table myTable(
col1 int,
col2 int,
col3 int
)
insert into myTable values
(1, 1, -998),
(2, -998, 2),
(3, 2, 1),
(-998, 1, 3)
;with cte as(
select
rn = row_number() over(order by (select null)),
col = col1
from myTable
union all
select
rn = row_number() over(order by (select null)),
col = col2
from myTable
union all
select
rn = row_number() over(order by (select null)),
col = col3
from myTable
)
select
minimum = min(col)
from cte
where col <> - 998
group by rn
drop table mytable
SELECT
CASE
WHEN (col1 <= col2 or col2 = -998)
and (col1 <= col3 or col3 = -998)
and col1 != -998
THEN col1
WHEN (col2 <= col1 or col1 = -998)
and (col2 <= col3 or col3 = -998)
and col2 != -998
THEN col2
WHEN (col3 <= col1 or col1 = -998)
and (col3 <= col2 or col2 = -998)
and col3 != -998
THEN col3
END AS [MIN_VAL]
FROM myTable;