Need help to build a PL/SQL Query - sql

I'm having scenario to which a sql query need to be built. I tried to come up with a efficient query, but could not find a clear way of doing this. My scenario is as follows:
I'm having TABLE_A and TABLE_B ,where FIELD_AB will definitely be a filed of TABLE_A, however, there can be exist FIELD_AB in TABLE_B.
I need to retrieve value for FIELD_AB, from TABLE_B if such field exist, if it is not, then retrieve value for FIELD_AB from TABLE_A.
I'm looking for a single query to retrieve the value of FIELD_AB, and according to my knowledge CASE statement can be used to accomplish this, but not clear a better way of using it.
EDIT:
Please do not misunderstood question. What I mean by "FIELD_AB can be exist" is that there is a possibility of FIELD_AB itself does not exist in the TABLE_B, not a value for FIELD_AB
Any help appreciated
Thank You

You probably need to use an outer join to link the two tables:
select a.id
, case when b.col_ab is null then a.col_ab
else b.col_ab end as ab
from table_b b
left outer join table_a a
on ( b.id = a.id )
/
Oracle has some alternative ways of testing for NULL. A simpler, if non-standard, way of testing for AB would be:
nvl2(b.col_ab, b.col_ab, a.col_ab) as ab
This is logically identical to the more verbose CASE() statement.

create table table_b ( field_ab int not null, value varchar(20) not null )
create table table_a ( field_ab int not null, value varchar(20) not null )
insert into table_a values( 1, '1 from a')
insert into table_a values( 2, '2 from a')
insert into table_a values( 3, '3 from a')
insert into table_b values( 2, '2 from b')
-- result is '2 from b'
select
case when b.field_ab is null then a.value
else b.value
end
from table_a a left outer join table_b b on a.field_ab = b.field_ab
where a.field_ab = 2
-- result is '1 from a'
select
case when b.field_ab is null then a.value
else b.value
end
from table_a a left outer join table_b b on a.field_ab = b.field_ab
where a.field_ab = 1

Related

LEFT JOIN with OR clause without UNION

