Multiple Columns and different Join Conditions for Oracle update - sql

I have 2 tables , 1 is location and the Other one is Look up table. I have to look into the look up table for the location values and if they are present mark them as 'Y' and 'N' along with their corresponding values
I have written individual update Statements as below:
**Location1,L1value**
Update Location
set (Location1,L1value) =
(select UPPER(VAlue),'Y' from Location_lookup where trim(Location1)=Location
where exists (select 1 from Location_lookup where trim(Location1)=Location);
commit;
**Location2,value**
Update Location
set (Location2,L2value) =
(select UPPER(VAlue),'Y' from Location_lookup where trim(Location2)=Location
where exists (select 1 from Location_lookup where trim(Location2)=Location);
commit;
Similarly for 3rd flag and value.
Is there a way to write single update for all the three conditions? Reason why I am looking for single update is that I have 10+ million records and I do not want to scan the records three different times. The lookup table has > 32 million records.

Here is a solution which uses Oracle's bulk FORALL ... UPDATE capability. This is not quite as performative as a pure SQL solution but it is simpler to code and the efficiency difference probably won't matter much for 10 million rows on a modern enterprise server, especially if this is a one-off exercise.
Points to note:
You don't say whether LOCATION has a primary key. For this answer I have assumed it has an ID column. The solution won't work if there isn't a primary key, but if your table doesn't have a primary key you've likely got bigger problems.
Your question mentions setting the FLAG columns "as 'Y' and 'N'" but the required output only shows 'Y' setting. I have included processing for 'N' but see the coda underneath.
declare
cursor get_locations is
with lkup as (
select *
from location_lookup
)
select locn.id
,locn.location1
,upper(lup1.value) as l1value
,nvl2(lup1.value, 'Y', 'N') as l1flag
,locn.location2
,upper(lup2.value) as l2value
,nvl2(lup2.value, 'Y', 'N') as l2flag
,locn.location3
,upper(lup3.value) as l3value
,nvl2(lup3.value, 'Y', 'N') as l3flag
from location locn
left outer join lkup lup1 on trim(locn.location1) = lup1.location
left outer join lkup lup2 on trim(locn.location2) = lup2.location
left outer join lkup lup3 on trim(locn.location3) = lup3.location
where lup1.location is not null
or lup2.location is not null
or lup3.location is not null;
type t_locations_type is table of get_locations%rowtype index by binary_integer;
t_locations t_locations_type;
begin
open get_locations;
loop
fetch get_locations bulk collect into t_locations limit 10000;
exit when t_locations.count() = 0;
forall idx in t_locations.first() .. t_locations.last()
update location
set l1value = t_locations(idx).l1value
,l1flag = t_locations(idx).l1flag
,l2value = t_locations(idx).l2value
,l2flag = t_locations(idx).l2flag
,l3value = t_locations(idx).l3value
,l3flag = t_locations(idx).l3flag
where id = t_locations(idx).id;
end loop;
close get_locations;
end;
/
There is a working demo on db<>fiddle here. The demo output doesn't exactly match the sample output posted in the query, because that doesn't the given input data.
Setting flags to 'Y' or 'N'?
The code above uses left outer joins on the lookup table. If a row is found the NVL2() function will return 'Y' otherwise it returns 'N'. This means the flag columns are always populated, regardless of whether the value columns are. The exception is for rows which have no matches in LOCATION_LOOKUP for any location (ID=4000 in my demo). In this case the flag columns will be null. This inconsistency follows from the inconsistencies in the question.
To resolve it:
if you want all flag columns to be populated with 'N' remove the WHERE clause from the get_locations cursor query.
if you don't want to set flags to 'N' change the NVL2() function calls accordingly: nvl2(lup1.value, 'Y', null) as l1flag

Related

Improving insert query for SCD2 solution

I have two insert statements. The first query is to inserta new row if the id doesn't exist in the target table. The second query inserts to the target table only if the joined id hash value is different (indicates that the row has been updated in the source table) and the id in the source table is not null. These solutions are meant to be used for my SCD2 solution, which will be used for inserts of hundreds thousands of rows. I'm trying not to use the MERGE statement for practices.
The columns "Current" value 1 indicates that the row is new and 0 indicates that the row has expired. I use this information later to expire my rows in the target table with my update queries.
Besides indexing is there a more competent and effective way to improve my insert queries in a way that resembles the like of the SCD2 merge statement for inserting new/updated rows?
Query:
Query 1:
INSERT INTO TARGET
SELECT Name,Middlename,Age, 1 as current,Row_HashValue,id
from Source s
Where s.id not in (select id from TARGET) and s.id is not null
Query 2:
INSERT INTO TARGET
SELECT Name,Middlename,Age,1 as current ,Row_HashValue,id
FROM SOURCE s
LEFT JOIN TARGET t ON s.id = t.id
AND s.Row_HashValue = t.Row_HashValue
WHERE t.Row_HashValue IS NULL and s.ID IS NOT NULL
You can use WHERE NOT EXISTS, and have just one INSERT statement:
INSERT INTO TARGET
SELECT Name,Middlename,Age,1 as current ,Row_HashValue,id
FROM SOURCE s
WHERE NOT EXISTS (
SELECT 1
FROM TARGET t
WHERE s.id = t.id
AND s.Row_HashValue = t.Row_HashValue)
AND s.ID IS NOT NULL;

Update statement using a WHERE clause that contains columns with null Values

I am updating a column on one table using data from another table. The WHERE clause is based on multiple columns and some of the columns are null. From my thinking, this nulls are what are throwing off your standard UPDATE TABLE SET X=Y WHERE A=B statement.
See this SQL Fiddle of the two tables where am trying to update table_one based on data from table_two.
My query currently looks like this:
UPDATE table_one SET table_one.x = table_two.y
FROM table_two
WHERE
table_one.invoice_number = table_two.invoice_number AND
table_one.submitted_by = table_two.submitted_by AND
table_one.passport_number = table_two.passport_number AND
table_one.driving_license_number = table_two.driving_license_number AND
table_one.national_id_number = table_two.national_id_number AND
table_one.tax_pin_identification_number = table_two.tax_pin_identification_number AND
table_one.vat_number = table_two.vat_number AND
table_one.ggcg_number = table_two.ggcg_number AND
table_one.national_association_number = table_two.national_association_number
The query fails for some rows in that table_one.x isn't getting updated when any of the columns in either table are null. i.e. it only gets updated when all columns have some data.
This question is related to my earlier one here on SO where I was getting distinct values from a large data set using Distinct On. What I now I want is to populate the large data set with a value from the table which has unique fields.
UPDATE
I used the first update statement provided by #binotenary. For small tables, it runs in a flash. Example is had one table with 20,000 records and the update was completed in like 20 seconds. But another table with 9 million plus records has been running for 20 hrs so far!. See below the output for EXPLAIN function
Update on table_one (cost=0.00..210634237338.87 rows=13615011125 width=1996)
-> Nested Loop (cost=0.00..210634237338.87 rows=13615011125 width=1996)
Join Filter: ((((my_update_statement_here))))
-> Seq Scan on table_one (cost=0.00..610872.62 rows=9661262 width=1986)
-> Seq Scan on table_two (cost=0.00..6051.98 rows=299998 width=148)
The EXPLAIN ANALYZE option took also forever so I canceled it.
Any ideas on how to make this type of update faster? Even if it means using a different update statement or even using a custom function to loop through and do the update.
Since null = null evaluates to false you need to check if two fields are both null in addition to equality check:
UPDATE table_one SET table_one.x = table_two.y
FROM table_two
WHERE
(table_one.invoice_number = table_two.invoice_number
OR (table_one.invoice_number is null AND table_two.invoice_number is null))
AND
(table_one.submitted_by = table_two.submitted_by
OR (table_one.submitted_by is null AND table_two.submitted_by is null))
AND
-- etc
You could also use the coalesce function which is more readable:
UPDATE table_one SET table_one.x = table_two.y
FROM table_two
WHERE
coalesce(table_one.invoice_number, '') = coalesce(table_two.invoice_number, '')
AND coalesce(table_one.submitted_by, '') = coalesce(table_two.submitted_by, '')
AND -- etc
But you need to be careful about the default values (last argument to coalesce).
It's data type should match the column type (so that you don't end up comparing dates with numbers for example) and the default should be such that it doesn't appear in the data
E.g coalesce(null, 1) = coalesce(1, 1) is a situation you'd want to avoid.
Update (regarding performance):
Seq Scan on table_two - this suggests that you don't have any indexes on table_two.
So if you update a row in table_one then to find a matching row in table_two the database basically has to scan through all the rows one by one until it finds a match.
The matching rows could be found much faster if the relevant columns were indexed.
On the flipside if table_one has any indexes then that slows down the update.
According to this performance guide:
Table constraints and indexes heavily delay every write. If possible, you should drop all the indexes, triggers and foreign keys while the update runs and recreate them at the end.
Another suggestion from the same guide that might be helpful is:
If you can segment your data using, for example, sequential IDs, you can update rows incrementally in batches.
So for example if table_one an id column you could add something like
and table_one.id between x and y
to the where condition and run the query several times changing the values of x and y so that all rows are covered.
The EXPLAIN ANALYZE option took also forever
You might want to be careful when using the ANALYZE option with EXPLAIN when dealing with statements with sideffects.
According to documentation:
Keep in mind that the statement is actually executed when the ANALYZE option is used. Although EXPLAIN will discard any output that a SELECT would return, other side effects of the statement will happen as usual.
Try below, similar to the above #binoternary. Just beat me to the answer.
update table_one
set column_x = (select column_y from table_two
where
(( table_two.invoice_number = table_one.invoice_number)OR (table_two.invoice_number IS NULL AND table_one.invoice_number IS NULL))
and ((table_two.submitted_by=table_one.submitted_by)OR (table_two.submitted_by IS NULL AND table_one.submitted_by IS NULL))
and ((table_two.passport_number=table_one.passport_number)OR (table_two.passport_number IS NULL AND table_one.passport_number IS NULL))
and ((table_two.driving_license_number=table_one.driving_license_number)OR (table_two.driving_license_number IS NULL AND table_one.driving_license_number IS NULL))
and ((table_two.national_id_number=table_one.national_id_number)OR (table_two.national_id_number IS NULL AND table_one.national_id_number IS NULL))
and ((table_two.tax_pin_identification_number=table_one.tax_pin_identification_number)OR (table_two.tax_pin_identification_number IS NULL AND table_one.tax_pin_identification_number IS NULL))
and ((table_two.vat_number=table_one.vat_number)OR (table_two.vat_number IS NULL AND table_one.vat_number IS NULL))
and ((table_two.ggcg_number=table_one.ggcg_number)OR (table_two.ggcg_number IS NULL AND table_one.ggcg_number IS NULL))
and ((table_two.national_association_number=table_one.national_association_number)OR (table_two.national_association_number IS NULL AND table_one.national_association_number IS NULL))
);
You can use a null check function like Oracle's NVL.
For Postgres, you will have to use coalesce.
i.e. your query can look like :
UPDATE table_one SET table_one.x =(select table_two.y from table_one,table_two
WHERE
coalesce(table_one.invoice_number,table_two.invoice_number,1) = coalesce(table_two.invoice_number,table_one.invoice_number,1)
AND
coalesce(table_one.submitted_by,table_two.submitted_by,1) = coalesce(table_two.submitted_by,table_one.submitted_by,1))
where table_one.table_one_pk in (select table_one.table_one_pk from table_one,table_two
WHERE
coalesce(table_one.invoice_number,table_two.invoice_number,1) = coalesce(table_two.invoice_number,table_one.invoice_number,1)
AND
coalesce(table_one.submitted_by,table_two.submitted_by,1) = coalesce(table_two.submitted_by,table_one.submitted_by,1));
Your current query joins two tables using Nested Loop, which means that the server processes
9,661,262 * 299,998 = 2,898,359,277,476
rows. No wonder it takes forever.
To make the join efficient you need an index on all joined columns. The problem is NULL values.
If you use a function on the joined columns, generally the index can't be used.
If you use an expression like this in the JOIN:
coalesce(table_one.invoice_number, '') = coalesce(table_two.invoice_number, '')
an index can't be used.
So, we need an index and we need to do something with NULL values to make index usable.
We don't need to make any changes in table_one, because it has to be scanned in full in any case.
But, table_two definitely can be improved. Either change the table itself, or create a separate (temporary) table. It has only 300K rows, so it should not be a problem.
Make all columns that are used in the JOIN to be NOT NULL.
CREATE TABLE table_two (
id int4 NOT NULL,
invoice_number varchar(30) NOT NULL,
submitted_by varchar(20) NOT NULL,
passport_number varchar(30) NOT NULL,
driving_license_number varchar(30) NOT NULL,
national_id_number varchar(30) NOT NULL,
tax_pin_identification_number varchar(30) NOT NULL,
vat_number varchar(30) NOT NULL,
ggcg_number varchar(30) NOT NULL,
national_association_number varchar(30) NOT NULL,
column_y int,
CONSTRAINT table_two_pkey PRIMARY KEY (id)
);
Update the table and replace NULL values with '', or some other appropriate value.
Create an index on all columns that are used in JOIN plus column_y. column_y has to be included last in the index. I assume that your UPDATE is well-formed, so index should be unique.
CREATE UNIQUE INDEX IX ON table_two
(
invoice_number,
submitted_by,
passport_number,
driving_license_number,
national_id_number,
tax_pin_identification_number,
vat_number,
ggcg_number,
national_association_number,
column_y
);
The query will become
UPDATE table_one SET table_one.x = table_two.y
FROM table_two
WHERE
COALESCE(table_one.invoice_number, '') = table_two.invoice_number AND
COALESCE(table_one.submitted_by, '') = table_two.submitted_by AND
COALESCE(table_one.passport_number, '') = table_two.passport_number AND
COALESCE(table_one.driving_license_number, '') = table_two.driving_license_number AND
COALESCE(table_one.national_id_number, '') = table_two.national_id_number AND
COALESCE(table_one.tax_pin_identification_number, '') = table_two.tax_pin_identification_number AND
COALESCE(table_one.vat_number, '') = table_two.vat_number AND
COALESCE(table_one.ggcg_number, '') = table_two.ggcg_number AND
COALESCE(table_one.national_association_number, '') = table_two.national_association_number
Note, that COALESCE is used only on table_one columns.
It is also a good idea to do UPDATE in batches, rather than the whole table at once. For example, pick a range of ids to update in a batch.
UPDATE table_one SET table_one.x = table_two.y
FROM table_two
WHERE
table_one.id >= <some_starting_value> AND
table_one.id < <some_ending_value> AND
COALESCE(table_one.invoice_number, '') = table_two.invoice_number AND
COALESCE(table_one.submitted_by, '') = table_two.submitted_by AND
COALESCE(table_one.passport_number, '') = table_two.passport_number AND
COALESCE(table_one.driving_license_number, '') = table_two.driving_license_number AND
COALESCE(table_one.national_id_number, '') = table_two.national_id_number AND
COALESCE(table_one.tax_pin_identification_number, '') = table_two.tax_pin_identification_number AND
COALESCE(table_one.vat_number, '') = table_two.vat_number AND
COALESCE(table_one.ggcg_number, '') = table_two.ggcg_number AND
COALESCE(table_one.national_association_number, '') = table_two.national_association_number
You can use coalesce function which will return true every time when any variable passed is null. Null check function will help you.
Null-related functions here.

Oracle SQL, trying to get one value from a select/join to use to update one column in one table?

I have one table with the following columns:
T_RESOLVED_DATE
I_HOUSEHOLD_NUMBER
I_RESOLVED_SET_NUMBER
I_STATION_CODE
I_RESOLVED_START_MIN
I_DURATION
I_PERSON_NUMBER
I_COVIEW_DEMO_ID
Initially, I_COVIEW_DEMO_ID is set to null.
Then I have another table with the following columns:
T_RESOLVED_DATE
I_HOUSEHOLD_NUMBER
I_PERSON_NUMBER
I_AGE
T_GENDER
I_COVIEW_DEMO_ID
I am trying to update I_COVIEW_DEMO_ID in the first table by using the value of I_COVIEW_DEMO_ID in the second table where the T_RESOLVED_DATE, I_HOUSEHOLD_NUMBER, and I_PERSON_NUMBER are equal in both tables. The first table may contain multiple rows with the same DATE, HOUSEHOLD_NUMBER, and PERSON_NUMBER, because the rows can vary by the rest of the columns.
I have tried to do a select and a group by which seems to get me part way there, but I am getting a "single-row subquery returns more than one row" error when I try to update the columns in the first table. This is what I've tried, along with variations of it:
UPDATE
Table1
SET
I_COVIEW_DEMO_ID =
(SELECT
b.I_COVIEW_DEMO_ID
FROM Table1 a,
Table2 b
WHERE a.I_HOUSEHOLD_NUMBER = b.I_HOUSEHOLD_NUMBER AND
a.I_PERSON_NUMBER = b.I_PERSON_NUMBER AND
a.T_RESOLVED_DATE = b.T_RESOLVED_DATE
GROUP BY b.I_COVIEW_DEMO_ID);
Any suggestions?
I was able to get it to work using this statement:
MERGE INTO table1 a
USING
(
SELECT DISTINCT
T_RESOLVED_DATE,
I_HOUSEHOLD_NUMBER,
I_PERSON_NUMBER,
I_COVIEW_DEMO_ID
FROM
table2
) b
ON
(
a.T_RESOLVED_DATE = b.T_RESOLVED_DATE
AND a.I_HOUSEHOLD_NUMBER = b.I_HOUSEHOLD_NUMBER
AND a.I_PERSON_NUMBER = b.I_PERSON_NUMBER
) WHEN MATCHED THEN
UPDATE SET
a.I_COVIEW_DEMO_ID = b.I_COVIEW_DEMO_ID;
As per our discussion on the comments this would be a simple PLSQL block to do what you need. I'm doing direct from my head without test, so you may need to fix some sintaxe mistake.
BEGIN
FOR rs IN ( SELECT I_HOUSEHOLD_NUMBER,
I_PERSON_NUMBER,
I_COVIEW_DEMO_ID,
T_RESOLVED_DATE
FROM Table2 ) LOOP
UPDATE Table1
SET I_COVIEW_DEMO_ID = rs.I_COVIEW_DEMO_ID
WHERE I_PERSON_NUMBER = rs.I_PERSON_NUMBER
AND I_HOUSEHOLD_NUMBER = rs.I_HOUSEHOLD_NUMBER
AND T_RESOLVED_DATE = rs.T_RESOLVED_DATE;
END LOOP;
--commit after all updates, if there is many rows you should consider in
--making commits by blocks. Define a count and increment it whithin the for
--after some number of updates you commit and restart the counter
COMMIT;
END;

Writing a single UPDATE statement that prevents duplicates

I've been trying for a few hours (probably more than I needed to) to figure out the best way to write an update sql query that will dissallow duplicates on the column I am updating.
Meaning, if TableA.ColA already has a name 'TEST1', then when I'm changing another record, then I simply can't pick a value for ColA to be 'TEST1'.
It's pretty easy to simply just separate the query into a select, and use a server layer code that would allow conditional logic:
SELECT ID, NAME FROM TABLEA WHERE NAME = 'TEST1'
IF TableA.recordcount > 0 then
UPDATE SET NAME = 'TEST1' WHERE ID = 1234
END IF
But I'm more interested to see if these two queries can be combined into a single query.
I am using Oracle to figure things out, but I'd love to see a SQL Server query as well. I figured a MERGE statement can work, but for obvious reasons you can't have the clause:
..etc.. WHEN NOT MATCHED UPDATE SET ..etc.. WHERE ID = 1234
AND you can't update a column if it's mentioned in the join (oracle limitation but not limited to SQL Server)
ALSO, I know you can put a constraint on a column that prevents duplicate values, but I'd be interested to see if there is such a query that can do this without using constraint.
Here is an example start-up attempt on my end just to see what I can come up with (explanations on it failed is not necessary):
ERROR: ORA-01732: data manipulation operation not legal on this view
UPDATE (
SELECT d.NAME, ch.NAME FROM (
SELECT 'test1' AS NAME, '2722' AS ID
FROM DUAL
) d
LEFT JOIN TABLEA a
ON UPPER(a.name) = UPPER(d.name)
)
SET a.name = 'test2'
WHERE a.name is null and a.id = d.id
I have tried merge, but just gave up thinking it's not possible. I've also considered not exists (but I'd have to be careful since I might accidentally update every other record that doesn't match a criteria)
It should be straightforward:
update personnel
set personnel_number = 'xyz'
where person_id = 1001
and not exists (select * from personnel where personnel_number = 'xyz');
If I understand correctly, you want to conditionally update a field, assuming the value is not found. The following query does this. It should work in both SQL Server and Oracle:
update table1
set name = 'Test1'
where (select count(*) from table1 where name = 'Test1') > 0 and
id = 1234

Check whether a table contains rows or not sql server 2005

How to Check whether a table contains rows or not sql server 2005?
For what purpose?
Quickest for an IF would be IF EXISTS (SELECT * FROM Table)...
For a result set, SELECT TOP 1 1 FROM Table returns either zero or one rows
For exactly one row with a count (0 or non-zero), SELECT COUNT(*) FROM Table
Also, you can use exists
select case when exists (select 1 from table)
then 'contains rows'
else 'doesnt contain rows'
end
or to check if there are child rows for a particular record :
select * from Table t1
where exists(
select 1 from ChildTable t2
where t1.id = t2.parentid)
or in a procedure
if exists(select 1 from table)
begin
-- do stuff
end
Like Other said you can use something like that:
IF NOT EXISTS (SELECT 1 FROM Table)
BEGIN
--Do Something
END
ELSE
BEGIN
--Do Another Thing
END
FOR the best performance, use specific column name instead of * - for example:
SELECT TOP 1 <columnName>
FROM <tableName>
This is optimal because, instead of returning the whole list of columns, it is returning just one. That can save some time.
Also, returning just first row if there are any values, makes it even faster. Actually you got just one value as the result - if there are any rows, or no value if there is no rows.
If you use the table in distributed manner, which is most probably the case, than transporting just one value from the server to the client is much faster.
You also should choose wisely among all the columns to get data from a column which can take as less resource as possible.
Can't you just count the rows using select count(*) from table (or an indexed column instead of * if speed is important)?
If not then maybe this article can point you in the right direction.
Fast:
SELECT TOP (1) CASE
WHEN **NOT_NULL_COLUMN** IS NULL
THEN 'empty table'
ELSE 'not empty table'
END AS info
FROM **TABLE_NAME**