Oracle Update using Lag function - sql

I am trying to use a lag function to update effective start dates in a SCD2 dimension. I am trying to use a subquery to self join the table based on the PK. The update will not update based on the previous end date and instead will only update to the default value (which is itself). When I remove the default value I get an error because the effective start date cannot be null. When I just use the select I get the desired result.
Any assistance would be greatly appreciated, I'm sure its something simple!
update schema.d_account ac1
set effective_start_dt = (select lag(effective_end_dt, 1, effective_start_dt) over(partition by id order by effective_end_dt asc)
from schema.d_account ac2
where ac1.account_dim_key = ac2.account_dim_key),
audit_last_update_dt = sysdate,
where id in '0013600000USKLqAAP'
Table:
Desired results:

There could be one of these reasons why your update is not working. I do not know the exact structure of your table. You have not provided the sample data. If your account_dim_key is a unique for each row then the select lag() ... from schema.d_account ac2 where ac1.account_dim_key = ac2.account_dim_key) will return one row and you will effectively update effective_start_dt to effective_start_dt (being the default value of the lag function)
If your account_dim_key is the same for all these rows you have provided as a sample then the select lag() ... from schema.d_account ac2 where ac1.account_dim_key = ac2.account_dim_key) will return multiple rows and Oracle will complain that the UPDATE is not possible (there is a specific error message, I do not remember the exact wording.
To make your query work you need to use a different approach:
update schema.d_account ac1
set effective_start_dt = (select prev_dt from
(select lag(effective_end_dt, 1, effective_start_dt) over(partition by id order by effective_end_dt asc) as prev_dt
, ROWID as rid
from schema.d_account ac2) a where a.rid = ROWID),
audit_last_update_dt = sysdate,
where id in '0013600000USKLqAAP'
So, basically you have a sub-query a with the ROWID column where you build the previous date. For the UPDATE statement you join this sub-query by the ROWID.
Note: if your account_dim_key is unique for each row you can use it in place of the ROWID: you may get better performance depending on the indexes you have for your table
UPDATE: the above query may give you a bad performance. You will be better off with the MERGE statement below:
MERGE INTO (SELECT id, effective_start_dt, ROWID rid, audit_last_update_dt
FROM schema.d_account WHERE id in '0013600000USKLqAAP') ac1
USING (select lag(effective_end_dt, 1, effective_start_dt) over(partition by id order by effective_end_dt asc) as prev_dt
, ROWID as rid
from schema.d_account) ac2
ON (ac1.rid = ac2.rid)
WHEN MATCHED THEN UPDATE SET ac1.effective_start_dt = ac2.prev_dt,
ac1.audit_last_update_dt = sysdate;

Related

Updating all the record for a column in a table with value from another table

Hi I want to update all the values of RequestId column of IIL_CHANGE_REQUEST Table with the RequestId of TABLE_NAME_4MINS Table where REQUESTBY column in both tables are same. I am trying to do this in oracle daatabalse(sql developer)
My query:
Update IIL_CHANGE_REQUEST
set REQUESTID=(SELECT TABLE_NAME_4MINS.REQUESTID
FROM TABLE_NAME_4MINS
WHERE IIL_CHANGE_REQUEST.REQUESTBY = TABLE_NAME_4MINS.REQUESTBY)
WHERE EXISTS (SELECT TABLE_NAME_4MINS.REQUESTID
FROM TABLE_NAME_4MINS
WHERE IIL_CHANGE_REQUEST.REQUESTBY = TABLE_NAME_4MINS.REQUESTBY)
But every time I do this I get an error saying:
Error starting at line : 1 in command -
Update IIL_CHANGE_REQUEST
set REQUESTID=(SELECT TABLE_NAME_4MINS.REQUESTID
FROM TABLE_NAME_4MINS
WHERE IIL_CHANGE_REQUEST.REQUESTBY = TABLE_NAME_4MINS.REQUESTBY)
WHERE EXISTS (SELECT TABLE_NAME_4MINS.REQUESTID
FROM TABLE_NAME_4MINS
WHERE IIL_CHANGE_REQUEST.REQUESTBY = TABLE_NAME_4MINS.REQUESTBY)
Error report -
ORA-01427: single-row subquery returns more than one row
Please anyone help how can I do it.
It depends on what you want to do in such cases. If you don't really care, take any value, for example minimum:
update iil_change_request a
set a.requestid = (select min(b.requestid) --> here
from table_name_4mins b
where a.requestby = b.requestby)
where exists (select c.requestid
from table_name_4mins c
where a.requestby = c.requestby);
If that's not what you want, then you'll have to figure out what to do with those "duplicates". Perhaps you'll have to include yet another WHERE condition, or fix data, or ... who knows? I don't, while you should.
You need to find a condition to narrow down returned rows to the only per REQESTSTBY, for example you can replace ... with a column name in the query below to return just first row as per order-by:
MERGE INTO IIL_CHANGE_REQUEST r
USING (
SELECT *
FROM (
SELECT REQUESTBY, REQUESTID
,row_number()over(partition by REQUESTBY order by ...) rn
FROM TABLE_NAME_4MINS
)
where rn=1
) t
ON (r.REQUESTBY = t.REQUESTBY)
WHEN MATCHED THEN UPDATE
set REQUESTID=t.REQUESTID;

Postgres remove gaps in row numbers

I have a table called assignment with an index and unit_id columns. Every assignment in a unit has a unique index. This way, assignments in a unit can be reordered by swapping their indexes.
I'm trying to figure out the best way to remove potential gaps in the indexes (if a row is deleted, the indexes will go 0, 1, 3 for example). My current solution is to loop through each assignment in a unit programmatically and run an UPDATE query if its index doesn't match the loop index. Like this:
let i = 0;
for (const assignmentId of assignmentIds) {
await Assignment.query()
.patch({ index: i })
.where('id', assignmentId);
i++;
}
I'm trying to figure out how to do this with a single query using the ROW_NUMBER function like this:
UPDATE assignment SET
index = subquery.new_index - 1
FROM (
SELECT
ROW_NUMBER() OVER () as new_index
FROM assignment
WHERE assignment.unit_id = 35
ORDER BY assignment.index
) as subquery
WHERE unit_id=35;
But when I run this, it just sets all the indexes in unit 35 to 1. Why is this happening?
You need to provide a new index for each row by identifying the rows by old index:
update assignment
set index = subquery.new_index - 1
from (
select index as old_index, row_number() over (order by index) as new_index
from assignment
where assignment.unit_id = 35
) as subquery
where unit_id = 35
and old_index = index
and new_index <> old_index + 1; -- eliminate unnecessary updates
You need to join the subquery on the PK column (you neend you need to include that in the sub-query to be able to do that). The order by needs to go into the window function, not the overall query:
UPDATE assignment
SET index = subquery.new_index - 1
FROM (
SELECT pk_column,
ROW_NUMBER() OVER (partition by unit_id order by index) as new_index
FROM assignment
) as subquery
WHERE subquery.pk_column = assignment.pk_column
If you only want to do that for a single unit, you can add AND unit_id = 35 to the WHERE clause of the UPDATE statement.
If you don't mind the original index, you can run update with a temp sequence:
CREATE TEMP SEQUENCE new_index;
SELECT SETVAL('new_index', 1);
And run the update based on your unit:
UPDATE assignment SET index=nextval('new_index') WHERE unit_id=35;
This will update all lines on the unit_id=35 on the index column.
What's the diference from your FROM clause? The nextval() executes a function for every line, returning the next number of sequence.
Since you specifically indicated unit_id was not unique ("sets all the indexes in unit 35") you can use rownum() to project the expected index for each intem (as others have indicated). But I also hate using of magic numbers, the following does not depend on them.
with expectations as
(select *
from (
select unit_id
, unit_index
, row_number() over (partition by unit_id order by unit_id, unit_index) exp_index
from units
) au
where unit_index != exp_index
)
update units u
set unit_index = exp_index
from expectations e
where (u.unit_id, u.unit_index) = (e.unit_id,e.unit_index);
See fiddle here.

SQL Query : should return Single Record if Search Condition met, otherwise return Multiple Records

I have table with Billions of Records, Table structure is like :
ID NUMBER PRIMARY KEY,
MY_SEARCH_COLUMN NUMBER,
MY_SEARCH_COLUMN will have Numeric value upto 15 Digit in length.
What I want is, if any specific record is matched, I will have to get that matched value only,
i.e. : If I enter WHERE MY_SEARCH_COLUMN = 123454321 and table has value 123454321 then this only should be returned.
But if exact value is not matched, I will have to get next 10 values from the table.
i.e. : if I enter WHERE MY_SEARCH_COLUMN = 123454321 and column does not have the value 123454321 then it should return 10 values from the table which is greater than 123454321
Both the case should be covered in single SQL Query, and I have have to keep in mind the Performance of the Query. I have already created Index on the MY_SEARCH_COLUMN columns, so other suggestions are welcome to improve the Performance.
This could be tricky to do without using a proc or maybe some dynamic SQL, but we can try using ROW_NUMBER here:
WITH cte AS (
SELECT ID, MY_SEARCH_COLUMN,
ROW_NUMBER() OVER (ORDER BY MY_SEARCH_COLUMN) rn
FROM yourTable
WHERE MY_SEARCH_COLUMN >= 123454321
)
SELECT *
FROM cte
WHERE rn <= CASE WHEN EXISTS (SELECT 1 FROM yourTable WHERE MY_SEARCH_COLUMN = 123454321)
THEN 1
ELSE 10 END;
The basic idea of the above query is that we assign a row number to all records matching the target or greater. Then, we query using either a row number of 1, in case of an exact match, or all row numbers up to 10 in case of no match.
SELECT *
FROM your_table AS src
WHERE src.MY_SEARCH_COLUMN = CASE WHEN EXISTS (SELECT 1 FROM your_table AS src2 WITH(NOLOCK) WHERE src2.MY_SEARCH_COLUMN = 123456321)
THEN 123456321
ELSE src.MY_SEARCH_COLUMN
END

Update columns in DB2 using randomly chosen static values provided at runtime

I would like to update rows with values chosen randomly from a set of possible values.
Ideally I would be able to provide this values at runtime, using JdbcTemplate from Java application.
Example:
In a table, column "name" can contain any name. The goal is to run through the table and change all names to equal to either "Bob" or "Alice".
I know that this can be done by creating a sql function. I tested it and it was fine but I wonder if it is possible to just use simple query?
This will not work, seems that the value is computed once, and applied to all rows:
UPDATE test.table
SET first_name =
(SELECT a.name
FROM
(SELECT a.name, RAND() idx
FROM (VALUES('Alice'), ('Bob')) AS a(name) ORDER BY idx FETCH FIRST 1 ROW ONLY) as a)
;
I tried using MERGE INTO, but it won't even run (possible_names is not found in SET query). I am yet to figure out why:
MERGE INTO test.table
USING
(SELECT
names.fname
FROM
(VALUES('Alice'), ('Bob'), ('Rob')) AS names(fname)) AS possible_names
ON ( test.table.first_name IS NOT NULL )
WHEN MATCHED THEN
UPDATE SET
-- select random name
first_name = (SELECT fname FROM possible_names ORDER BY idx FETCH FIRST 1 ROW ONLY)
;
EDIT: If possible, I would like to only focus on fields being updated and not depend on knowing primary keys and such.
Db2 seems to be optimizing away the subselect that returns your supposedly random name, materializing it only once, hence all rows in the target table receive the same value.
To force subselect execution for each row you need to somehow correlate it to the table being updated, for example:
UPDATE test.table
SET first_name =
(SELECT a.name
FROM (VALUES('Alice'), ('Bob')) AS a(name)
ORDER BY RAND(ASCII(SUBSTR(first_name, 1, 1)))
FETCH FIRST 1 ROW ONLY)
or may be even
UPDATE test.table
SET first_name =
(SELECT a.name
FROM (VALUES('Alice'), ('Bob')) AS a(name)
ORDER BY first_name, RAND()
FETCH FIRST 1 ROW ONLY)
Now that the result of subselect seems to depend on the value of the corresponding row in the target table, there's no choice but to execute it for each row.
If your table has a primary key, this would work. I've assumed the PK is column id.
UPDATE test.table t
SET first_name =
( SELECT name from
( SELECT *, ROW_NUMBER() OVER(PARTITION BY id ORDER BY R) AS RN FROM
( SELECT *, RAND() R
FROM test.table, TABLE(VALUES('Alice'), ('Bob')) AS d(name)
)
)
AS u
WHERE t.id = u.id and rn = 1
)
;
There might be a nicer/more efficient solution, but I'll leave that to others.
FYI I used the following DDL and data to test the above.
create table test.table(id int not null primary key, first_name varchar(32));
insert into test.table values (1,'Flo'),(2,'Fred'),(3,'Sue'),(4,'John'),(5,'Jim');

PL/SQL Increase value of new row, with value of previous

I need to increase value of next NEWLOSAL row, to be bigger than one, from previous of NEWHISA.
Like HISAL and LOSAL column.
NEWLOSAL need to be previous NEWHISAL + 1.
not that sure if this is what you want:
update table1 t1
set t1.Newlosal=case when t1.grade=1 then (t1.Newhisal+1) else (select t2.Newhisal+1 from table1 t2 where t2.grade = (t1.grade-1)) end
WHERE EXISTS (
SELECT 1
FROM table1 t2
WHERE t2.grade=(t1.grade-1))
This can efficiently be done using the merge statement and a window function:
merge into table1 tg
using
(
select id, -- I assume this is the PK column
lag(newhisal) over (order by grade) + 1 as new_losal
from table1
) nv on (nv.id = tg.id)
when matched then update
set tg.newlosal = nv.new_losal;
In SQL rows in a table (or a result) or not ordered, so the concept of a "previous" row only makes sense if you define a sort order. That's what the over (order by grade) does in the window function. From the screen shot I can not tell by which column this should be sorted.
The screen shot also doesn't reveal the primary key column of your table. I assumed it's named ID. You have to change that to reflect your real PK column name.
I also didn't include a partition by clause in the window function assuming that the formula should be applied for all rows in the same way. If this is not the case you need to be more specific with your sample data.