Deleating value from auto incremented column - sql

First of all appologies if the question is below standard(basic for most of all).
I have a column whose value gets incremented automatically based on other column.E.g.:if column name has value "abc","abc" ,"xyz","xyz",xyz","hij" the value in auto-incremented column must be "1","2","1","2","3","1".
The problem occurs while deleating or updating record.
What if someone delete ["xyz"] value of having value "2"?
How to handle such situation ?

As one of the options(simple and straightforward one) you can create a view and generate that "auto-incremented column" on the fly - every time you query the view.
Here is an example:
-- source table, which does not contain that auto incremented
-- column you are interested in
create table t1(id, col1) as (
select 1, 'abc' from dual union all
select 2, 'abc' from dual union all
select 3, 'xyz' from dual union all
select 4, 'xyz' from dual union all
select 5, 'xyz' from dual union all
select 6, 'hij' from dual
);
-- and here is the view
create or replace view t1_v as
select id
, col1
, row_number() over(partition by col1
order by id) as auto_inc
from t1;
select *
from t1_v;
ID COL1 AUTO_INC
---------- ---- ----------
1 abc 1
2 abc 2
6 hij 1
3 xyz 1
4 xyz 2
5 xyz 3
Updating the value:
-- Update can be issued against base table or
-- a view, if it's a key-preserved one
update t1
set col1 = 'acb'
where id = 1;
select *
from t1_v;
ID COL1 AUTO_INC
---------- ---- ----------
2 abc 1
1 acb 1
6 hij 1
3 xyz 1
4 xyz 2
5 xyz 3
Deleting a row:
-- You can delete from the base table or
-- a view, if it's a key-preserved one
delete from t1
where id = 4;
select *
from t1_v;
ID COL1 AUTO_INC
---------- ---- ----------
2 abc 1
1 acb 1
6 hij 1
3 xyz 1
5 xyz 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 to use subquery to drop rows from Tab1 which are in Tab2 in Oracle SQL?

I have tables in Oracle SQL like below:
Tab1
ID
-----
1
2
3
Tab2
ID
-----
3
4
5
And I need to take values from Tab1 which are not in Tab2. I made query like below:
select ID
from Tab1
where ID not in (select ID from Tab2)
Above query does not work, how can I change it to achieve result as I need:
ID
---
1
2
I can add that I prefere to use subquery in this problem, how can I do that in Oracle SQL ?
With the MINUS set operator:
SQL> with
2 tab1 (id) as
3 (select 1 from dual union all
4 select 2 from dual union all
5 select 3 from dual
6 ),
7 tab2 (id) as
8 (select 3 from dual union all
9 select 4 from dual union all
10 select 5 from dual
11 )
12 select id from tab1
13 minus
14 select id from tab2;
ID
----------
1
2
SQL>
BTW, query you used (with a subquery) returns correct result; did you mean to say that you prefer NOT to use a subquery?
<snip>
12 select id from tab1
13 where id not in (select id from tab2);
ID
----------
1
2
I tried this code and it worked fine :
select ID
from Table1
where ID not in (select ID from Table2)
You cant DROP rows from a table, but you can DELETE them.
So correcting you title to
How to use subquery to DELETE rows from Tab1 which are in Tab2 in Oracle SQL?
do so:
delete from tab1
where id in (select id from tab2);
1 row deleted.
select * from tab1;
ID
----------
1
2
Do not forget to commit to make the change permanent.

Oracle: Select rows when value in one column changes

