Start calculation after the offset in LEAD - sql

I've a weird scenario to work on. I have data in the way below.
Col1 col2 col3
a b 201921
a b 201923
a b 201924
a b 201925
a b 201927
Col1 and col2 etc are there for a partition and there are so many columns like those. I have a dynamic parameter which will feed the offset in LEAD function.
What LEAD doing is for every row, it will find the next value based on offset. But what I need is a little different. When the first row finds the offset, the next row should skip the offset number of rows.
For example, lead of 201921, 1 is 201923. So, the next calculation should happen from the row which has 201924. And then 201927 and further.
What I wrote currently is
Lead(col3,<dynamic param>,col3) over (partition by col1,col2 order by col3)
Is there such a thing to skip rows and continue from the next? I am a bit curious.
Expected output( for offset 1):
Col1 col2 col3 col4
a b 201921 201923
a b 201923 skip
a b 201924 201925
a b 201925 skip
a b 201927 201927
Expected output( for offset 2):
Col1 col2 col3 col4
a b 201921 201924
a b 201923 skip
a b 201924 skip
a b 201925 201925
a b 201927 201927

You can implement using a query determining COL4 using a CASE statement. This will be the base query. <dynamic param> is what will need to be replaced with your dymanic parameter.
SELECT col1,
col2,
col3,
CASE
WHEN ROW_NUMBER () OVER (PARTITION BY col1, col2 ORDER BY col3) + <dynamic param> >
COUNT (*) OVER (PARTITION BY col1, col2)
THEN
col3
WHEN MOD (ROW_NUMBER () OVER (PARTITION BY col1, col2 ORDER BY col3), <dynamic param> + 1) = 1
THEN
LEAD (col3, <dynamic param>) OVER (PARTITION BY col1, col2 ORDER BY col3)
END AS col4
FROM t;
Here are examples using the samples you provided
SQL> --offset of 1
SQL> WITH
2 t (col1, col2, col3)
3 AS
4 (SELECT 'a', 'b', 201921 FROM DUAL
5 UNION ALL
6 SELECT 'a', 'b', 201923 FROM DUAL
7 UNION ALL
8 SELECT 'a', 'b', 201924 FROM DUAL
9 UNION ALL
10 SELECT 'a', 'b', 201925 FROM DUAL
11 UNION ALL
12 SELECT 'a', 'b', 201927 FROM DUAL)
13 SELECT col1,
14 col2,
15 col3,
16 CASE
17 WHEN ROW_NUMBER () OVER (PARTITION BY col1, col2 ORDER BY col3) + 1 >
18 COUNT (*) OVER (PARTITION BY col1, col2)
19 THEN
20 col3
21 WHEN MOD (ROW_NUMBER () OVER (PARTITION BY col1, col2 ORDER BY col3), 1 + 1) = 1
22 THEN
23 LEAD (col3, 1) OVER (PARTITION BY col1, col2 ORDER BY col3)
24 END AS col4
25 FROM t;
COL1 COL2 COL3 COL4
_______ _______ _________ _________
a b 201921 201923
a b 201923
a b 201924 201925
a b 201925
a b 201927 201927
SQL> --offset of 2
SQL> WITH
2 t (col1, col2, col3)
3 AS
4 (SELECT 'a', 'b', 201921 FROM DUAL
5 UNION ALL
6 SELECT 'a', 'b', 201923 FROM DUAL
7 UNION ALL
8 SELECT 'a', 'b', 201924 FROM DUAL
9 UNION ALL
10 SELECT 'a', 'b', 201925 FROM DUAL
11 UNION ALL
12 SELECT 'a', 'b', 201927 FROM DUAL)
13 SELECT col1,
14 col2,
15 col3,
16 CASE
17 WHEN ROW_NUMBER () OVER (PARTITION BY col1, col2 ORDER BY col3) + 2 >
18 COUNT (*) OVER (PARTITION BY col1, col2)
19 THEN
20 col3
21 WHEN MOD (ROW_NUMBER () OVER (PARTITION BY col1, col2 ORDER BY col3), 2 + 1) = 1
22 THEN
23 LEAD (col3, 2) OVER (PARTITION BY col1, col2 ORDER BY col3)
24 END AS col4
25 FROM t;
COL1 COL2 COL3 COL4
_______ _______ _________ _________
a b 201921 201924
a b 201923
a b 201924
a b 201925 201925
a b 201927 201927

