Split one row to many in same database table - sql

We have a requirement where we want to split one row to many rows ( in the same table ) based on some conditions.
Let's suppose we have this table :
ID
Value
1
V1
2
V2
3
V3
Requirement is,
if ID=1, split this row into two more rows where IDs of new rows will be 4 and 5 and the value will be V1 (same as ID = 1 value) only.
if ID=2, don't split.
if ID=3, split this row into one more row where ID of the new row will be 6 and value will be V3 (same as ID = 3 value) only.
The final o/p will be :
ID
Value
1
V1
4
V1
5
V1
2
V2
3
V3
6
V3
I am looking out for some SQL script/Stored Proc that will help me in achieving the same.

You can generate the rows with a join and derived table . . . and then use union all to bring in the existing rows:
select id, value
from t
union all
select x.new_id, t.value
from t join
(select 1 as old_id, 4 as new_id from dual union all
select 1 as old_id, 5 as new_id from dual union all
select 3 as old_id, 6 as new_id from dual
) x
on t.id = x.old_id;
If you just want to insert the values, use insert with the second query.

You can join your table with numbers as follows:
select case when t.id = 2 then t.id
when t.id = 3 then t.id * lvl
when t.id = 1 and lvl > 1 then lvl+2
else lvl
end as id, t.value
from your_table t
cross join (select level as lvl from dual connect by level <=3)
where t.id = 1 or (t.id=2 and lvl=1) or (t.id = 3 and lvl <= 2)

Related

update sql to update lowest hierarchical data for each row in a table

I have a table as shown below
id
previous_id
latest_id
1
null
null
2
1
null
3
2
null
4
null
null
5
4
null
6
6
null
I want to update the table by setting the latest_id column value to lowest hierarchical value, which will look like this:
id
previous_id
latest_id
1
null
3
2
1
3
3
2
3
4
null
6
5
4
6
6
5
6
I have tried to use connect by, but the query is getting too complicated as start with cannot have a static value assigned, this update is for the entire table.
Below is what I could write for a single record based on it's id, how can I generalize it for all records in the table?
UPDATE TABLENAME1
SET LATEST_ID = (SELECT MAX(ID)
FROM TABLENAME1
START WITH ID = 3
CONNECT BY PREVIOUS_ID = PRIOR ID );
You can use a correlated hierarchical query and filter to get the leaf rows:
UPDATE table_name t
SET latest_id = (SELECT id
FROM table_name h
WHERE CONNECT_BY_ISLEAF = 1
START WITH h.id = t.id
CONNECT BY previous_id = PRIOR id);
Which, for the sample data:
CREATE TABLE table_name (id, previous_id, latest_id) AS
SELECT 1, null, CAST(null AS NUMBER) FROM DUAL UNION ALL
SELECT 2, 1, null FROM DUAL UNION ALL
SELECT 3, 2, null FROM DUAL UNION ALL
SELECT 4, null, null FROM DUAL UNION ALL
SELECT 5, 4, null FROM DUAL UNION ALL
SELECT 6, 5, null FROM DUAL;
Updates the table to:
ID
PREVIOUS_ID
LATEST_ID
1
null
3
2
1
3
3
2
3
4
null
6
5
4
6
6
5
6
db<>fiddle here
To the accepted answer, I will add this alterative which might perform better for large datasets by eliminating the correlated subquery.
MERGE INTO table_name t
USING (
SELECT CONNECT_BY_ROOT(id) root_id, id latest_id
FROM table_name
WHERE connect_by_isleaf = 1
CONNECT BY previous_id = prior id ) u
ON ( t.id = u.root_id )
WHEN MATCHED THEN UPDATE SET t.latest_id = u.latest_id;

How modify sequence numbers based on one changed sequence number in oracle sql