I have the following table:
PLACE USER_ID Date
---------- ---------- -----------------------------
ABC 4 14/04/20 12:05:29,255000000
ABC 4 14/04/20 15:42:28,389000000
ABC 4 14/04/20 18:33:20,202000000
ABC 4 14/04/20 22:51:28,339000000
XYZ 4 14/04/20 11:07:23,335000000
XYZ 2 14/04/20 12:15:12,123000000
ABC 4 13/04/20 22:09:33,255000000
QWE 4 13/04/20 10:18:29,144000000
XYZ 2 14/04/20 10:05:47,255000000
And I need to get the rows when the place changes order by date for the user_id that I select.
So the desired result should be this (for user_id 4):
PLACE USER_ID DATE
---------- ---------- -----------------------------
ABC 4 14/04/20 12:05:29,255000000
XYZ 4 14/04/20 11:07:23,335000000
ABC 4 13/04/20 22:09:33,255000000
QWE 4 13/04/20 10:18:29,144000000
I tried with min date but in my example, I lose data if the user goes back to that place:
SELECT MIN(DATE), PLACE FROM user_places WHERE USER_ID=4 GROUP BY PLACE
Result I get (missing one row):
PLACE USER_ID DATE
---------- ---------- -----------------------------
XYZ 4 14/04/20 11:07:23,335000000
ABC 4 13/04/20 22:09:33,255000000
QWE 4 13/04/20 10:18:29,144000000
Thanks in advance!
In Oracle 12.1 and higher, gaps-and-islands problems like this one are an easy task for the match_recognize clause. For example:
Table setup
alter session set nls_timestamp_format = 'dd/mm/rr hh24:mi:ss,ff';
create table user_places (place, user_id, date_) as
select 'ABC', 4, to_timestamp('14/04/20 12:05:29,255000000') from dual union all
select 'ABC', 4, to_timestamp('14/04/20 15:42:28,389000000') from dual union all
select 'ABC', 4, to_timestamp('14/04/20 18:33:20,202000000') from dual union all
select 'ABC', 4, to_timestamp('14/04/20 22:51:28,339000000') from dual union all
select 'XYZ', 4, to_timestamp('14/04/20 11:07:23,335000000') from dual union all
select 'XYZ', 2, to_timestamp('14/04/20 12:15:12,123000000') from dual union all
select 'ABC', 4, to_timestamp('13/04/20 22:09:33,255000000') from dual union all
select 'QWE', 4, to_timestamp('13/04/20 10:18:29,144000000') from dual union all
select 'XYZ', 2, to_timestamp('14/04/20 10:05:47,255000000') from dual
;
commit;
Query and output
select place, user_id, date_
from (select * from user_places where user_id = 4)
match_recognize (
order by date_
all rows per match
pattern (a {- b* -} )
define b as place = a.place
)
order by date_ desc -- if needed
;
PLACE USER_ID DATE_
----- ------- ---------------------------
ABC 4 14/04/20 12:05:29,255000000
XYZ 4 14/04/20 11:07:23,335000000
ABC 4 13/04/20 22:09:33,255000000
QWE 4 13/04/20 10:18:29,144000000
A few things to note here:
DATE is a reserved keyword. Not a good column name. I used DATE_
instead; notice the trailing underscore.
I hardcoded the value 4. Of course, the better practice is to make that into a bind variable.
If you really only need to do this for one user_id at a time, it is most efficient to do what I did - filter the rows first, in a subquery. However, if you need to do this for all user id's in the same query, you don't need a subquery; you select from the table itself, and you need to add partition by user_id right at the top of the match_recognize clause, before order by date_.
You can use lag() in a subquery to retrieve the "previous" place, and then filter on rows where the previous place is different that the current place:
select place, user_id, date
from (
select t.*, lag(place) over(partition by user_id order by date) lag_place
from mytable t
) t
where lag_place is null or place <> lag_place
This gives you the expected output for all users. If you want only for user 4, then you can filter in the subquery (and there is no need to partition by user):
select place, user_id, date
from (
select t.*, lag(place) over(order by date) lag_place
from mytable t
where user_id = 4
) t
where lag_place is null or place <> lag_place

Show multiple rows from single row data based on column values

I have a data below
ID DATE COMPLIANCE ISBREAKREDUCED ISSECONDMEALBREAKREDUCED
1208240 4/12/2015 2 1 1
How do I use it in my base query to show different rows for column ISBREAKREDUCED & ISSECONDMEALBREAKREDUCED if there values are 1.
This table join condition is based on id and date fields which is why it selects single row. How can I split this row into a multiple rows in the output?
I think you are looking for a typical UNPIVOT query.
You could do it as:
SQL> WITH DATA AS(
2 SELECT 1208240 ID, 1 ISBREAKREDUCED, 1 ISSECONDMEALBREAKREDUCED FROM dual UNION ALL
3 SELECT 1208241 ID, 2 ISBREAKREDUCED, 3 ISSECONDMEALBREAKREDUCED FROM dual
4 )
5 SELECT *
6 FROM
7 (SELECT *
8 FROM DATA UNPIVOT (ISSECONDMEALBREAKREDUCED FOR ISBREAKREDUCED IN (ISBREAKREDUCED AS 1, ISSECONDMEALBREAKREDUCED AS 1))
9 )
10 WHERE ISBREAKREDUCED =1
11 AND ISSECONDMEALBREAKREDUCED =1
12 /
ID ISBREAKREDUCED ISSECONDMEALBREAKREDUCED
---------- -------------- ------------------------
1208240 1 1
1208240 1 1
SQL>