If I'm following your logic, you can use a case expression and analytic row_number() calculation to only populate col4 every offset row; something like (with n as the dynamic value):
select col1, col2, col3,
case when mod(row_number() over (partition by col1, col2 order by col3) + n, n + 1) = 0
then
lead(col3, n) over (partition by col1, col2 order by col3)
end as col4
from your_table
order by col1, col2, col3;
db<>fiddle
But that leaves the value in the last row for the partition null. Based on your second example you seem to actually want the last n rows to always have their own col3 value, which you could determine from lead() being null and then coalescing:
case when
mod(row_number() over (partition by col1, col2 order by col3) + n, n + 1) = 0
or lead(col3, n) over (partition by col1, col2 order by col3) is null
then
coalesce(lead(col3, n) over (partition by col1, col2 order by col3), col3)
end as col4
db<>fiddle
or using additional case branches:
case when
lead(col3, n) over (partition by col1, col2 order by col3) is null
then
col3
when
mod(row_number() over (partition by col1, col2 order by col3) + n, n + 1) = 0
then
lead(col3, n) over (partition by col1, col2 order by col3)
end as col4
db<>fiddle
If col3 can be nullable then you could always set the last n rows in the partition to their col3 answer instead of checking if the lead is null:
case when
row_number() over (partition by col1, col2 order by col3 desc) <= n
then
col3
when
mod(row_number() over (partition by col1, col2 order by col3) + n, n + 1) = 0
then
lead(col3, n) over (partition by col1, col2 order by col3)
end as col4
db<>fiddle

Related

Convert rows to column in Oracle with static column header

I am trying to convert below data set into columns, any help in SQL for this.
Data Set
col1
col2
A
1
A
2
A
3
A
4
A
5
Converted Data Set
col1
col2
col3
col4
col5
col6
A
1
2
3
4
5
You can PIVOT:
SELECT *
FROM data_set
PIVOT(
MAX(col2) FOR col2 IN (
1 AS col2,
2 AS col3,
3 AS col4,
4 AS col5,
5 AS col6
)
)
Or, use conditional aggregation:
SELECT col1,
MAX(CASE col2 WHEN 1 THEN col2 END) AS col2,
MAX(CASE col2 WHEN 2 THEN col2 END) AS col3,
MAX(CASE col2 WHEN 3 THEN col2 END) AS col4,
MAX(CASE col2 WHEN 4 THEN col2 END) AS col5,
MAX(CASE col2 WHEN 5 THEN col2 END) AS col6
FROM data_set
GROUP BY col1
Which, for your sample data:
CREATE TABLE data_set (col1, col2) AS
SELECT 'A', 1 FROM DUAL UNION ALL
SELECT 'A', 2 FROM DUAL UNION ALL
SELECT 'A', 3 FROM DUAL UNION ALL
SELECT 'A', 4 FROM DUAL UNION ALL
SELECT 'A', 5 FROM DUAL;
Both output:
COL1
COL2
COL3
COL4
COL5
COL6
A
1
2
3
4
5
db<>fiddle here

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

sort return data with SQL in SQLite order by row