How to change the rest sequence numbers when one sequence number changed. Then sort the rows based on sequence number. Here's the example:
seq_nbr
A 1 | A 1 | A 1
B 2 -> 5 | B 5 | C 2
C 3 | C 3 -> 2 | D 3
D 4 | D 4 -> 3 | E 4
E 5 | E 5 -> 4 | B 5
When the new sequence number assigned to B is greater than the old one, the sequence numbers between the old and the new value are pushed down (subract 1), to keep the consecutive numbering in that column.
Sayan M asked you in a comment what the desired result is if you change the sequence number to a lower value rather than higher. Presumably in that case all the sequence numbers between the old and new value must be pushed up (add 1), to keep the consecutive numbering.
The update statement below addresses both cases (as well as the trivial case when the sequence number doesn't change - it's updated to itself). It does not require analytic functions and it works as an update statement.
I assume the same table and column names as in MT0's answer, and the inputs are a "value" and a "new sequential number" (such as "B" and "5") - given as bind variables.
update
(
select value, seq_nbr,
(select seq_nbr from table_name where value = :i_value) as old_nbr
from table_name
)
set seq_nbr =
case when value = :i_value then :i_nbr
else seq_nbr + case when :i_nbr > old_nbr then -1 else 1 end
end
where :i_nbr != old_nbr
and seq_nbr between least(:i_nbr, old_nbr) and greatest(:i_nbr, old_nbr)
;
You can use a MERGE statement with the ROW_NUMBER analytic function:
MERGE INTO table_name dst
USING (
WITH new_seq_nbr (value, new_nbr) AS (
SELECT 'B', 5 FROM DUAL -- Your change goes here.
)
SELECT t.ROWID AS rid,
ROW_NUMBER() OVER (
ORDER BY
CASE t.value
WHEN n.value
THEN 2 * new_nbr + 1
ELSE 2 * seq_nbr
END
) AS new_seq_nbr
FROM table_name t
CROSS JOIN new_seq_nbr n
) src
ON (src.rid = dst.ROWID)
WHEN MATCHED THEN
UPDATE
SET dst.seq_nbr = src.new_seq_nbr
WHERE dst.seq_nbr <> src.new_seq_nbr;
Which, for the sample data:
CREATE TABLE table_name (value PRIMARY KEY, seq_nbr UNIQUE) AS
SELECT 'A', 1 FROM DUAL UNION ALL
SELECT 'B', 2 FROM DUAL UNION ALL
SELECT 'C', 3 FROM DUAL UNION ALL
SELECT 'D', 4 FROM DUAL UNION ALL
SELECT 'E', 5 FROM DUAL UNION ALL
SELECT 'F', 6 FROM DUAL;
After then MERGE then:
SELECT * FROM table_name ORDER BY seq_nbr;
Outputs:
VALUE
SEQ_NBR
A
1
C
2
D
3
E
4
B
5
F
6
db<>fiddle here

SQL Group by fixed list of values

If I have two columns:
col1 col2 amount
1 2 15
2 3 12
1 3 10
3 1 4
3 2 3
And I perform a group by col1,col2 then I get a row for each combination (present) in the data.
My problem though is, that I dont always have all combinations, but I would want to return a row of each combination still. So if there isn't a combination. for example 2 -> 1 then I would want its value to be 0.
Can I somehow specify the "levels" of the group by?
I'm using SQL Oracle.
and the outcome I would want is:
1 -> 2 15
1 -> 3 10
2 -> 1 0
2 -> 3 12
3 -> 1 4
3 -> 2 3
With their respective amount, and 0 if they dont exist, or null works. ( I have a filter to exclude where col1 and col2 are same)
Generate all the rows using cross join and then filter for the ones you want:
select c1.col1, c2.col2, coalesce(t.amount, 0)
from (select 1 as co1l from dual union all
select 2 as co1l from dual union all
select 3 as co1l from dual
) c1 cross join
(select 1 as co12 from dual union all
select 2 as co12 from dual union all
select 3 as co12 from dual
) c2 left join
t
on t.col1 = c1.col1 and t.col2 = c2.col2
where c1.col1 <> c2.col2;

Sql query to print values starting from column A till column B

