Update table, but make a new updated table? - sql

I am comparing two tables A and B. A and B are months (we will go with JAN and FEB).
FEB has updated data that belongs in JAN.
I need to update the data like so
UPDATE A
SET A.x = B.x, A.y = B.y, A.z = B.z
FROM JAN A, FEB B
WHERE (A.x <> B.x OR A.y <> B.y OR A.z <> B.z) AND A.PK = B.PK
Now I want the above to not take place on the Original JAN table.
Should I go about it this way? Or is there a better way?
SELECT *
INTO JAN_UPDATED
FROM JAN
UPDATE A
SET A.x = B.x, A.y = B.y, A.z = B.z
FROM JAN_UPDATED A, FEB B
WHERE (A.x <> B.x OR A.y <> B.y OR A.z <> B.z) AND A.PK = B.PK
EDIT: I want all of the original values + the updates in the new table
EDIT: Added PK

Using an outer join, you could select the data into a new table and update them along the way. Here's how:
SELECT A.PK,
COALESCE(B.x, A.x) AS x,
COALESCE(B.y, A.y) AS x,
COALESCE(B.z, A.z) AS x,
other columns as necessary
INTO JAN_UPDATED
FROM JAN A
LEFT JOIN FEB B ON A.PK = B.PK AND (A.x <> B.x OR A.y <> B.y OR A.z <> B.z)
The left part of the join, the JAN table, will return all the JAN rows, and the right part of the join, FEB, will only return matching rows, those which are different from their counterparts in JAN. When there's no match, the right part's columns will be filled with NULLs. Now, when pulling values, the COALESCE() function is used for x, y, and z: the FEB version is tried first, and if it is NULL (meaning this is a non-matching JAN row, the one that has no updates in FEB), then the JAN party (the unchanged value) is used instead.

The approach you are following is correct one if you require original records and updated one.
We cann't follow any other approach because there is no primary key defined on the tables.
Your solution also has fuzzy logic because it can update wrong records.

Related

sql join scenario

I have 2 tables name like A and B. A have columns say X, Y and Z and Table B have coulmns say P, Q and R. here in my case table have blank data for few rows in all the columns.
I need to join these 2 tables such that If A.X<>'' and B.X<>'' then It should join the table. If A.X='' and B.X='' then
it should check the next columns A.Y<>'' and B.Y<>''. If this is also blank it should join the table on next condition A.Z<>'' and B.Z<>''. If all these 3 condition have blanks It should not join for that row.
How can we achieve this using sql join?
Thanks in advance
You can go for conditional JOINS as given below:
SELECT *
FROM A
LEFT OUTER JOIN B as b1
ON A.X = b1.X AND B1.X <> '' -- JOIN only rows WHERE x is not blank
LEFT OUTER JOIN B as b2
ON A.Y = b2.Y AND b2.Y <> '' -- JOIN only rows WHERE y is not blank
LEFT OUTER JOIN B AS b3
ON A.Z = b3.Z AND b3.Z <> '' -- JOIN only rows WHERE z is not blank
WHERE
b1.X IS NOT NULL OR
b2.Y IS NOT NULL OR
b3.Z IS NOT NULL
Ramu's answer is close (I upvoted it) but it needs to be refined. The important part of the answer that is correct -- the equality conditions in the JOINs make the query easier to optimize.
However, it is better written as:
SELECT a.*,
COALESCE(b1.P, b2.P, b3.P) as P,
COALESCE(b1.Q, b2.Q, b3.Q) as Q,
COALESCE(b1.R, b2.R, b3.R) as R
FROM A LEFT JOIN
B b1
ON A.X = b1.X LEFT JOIN
B b2
ON A.Y = b2.Y AND
b1.X IS NULL LEFT JOIN -- no previous match
B b3
ON A.Z = b3.Z AND
b2.Y IS NULL AND
b1.X IS NULL -- no previous match
WHERE b1.X IS NOT NULL OR
b2.Y IS NOT NULL OR
b3.Z IS NOT NULL ;
The two key changes are:
The LEFT JOIN conditions check that previous columns did not match.
The SELECT uses COALESCE() to fetch columns.
Also, I don't think the condition on empty strings is needed. There will be no match if there are no empty string values in B for that column. If both tables have empty strings, then you apparently do want a match -- and an empty string matches an empty string in SQL Server.
You can also express this using APPLY:
SELECT a.*, b.*
FROM A CROSS APPLY
(SELECT TOP (1) WITH TIES B.*
FROM B
WHERE A.X = b.X OR
A.Y = b.Y OR
A.Z = b.Z
ORDER BY (CASE WHEN A.X = B.X THEN 1
WHEN A.Y = B.Y THEN 2
WHEN A.Z = B.Z THEN 3
)
) B;
However, I would expect the previous version to have much better performance.
Use a CASE expression in the ON clause:
SELECT *
FROM A INNER JOIN B
ON 1 = CASE
WHEN A.X <> '' AND B.P <> '' AND A.X = B.P THEN 1
WHEN A.Y <> '' AND B.Q <> '' AND A.Y = B.Q THEN 1
WHEN A.Z <> '' AND B.R <> '' AND A.Z = B.R THEN 1
END
or:
SELECT *
FROM A INNER JOIN B
ON 1 = CASE
WHEN A.X <> '' AND A.X = B.P THEN 1
WHEN A.Y <> '' AND A.Y = B.Q THEN 1
WHEN A.Z <> '' AND A.Z = B.R THEN 1
END
There is no need for further joins.
The CASE expression makes sure that each condition will be checked in the order that you want it to be checked.
So if the 1st condition is satisfied then the rows of the 2 tables will be joined, if not then the 2nd condition will be checked and so on.