I have a DB which has 8 columns, all are integers range 1~99:
Col1 Col2 Col3 Col4 Col5 Col6 Col7 Col8
1 13 24 18 35 7 50 88
13 4 33 90 78 42 26 57
22 18 30 3 57 90 71 8
...
When I perform "select Col1, Col2, Col3, Col5, Col6, Col7, Col8 from MyTable where Col4>10"
I would like the return data is sorted, e.g. the first row should return like this:
1,7,13,24,35,50,88
However, "order by" only work on "Column", is there anyway to preform this in SQL ? Or need a temp table/max() to perform this ? Thanks.
Regds
LAM Chi-fung
Your current design is not appropriate for this requirement.
Consider changing it to something like this:
CREATE TABLE tablename (
id INTEGER, -- corresponds to the rowid of your current table
col_id INTEGER NOT NULL, -- 1-8, corresponds to the number of each of the columns ColX
value INTEGER NOT NULL -- corresponds to the value of each of the columns ColX
);
You can populate it from your current table:
INSERT INTO tablename (id, col_id, value)
SELECT rowid, 1, Col1 FROM MyTable UNION ALL
SELECT rowid, 2, Col2 FROM MyTable UNION ALL
SELECT rowid, 3, Col3 FROM MyTable UNION ALL
SELECT rowid, 4, Col4 FROM MyTable UNION ALL
SELECT rowid, 5, Col5 FROM MyTable UNION ALL
SELECT rowid, 6, Col6 FROM MyTable UNION ALL
SELECT rowid, 7, Col7 FROM MyTable UNION ALL
SELECT rowid, 8, Col8 FROM MyTable
Now you can get the result that you want with GROUP_CONCAT() window function and aggregation:
SELECT result
FROM (
SELECT id, GROUP_CONCAT(value) OVER (PARTITION BY id ORDER BY value) result
FROM tablename
WHERE id IN (SELECT id FROM tablename WHERE col_id = 4 AND value > 10)
)
GROUP BY id
HAVING MAX(LENGTH(result))
See the demo.
Results:
result
1,7,13,18,24,35,50,88
4,13,26,33,42,57,78,90
Fix your data model! You should not be storing values in columns. You should be storing them in rows.
That is, SQL doesn't "sort columns". It deals with data in rows!
You can do what you want by unpivoting the data into rows, calculating the new order, and then reaggregating:
with t as (
select row_number() over () as id,
t.*
from mytable t
where col4 > 10
)
select max(case when seqnum = 1 then col end) as col1,
max(case when seqnum = 2 then col end) as col2,
max(case when seqnum = 3 then col end) as col3,
max(case when seqnum = 4 then col end) as col4,
max(case when seqnum = 5 then col end) as col5,
max(case when seqnum = 6 then col end) as col6,
max(case when seqnum = 7 then col end) as col7,
max(case when seqnum = 8 then col end) as col8
from (select row_number() over (partition by id order by col) as seqnum,
x.*
from (select id, col1 as col from t union all
select id, col2 as col from t union all
select id, col3 as col from t union all
select id, col4 as col from t union all
select id, col5 as col from t union all
select id, col6 as col from t union all
select id, col7 as col from t union all
select id, col8 as col from t
) x
) x
group by id;

Can I change column order in SQL table based on a value that appears in different columns?