Projection/Replication in SQL Query?

My SQL is a bit rusty -- is there a SQL way to project an input table that looks something like this:
Name SlotValue Slots
---- --------- -----
ABC 3 1
ABC 4 2
ABC 6 5
Into a 'projected' result table that looks like this:
Name SlotSum Slot
---- ------- ----
ABC 13 1
ABC 10 2
ABC 6 3
ABC 6 4
ABC 6 5
In other words, the result set should contain a number of rows equal to MAX(Slots), enumerated (Slot) from 1 to MAX(Slots), and Sum for each of these 'slots' should reflect the sum of the SlotValues projected out to the 'Slots' position. for the pathological case:
Name SlotValue Slots
---- --------- -----
ABC 4 3
we should get:
Name SlotSum Slot
---- ------- ----
ABC 4 1
ABC 4 2
ABC 4 3
The summation logic is pretty straightforward -- project each SlotValue out to the number of Slots:
SlotValue SlotValue SlotValue Slot Sum
--------- --------- --------- ---- ---
3 4 6 1 13 (3+4+6)
0 4 6 2 10 (0+4+6)
0 0 6 3 6 (0+0+6)
0 0 6 4 6 (0+0+6)
0 0 6 5 6 (0+0+6)
UPDATE: In the end I used a variant of LOCALGHOST's approach in a stored proc. I was hoping there might be a way to do this without a loop.
I'm not sure you'll be able to do this in a view. You'd have to use a procedure.
You could make ProjectedTable a temporary/variable table in the procedure as well. I'd be really interested to see how you'd get this into a view because you need to dynamically generate a range of numbers.
declare #maxSlot int
set #maxSlot = select max(slots) from SlotTable
truncate ProjectedTable
while #i > 0
begin
insert into ProjectedTable (
SlotSum
,Slot
) values (
(select sum(slotValue) from SlotTable where slots >= #maxSlot)
,#maxSlot
)
set #maxSlot = #maxSlot - 1
end
select SlotSum, Slot from ProjectedTable
Here you go. This will do up to 100 slots in its current form. You can use your imagination to accommodate more.
DECLARE #SLOT TABLE
(
SlotName varchar(25) NOT NULL,
SlotValue int NOT NULL,
Slot int NOT NULL
)
INSERT INTO #SLOT (SlotName, SlotValue, Slot)
SELECT 'ABC', 3, 1
UNION
SELECT 'ABC', 4, 2
UNION
SELECT 'ABC', 6, 5
SELECT
CASE
WHEN SLOT.SlotName IS NOT NULL THEN SLOT.SlotName
ELSE
COALESCE(
(SELECT TOP 1 SL.SlotName FROM #SLOT AS SL WHERE SL.Slot < SLOT_PROJECT.Slot ORDER BY SL.Slot DESC),
(SELECT TOP 1 SL.SlotName FROM #SLOT AS SL WHERE SL.Slot > SLOT_PROJECT.Slot ORDER BY SL.Slot ASC)
)
END AS SlotName,
(
SELECT
SUM(SLOT10.SlotValue)
FROM
#SLOT AS SLOT10
WHERE
SLOT10.Slot >= SLOT_PROJECT.Slot
) AS SlotSum,
SLOT_PROJECT.Slot
FROM
(
SELECT
(TENS.Seq + ONES.Seq) AS Slot
FROM
(
SELECT 1 AS Seq
UNION ALL
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 4
UNION ALL
SELECT 5
UNION ALL
SELECT 6
UNION ALL
SELECT 7
UNION ALL
SELECT 8
UNION ALL
SELECT 9
) AS ONES
CROSS JOIN
(
SELECT 0 AS Seq
UNION ALL
SELECT 10
UNION ALL
SELECT 20
UNION ALL
SELECT 30
UNION ALL
SELECT 40
UNION ALL
SELECT 50
UNION ALL
SELECT 60
UNION ALL
SELECT 70
UNION ALL
SELECT 80
UNION ALL
SELECT 90
) AS TENS
WHERE
(TENS.Seq + ONES.Seq) <= (SELECT MAX(Slot) FROM #SLOT)
) AS SLOT_PROJECT
LEFT JOIN #SLOT AS SLOT ON
SLOT.Slot = SLOT_PROJECT.Slot