what is the right way to reduce multiple update statement in oracle

I have 2 tables out of which one column/field is same in both tables, I need to update the Table A with data from table B.
Here table A.x value needs to be taken and compared with B.w and equivalant B.z value needs to be updated in A.x. x value differs from x1,x2 etc.. so each value needs to be taken and compared with w in Table B and equivalent z value need to be updated in x,x1,x2 etc in Table A.
Table A (columns j, x, x1,x2,x3..x20 and so on)
---------
j x x1 x2 ..x20 and y y1 y2 .. y20
Table B (columns w and z)
--------
w z
UPDATE TableA a SET a.x = (SELECT b.w
FROM TableB b
WHERE a.x = b.z)
WHERE a.j='somevalue';
If I write this way I need to write 40 update statement, is there any easy way to do these updates.
And the subquery might return multiple rows and I need to refine that too.
Thanks,
Ashraf
As a.x has another value as a.x1 and a.x2, etc. you need a subquery per value of course, because it is different b records you want to find.
UPDATE TableA a
SET a.x = (SELECT b.w FROM TableB b WHERE a.x = b.z)
, a.x1 = (SELECT b.w FROM TableB b WHERE a.x1 = b.z)
, a.x2 = (SELECT b.w FROM TableB b WHERE a.x2 = b.z)
...
WHERE a.j = 'somevalue';
You might want to consider changing your table design for tableA, so as to have rows holding the values rather than columns. That would make updates (and queries in general for that matter) much easier.

Alternative to <> in SQL Developer

I did some searching on this site and couldn't find exactly what I'm looking for, so I hope this isn't a duplicate. I have an issue where a query in a view is taking about 39 seconds to run, which is dragging down a report query that joins to this view multiple times.
To keep this simple I'm going to keep the code simple, but keep the structure exactly as it is on the view. Here is the SELECT statement:
SELECT ....
FROM A a
JOIN B b on a.x = b.x
JOIN C c ON c.s = 'P' AND c.y = b.y
JOIN B AS b2 ON b2.y = c.y AND b2.x <> a.x
JOIN B b3 ON b3.x = b2.x
The x's and y's are the same column names in all join predicates.
The issue I am having comes with the line AND b2.x <> a.x. Without this, it runs in about 1 second, but with it its always taking over 30 seconds. I've tried rewriting this predicate multiple times:
b2.x IN (select b2.x FROM B b2 join A a on b2.x <> a.x)
b2.x NOT IN (select b2.x FROM b b2 JOIN A a on b2.x <> a.x)
NOT b2.x = a.x
OR even removing it and putting in a where clause after the joins, with all of the above varieties and also :
WHERE b2.x NOT IN (SELECT x FROM a)
WHERE b2.x (NOT IN SELECT DISTINCT x FROM a)
Im running out of ideas and need to figure out a way to optimize this. Any suggestions or hints at what else I can look at? Just running
SELECT b2.x from B b2 JOIN A a ON b2.x <> a.x
runs very quickly, so I don't think the underlying tables are the issues.
If the query runs really fast without the condition, but poorly with it, then I might suggest a materialized CTE:
WITH abc as (
SELECT /*+ materialize */...., b2.x as b2x, a.x as ax
FROM A a JOIN
B b
ON a.x = b.x JOIN
C c
ON c.s = 'P' AND c.y = b.y JOIN
B b2
ON b2.y = c.y AND b2.x <> a.x JOIN
B b3
ON b3.x = b2.x
)
SELECT abc.*
FROM ABC
WHERE b2x <> ax;