I have a table that looks like this:
Column1 | Column2 | Column3| Column4
4 | 3 | 2 | 1
2 | 1
3 | 2 | 1
I want to flip the columns so that 1 always start in column 1 and then the rest of the values follow to the right. Like this:
Column1 | Column2 | Column3 | Column4
1 | 2 | 3 | 4
1 | 2
1 | 2 | 3
This is an example table. The real table is a hierarchy of a company so 1 = CEO and 2 = SVP for example. 1 is always the same name but as the number gets higher (lower in chain of command) the more names that are in that level. I'm hoping for an automated solution that looks for 1, makes that the first column and then populates the columns. I am struggling because the value that 1 represents is in different columns so I can't just change the order of the columns.
I was able to accomplish this using VBA but I would prefer to keep it in SQL.
I don't have any useful code that I have tried so far.
You can use Case expression:
WITH CTE1 AS
(SELECT 4 AS COL1, 3 AS COL2 , 2 AS COL3, 1 AS COL4 FROM DUAL
UNION ALL
SELECT 2, 1, NULL, NULL FROM DUAL
UNION ALL
SELECT 3, 2, 1, NULL FROM DUAL
)
SELECT CASE WHEN COL1 <> 1 THEN 1 ELSE COL1 END AS COL1,
CASE WHEN COL2 <> 2 THEN 2 ELSE COL2 END AS COL2,
CASE WHEN COL3 <> 3 THEN 3 ELSE COL3 END AS COL3,
CASE WHEN COL4 <> 4 THEN 4 ELSE COL4 END AS COL4
FROM CTE1;
You can apply some CASEes checking all possibilities, this is assuming NULLs for missing data:
COALESCE(col4,col3,col2,col1) AS c1,
CASE
WHEN col4 IS NOT NULL THEN col3
WHEN col3 IS NOT NULL THEN col2
WHEN col2 IS NOT NULL THEN col1
END AS c2,
CASE
WHEN col4 IS NOT NULL THEN col2
WHEN col3 IS NOT NULL THEN col1
END AS c3,
CASE
WHEN col4 IS NOT NULL THEN col1
END AS c4
You want to sort the values. A generic SQL solution would use:
select max(case when seqnum = 1 then col end) as col1,
max(case when seqnum = 2 then col end) as col2,
max(case when seqnum = 3 then col end) as col3,
max(case when seqnum = 4 then col end) as col4
from (select col1, col2, col3, col4, col,
row_number() over (order by col) as seqnum
from ((select col1 as col, 1 as which, col1, col2, col3, col4 from t) union all
(select col2 as col, 2 as which, col1, col2, col3, col4 from t) union all
(select col3 as col, 3 as which, col1, col2, col3, col4 from t) union all
(select col4 as col, 4 as which, col1, col2, col3, col4 from t)
) t
where col is not null
) t
group by col1, col2, col3, col4;
This would be simpler in a database that supports lateral joins. And a unique id on each row would also help.

how to get the maximum occurrence value from a table for a combination?

I have the following table;
column 1 column 2 column 3
1 2 X
1 2 X
1 2 Y
1 3 Z
1 3 X
I need to write an SQL query to get the output as;
1 2 X (because X is the maximum occurrence)
1 3 Z or X(because number of occurrence of Z or X is same)
How do i do this ?
I think i have a solution for you, try this script using the functions RANK(), ROW_NUMBER() & DENSE_RANK(), you choose the function that fits with your needs :
with temp as (
select 1 as col1, 2 AS col2, 'X' as col3 union all
select 1 as col1, 2 AS col2, 'Y' as col3 union all
select 1 as col1, 2 AS col2, 'X' as col3 union all
select 1 as col1, 3 AS col2, 'Z' as col3 union all
select 1 as col1, 3 AS col2, 'T' as col3 union all
select 1 as col1, 3 AS col2, 'Y' as col3 union all
select 1 as col1, 3 AS col2, 'Y' as col3 union all
select 1 as col1, 4 AS col2, 'Y' as col3 union all
select 1 as col1, 4 AS col2, 'W' as col3)
,temp2 AS (
select
col1
,col2
,col3
,COUNT(1) nb_occurence
,RANK() OVER(PARTITION BY col1,col2 ORDER BY COUNT(1) DESC) Ordre_RANK
,ROW_NUMBER() OVER(PARTITION BY col1,col2 ORDER BY COUNT(1) DESC) Ordre_ROW_NUMBER
,DENSE_RANK() OVER(PARTITION BY col1,col2 ORDER BY COUNT(1) DESC) Ordre_DENSE_RANK
from temp
GROUP BY
col1
,col2
,col3 )
SELECT *
FROM temp2
--WHERE Ordre_RANK = 1
--WHERE Ordre_ROW_NUMBER = 1
--WHERE Ordre_DENSE_RANK = 1
I hope this will help you.