New to SQL so looking for help
I'm trying to write a query which would print values starting from column A till the column B excluding the value present in column 'ANS' of second table.
Like here are the two tables X and Y
Table1
A FROM TO
a 6 9
b 3 6
c 0 3
d 2 3
Table2
A ANS
a 7
b 5
c 1
And I want the output as
A ANS
a 6
a 8
a 9
b 3
b 4
b 6
c 0
c 2
c 3
d 2
d 3
I've tried to write something like this but it doesn't work
WITH y(n) AS
(SELECT 1 AS n
FROM dual
UNION ALL
SELECT n + 1 AS n
FROM y, table1 T
WHERE n <= T.TO AND n>= T.FROM )
SELECT * FROM y;
Which prints 5000+ rows (that's why I am not attaching output)
Thanks in advance
After you get all the numbers between from and to with a recursive cte, left join on the generated table and get only those numbers which don't exist in table2 using not exists.
--Get the maximum value of `to` column and generate all numbers between 0 and that value
WITH maxto(maxt) as (SELECT MAX(TO) FROM TABLE1)
,y(n) AS
(SELECT 0 AS n FROM dual
UNION ALL
SELECT n + 1 AS n FROM y WHERE n < (SELECT maxt FROM maxto))
SELECT * FROM
(SELECT t1.a, y.n
FROM y
LEFT JOIN table1 t1 on y.n between t1.from and t1.to
WHERE t1.a IS NOT NULL) x
WHERE NOT EXISTS (SELECT 1 FROM table2 WHERE x.a = a and x.n = ans)
ORDER BY 1,2
Sample demo
WITH y(n) AS
(SELECT level - 1 FROM dual connect by level <= select max(TO- FROM) +2 from table1)
SELECT t1.a, t1.from + y.n FROM table1 t1
JOIN y on 1 = 1
left JOIN table2 on y.n + t1.FROM = t2.ANS and t2.a = t1.a
where y.n < t1.TO-t1.FROM
and t2.ANS is null;
You can use a "hierarchical query" and a MINUS operation and avoid joins altogether. MINUS is easy to understand if you are somewhat familiar with set theory. Generating numbers using hierarchical queries is somewhat unnatural (and may only be available in Oracle, I don't know any other db products), but it is used very often and it works very fast.
I changed the column names to from_n and to_n; I don't remember if "from" and/or "to" are reserved words in Oracle, but why take the risk.
with
table1 ( a, from_n, to_n ) as (
select 'a', 6, 9 from dual union all
select 'b', 3, 6 from dual union all
select 'c', 0, 3 from dual union all
select 'd', 2, 3 from dual
),
table2 ( a, ans ) as (
select 'a', 7 from dual union all
select 'b', 5 from dual union all
select 'c', 1 from dual
)
-- everything above this point is for testing only and can be removed
-- solution (SQL query) begins below
select a, from_n + level - 1 as ans
from table1
connect by level <= 1 + to_n - from_n
and prior a = a
and prior sys_guid() is not null
minus
select a, ans
from table2
;
Output:
A ANS
- ----------
a 6
a 8
a 9
b 3
b 4
b 6
c 0
c 2
c 3
d 2
d 3
11 rows selected

fetch record as in order passed for IN condition in oracle

I want to fetch the records in order passed for IN condition.
select * from table where id in(6,3,7,1);
is returning the rows as
id name
1 abc
3 xy
6 ab
7 ac
but I want to display the records in same orders as ids passed in condition in Oracle
id name
6 ab
3 xy
7 ac
1 abc
Please help me in fetching the records in same order as in condition ids in oracle. The values in IN condition may change dynamically.
You can do this with a case statement in the order by clause or using a join.
select *
from table
where id in(6,3,7,1)
order by (case id when 6 then 1 when 3 then 2 when 7 then 3 when 1 then 4 end);
Or:
with ids as (
select 6 as id, 1 as ordering from dual union all
select 3 as id, 2 as ordering from dual union all
select 7 as id, 3 as ordering from dual union all
select 1 as id, 4 as ordering from dual
)
select *
from table t join
ids
on t.ids = ids.id
order by ids.ordering;
Note that you don't need the in in this case, because the join does the filtering.
you can use trick
select * from table where id in(6,3,7,1) order by case when id = 6 then 1
id = 3 then 2
id = 7 then 3
id = 1 then 4
end