Locally symmetric difference in sql

I have a problem similar to this StackOverflow question, except that I need to exclude certain fields from the comparison but still include it in the result set.
I'm penning the problem as locally symmetric difference.
For example Table A and B have columns X,Y,Z and I want to compare only Y,Z for differences but I still want the result set to include X.
Sounds like this is basically what you want. Match rows between two tables on columns Y and Z, find the unmatched rows, and output the values of columns X, Y, and Z.
SELECT a.x, a.y, a.z, b.x, b.y, b.z
FROM a FULL OUTER JOIN b ON a.y = b.y AND a.z = b.z
WHERE a.y IS NULL OR b.y IS NULL
Old style SQL for a full join - A concatenated with B, excluding rows in B also in A (the middle):
-- all rows in A with or without matching B
select a.x, a.y, a.z
from a
left join b
on a.x = b.x
and a.y = b.y
union all
-- all rows in B with no match in A to "exclude the middle"
select b.x, b.y, null as z
from b
where not exists (select null
from a
where b.x = a.x
and b.y = a.y)
ANSI Style:
select coalesce(a.x, b.x) as x,
coalesce(a.y, b.y) as y,
a.z
from a
full outer join b
on a.x = b.x
and a.y = b.y
The coalesce's are there for safety; I've never actually had cause to write a full outer join in the real world.
If what you really want to find out if two table are identical, here's how:
SELECT COUNT(*)
FROM (SELECT list_of_columns
FROM one_of_the_tables
MINUS
SELECT list_of_columns
FROM the_other_table
UNION ALL
SELECT list_of_columns
FROM the_other_table
MINUS
SELECT list_of_columns
FROM one_of_the_tables)
If that returns a non-zero result, then there is a difference. It doesn't tell you which table it's in, but it's a start.

Update via subquery, what if the subquery returns no rows?

I am using a subquery in an UPDATE:
UPDATE tableA
SET x,y,z = ( (SELECT x, y, z
FROM tableB b
WHERE tableA.id = b.id
AND (tableA.x != b.x
OR tableA.y != b.y
OR tableA.z != b.z))) );
My question is, what happens if the subquery returns no rows? Will it do an update with nulls?
Secondly, is there a better way to write this. I am basically updating three fields in tableA from tableB, but the update should only happen if any of the three fields are different.
what happens if the subquery returns
no rows? Will it do an update with
nulls?
Yes-- you can test this like:
update YourTable
set col1 = (select 1 where 1=0)
This will fill col1 with NULLs. In case the subquery returns multiple rows, like:
update YourTable
set col1 = (select 1 union select 2)
The database will generate an error.
Secondly, is there a better way to
write this. I am basically updating
three fields in tableA from tableB,
but the update should only happen if
any of the three fields are different.
Intuitively I wouldn't worry about the performance. If you really wish to avoid the update, you can write it like:
UPDATE a
SET x = b.x, y = b.y, z = b.z
FROM tableA a, tableB b
WHERE a.id = b.id AND (a.x <> b.x OR a.y <> b.y OR a.z <> b.z)
The WHERE clause prevents updates with NULL.
On informix I used, a variation of Andomar's solution:
UPDATE a
SET x,y,z = ( (SELECT x, y, z
FROM tableB b
WHERE tableA.id = b.id) )
WHERE tableA.id IN (SELECT fromTable.id
FROM tableA toTable, tableB fromTable
WHERE toTable.id = fromTable.id
AND ((toTable.x <> fromTable.x)
OR (toTable.y <> fromTable.y)
OR (toTable.z <> fromTable.z))