I know this shouldn't happen in a database, but it happened and we have to deal with it. We need to insert new rows into a table if they don't exist based on the values in another table. This is easy enough (just do LEFT JOIN and check for NULL values in 1st table). But...the join isn't very straight forward and we need to search 1st table on 2 conditions with an OR and not AND. So basically if it finds a match on either of the 2 attributes, we consider that the corresponding row in 1st table exists and we don't have to insert a new one. If there are no matches on either of the 2 attributes, then we consider it as a new row. We can use OR condition in the LEFT JOIN statement but from what I understand, it does full table scan and the query takes a very long time to complete even though it yields the right results. We cannot use UNION either because it will not give us what we're looking for.
Just for simplicity purpose consider the scenario below (we need to insert data into tableA).
If(OBJECT_ID('tempdb..#tableA') Is Not Null) Begin
Drop Table #tableA End
If(OBJECT_ID('tempdb..#tableB') Is Not Null) Begin
Drop Table #tableB End
create table #tableA ( email nvarchar(50), id int )
create table #tableB ( email nvarchar(50), id int )
insert into #tableA (email, id) values ('123#abc.com', 1), ('456#abc.com', 2), ('789#abc.com', 3), ('012#abc.com', 4)
insert into #tableB (email, id) values ('234#abc.com', 1), ('456#abc.com', 2), ('567#abc.com', 3), ('012#abc.com', 4), ('345#abc.com', 5)
--THIS QUERY IS CORRECTLY RETURNING 1 RECORD
select B.email, B.id
from #tableB B
left join #tableA A on A.email = B.email or B.id = A.id
where A.id is null
--THIS QUERY IS INCORRECTLY RETURNING 3 RECORDS SINCE THERE ARE ALREADY RECORDS WITH ID's 1 & 3 in tableA though the email addresses of these records don't match
select B.email, B.id
from #tableB B
left join #tableA A on A.email = B.email
where A.id is null
union
select B.email, B.id
from #tableB B
left join #tableA A on B.id = A.id
where A.id is null
If(OBJECT_ID('tempdb..#tableA') Is Not Null) Begin
Drop Table #tableA End
If(OBJECT_ID('tempdb..#tableB') Is Not Null) Begin
Drop Table #tableB End
The 1st query works correctly and only returns 1 record, but the table size is just few records and it completes under 1 sec. When the 2 tables have thousands or records, the query may take 10 min to complete. The 2nd query of course returns the records we don't want to insert because we consider them existing. Is there a way to optimize this query so it takes an acceptable time to complete?
You are using an anti join, which is another way of writing the straight-forward NOT EXISTS:
where not exists
(
select null
from #tableA A
where A.email = B.email or B.id = A.id
)
I.e. where not exists a row in table A with the same email or the same id. In other words: where not exists a row with the same email and not exists a row with the same id.
where not exists (select null from #tableA A where A.email = B.email)
and not exists (select null from #tableA A where B.id = A.id)
With the appropriate indexes
on #tableA (id);
on #tableA (email);
this should be very fast.
It's hard to tune something you can't see. Another option to get the data is to:
SELECT B.email
, B.id
FROM #TableB B
EXCEPT
(
SELECT B.email
, B.id
FROM #tableB B
INNER JOIN #tableA A
ON A.email = B.email
UNION ALL
SELECT B.email
, B.id
FROM #tableB B
INNER JOIN #tableA A
ON B.id = A.id
)
This way you don't have to use OR, you can use INNER JOIN rather than LEFT JOIN and you can use UNION ALL instead of UNION (though this advantage may well be negated by the EXCEPT). All of which may help your performance. Perhaps the joins can be more efficient when replaced with EXISTS.
You didn't mention how this problem occurred (where the data from both tables is coming from, and why they are out of sync when they shouldn't be), but it would be preferable to fix it at the source.
No the query returns correctly 3 rows
because
select B.email, B.id
from #tableB B
left join #tableA A on A.email = B.email
where A.id is null
Allone reurns the 3 rows.
For your "problemm"
select B.email, B.id
from #tableB B
left join #tableA A on A.email = B.email or B.id = A.id
where A.id is null
will che3kc for every row, if it is true to be included
So for example
('123#abc.com', 1) ('234#abc.com', 1)
as the Ids are the same it will be joined
but when you join by the emails the condition is false and so is included in the result set
You can only use the UNION approach, when you are comparing only the emails or the ids, but with both the queries are not equivalent

max function does not when having case when clause

i have two tables.
one is as below
table a
ID, count
1, 123
2, 123
3, 123
table b
ID, count
table b is empty
when using
SELECT CASE
WHEN isnotnull(max(b.count)) THEN max(a.count) + max(b.count)
ELSE max(a.count)
FROM a, b
the only result is always NULL
i am very confused. why?
You don't need to use a JOIN, a simple SUM of two sub-queries will give you your desired result. Since you only add MAX(b.count) when it is non-NULL, we can just add it all the time but COALESCE it to 0 when it is NULL.
SELECT COALESCE((SELECT MAX(count) FROM b), 0) + (SELECT MAX(count) FROM a)
Another way to make this work is to UNION the count values from each table:
SELECT COALESCE(MAX(bcount), 0) + MAX(acount)
FROM (SELECT count AS acount, NULL AS bcount FROM a
UNION
SELECT NULL AS acount, count AS bcount FROM b) u
Note that if you use a JOIN it must be a FULL JOIN. If you use a LEFT JOIN you risk not seeing all the values from table b. For example, consider the case where table b has one entry: ID=4, count=456. A LEFT JOIN on ID will not include this value in the result table (since table a only has ID values of 1,2 and 3) so you will get the wrong result:
CREATE TABLE a (ID INT, count INT);
INSERT INTO a VALUES (1, 123), (2, 123), (3, 123);
CREATE TABLE b (ID INT, count INT);
INSERT INTO b VALUES (4, 456);
SELECT COALESCE(MAX(b.count), 0) + MAX(a.count)
FROM a
LEFT JOIN b ON a.ID = b.ID
Output
123 (should be 579)
To use a FULL JOIN you would write
SELECT COALESCE(MAX(b.count), 0) + MAX(a.count)
FROM a
FULL JOIN b ON a.ID = b.ID
Since, tableb is empty, max(b.count) will return NULL. And any operation done with NULL, results in NULL.
So, max(a.count) + max(b.count) is NULL.(this is 123 + NULL which will be NULL always). Hence, your query is returning NULL.
Just use a coalesce to assign a default value whenever NULL comes.
use coalesce() function and explicit join, avoid coma separated table name type old join method
select coalesce(max(a.count)+max(b.count),max(a.count))
from a left join b on a.id=b.id
Use left join
SELECT coalesce(max(a.count) + max(b.count),max(a.count))
FROM a left join b a.id=b.id

Why does this query return values in the column

I have the following query:
SELECT
a.id,
c.c_date
FROM table_a a ,
table_c c
WHERE
a.id = c.id AND
a.id IN (SELECT id from table_c where c_date is null);
I have two tables, table_a and table_c.
I join these two tables, but then get use an IN statement to only show the id's for in which are in table_c AND have the c_date column set to null`.
This query though returns id, and c_date values, and some of the c_date values are not null, how is this possible?
I thought in my sub query I am only selecting id which have null c_dates?
This should work without the subquery assuming you don't want to return null dates. Please note the use of the join:
SELECT a.id,
c.c_date
FROM table_a a
JOIN table_c c ON a.id = c.id
WHERE c_date is null;
It's difficult to answer your specific question though without sample data and expected results. You probably have multiple records in table_c that match the id field in table_a.
It will be easier to explain with this example:
table_a
id col_x col_...
1
2
table_c
id c_date col_m col_...
1 null
1 03/14/2016
2 04/14/2016
You should consider review your intention on your result set. Change your query to #sgeddes answer is a way.

SQL UPDATE from multiple rows to single row

this illustrates the issue:
CREATE TABLE Table_A (id int, value char)
INSERT INTO Table_A VALUES (1, 'A')
INSERT INTO Table_A VALUES (2, 'B')
CREATE TABLE Table_B (id int, value char)
INSERT INTO Table_B VALUES (1, 'C')
INSERT INTO Table_B VALUES (1, 'D')
If you run
UPDATE a SET a.value = (SELECT b.value FROM Table_B b WHERE a.id = b.id)
FROM Table_A a, Table_B b WHERE a.id = b.id
You get an error saying
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
But if you run this
UPDATE a SET a.value = b.value
FROM Table_A a, Table_B b WHERE a.id = b.id
No error is thrown and you get the row updated, why is that?
Edit:
Sorry guys, you seem to focusing on explaining why the first query gives error, but I think that is obvious and to me that is a desire result (because setting value of Table_A for id 1 to value of Table_B with id 1 is undefined when there are multiple values in Table_B with id 1)
My question is actually asking why the second query does not give you an error, which is causing trouble to me (i.e. I want it to break if I have more than one row with the same id but different values)
You got that error because you are using subquery when you set a new value and the subquery return more than 1 result.
SET a.value = (SELECT b.value FROM Table_B b WHERE a.id = b.id)
It will error when update a value with id = 1, because there is two record that have id = 1 in table b.
So your query will look like this (this is only for illustration and of course will cause an error)
UPDATE a SET a.value = ('C', 'D')
FROM Table_A a, Table_B b WHERE a.id = b.id
When you're using this query
UPDATE a SET a.value = b.value
FROM Table_A a, Table_B b WHERE a.id = b.id
You are join the table a with table b using id field, so the result is
a.id => a.value => b.value : 1 A C
a.id => a.value => b.value : 1 A D
No entry record for id = 2 because there is no matching record in table b.
So your update query will looks like this
UPDATE a SET a.value = 'C'
FROM Table_A a, Table_B b WHERE a.id = 1
UPDATE a SET a.value = 'D'
FROM Table_A a, Table_B b WHERE a.id = 1
Because your subquery will return both more than 1 result. The Assign statement will not accept more than 1 value.
You have to use JOIN
May be something like this
UPDATE A
SET A.value = B.value
FROM Table_A A INNER JOIN Table_B B ON A.id = B.id
FIDDLE DEMO
The reason your second query does not error our is because it will assign the first available B.value to a.value where it satisfies the condition
A.id = B.id
Try the following snippet and you will see how the Update...Set works.
create table #temp (id int, name varchar(100), phone int)
create table #temp1 (id int, phone int)
insert into #temp (id, name)
select 1, 'Mark' union
select 2, 'JOhn' union
select 3, 'Jerry'
insert into #temp1 (id, phone)
select 1, 123456 union
select 1, 222564 union
select 1, 222210
select * from #temp
select * from #temp1
update t
set phone = t1.phone
from #temp1 t1, #temp t
where t.id = t1.id
select * from #temp

Filtering out null values only if they exist in SQL select

Below I have sql select to retrieve values from a table. I want to retrieve the values from tableA regardless of whether or not there are matching rows in tableB. The below gives me both non-null values and null values. How do I filter out the null values if non-null rows exist, but otherwise keep the null values?
SELECT a.* FROM
(
SELECT
id,
col1,
coll2
FROM tableA a LEFT OUTER JOIN tableB b ON b.col1=a.col1 and b.col2='value'
WHERE a.id= #id
AND a.col2= #arg
) AS a
ORDER BY col1 ASC
You can do this by counting the number of matches using a window function. Then, either return all rows in A if there are no matching B rows, or only return the rows that do match:
select id, col1, col2
from (SELECT a.id, a.col1, a.coll2,
count(b.id) over () as numbs
FROM tableA a LEFT OUTER JOIN tableB b ON b.col1=a.col1 and b.col2='value'
WHERE a.id = #id AND a.col2= #arg
) ab
where numbs = 0 or b.id is not null;
Filter them out in WHERE clause
SELECT
id,
col1,
coll2
FROM tableA a LEFT OUTER JOIN tableB b ON b.col1=a.col1 and b.col2='value'
WHERE a.id= #id
AND a.col2= #arg
AND A.Col1 IS NOT NULL -- HERE
) AS a
ORDER BY col1 ASC
For some reason, people write code like Marko that puts a filter (b.col2 = 'value') in the JOIN clause. While this works, it is not good practice.
Also, you should get in the habit of having the ON clause in the right sequence. We are joining table A to table B, why write it as B.col1 = A.col1 which is backwards.
While the above statement works, it could definitely be improved.
I created the following test tables.
-- Just playing
use tempdb;
go
-- Table A
if object_id('A') > 0 drop table A
go
create table A
(
id1 int,
col1 int,
col2 varchar(16)
);
go
-- Add data
insert into A
values
(1, 1, 'Good data'),
(2, 2, 'Good data'),
(3, 3, 'Good data');
-- Table B
if object_id('B') > 0 drop table B
go
create table B
(
id1 int,
col1 int,
col2 varchar(16)
);
-- Add data
insert into B
values
(1, 1, 'Good data'),
(2, 2, 'Good data'),
(3, NULL, 'Null data');
Here is the improved statement. I choose literals instead of variables. However, you can change for your example.
-- Filter non matching records
SELECT
A.*
FROM A LEFT OUTER JOIN B ON
A.col1 = B.col1
WHERE
B.col1 IS NOT NULL AND
A.id1 in (1, 2) AND
A.col2 = 'Good data'
ORDER BY
A.id1 DESC
Here is an image of the output.