Doing sums with full joins? - sql

I'm working with the following data:
create table #CompanyA (
ID varchar(50),
tran_count int
)
insert into #CompanyA
values
('A', 1),
('B',4)
create table #CompanyB (
ID varchar(50),
tran_count int
)
insert into #CompanyB
values ('A',5),
('C',3)
and i'm trying to write a select joining the two tables by ID column, and that will add the respective tran_counts by ID. I tried doing this by using a full join, but I don't know how to account for the NULL values:
Select A.ID, B.ID, A.tran_count, B.tran_count, A.tran_count + B.tran_count as total
from #CompanyA A full join #CompanyB B
on A.ID = B.ID
ID ID tran_count tran_count total
A A 1 5 6
B NULL 4 NULL NULL
NULL C NULL 3 NULL
The desired output I want is this:
ID sumtotal
A 6
B 4
C 3
Please let me know!

You can use the DECODE function.
Try this:
DECODE(field,NULL,0, field)

You can replace all null values with any of your choising with isNull().
In your example, the query would look like this.
Select A.id as ID, isnull(A.tran_count,0) + isnull(B.tran_count,0) as sumtotal
from #CompanyA A full join #CompanyB B
on A.ID = B.ID

Why not "union all" the two tables and sum the tran_count? From the sample you have given this should work
select id, sum(tran_count) as sumtotal
from
(
select id,tran_count
from #CompanyA
union all
select id,tran_count
from #CompanyB
) a
group by id

Related

Returning the first result from another table if statement is true

So I'm struggling to find the logic for the next problem:
I got 2 tables
TABLE A has the following column
Postalcode
1111
2222
3333
4444
TABLE B has the following column
Postalcode
1111AA
1111BB
1111CA
2222AA etc
What I would like to have is that if the Postalcodes first 4 numbers are found from Table A in table B, then I would like to have the first result of that postalcode from Table B (4digits+2letters).
e.g. if the postalcode in A is 1111 and substring(postalcode, 1, 4) of Table B is also 1111, then return the first result of that postalcode from Table B --> 1111AA
I can't seem to find the answer for this and I'm struggling for a while now.
Hope you guys have the solution for me.
If for each record in table A you want to match at most one record from Table B, an OUTER APPLY (SELECT TOP 1 ...) should do the trick.
Try:
select a.PostalCode, b1.Postalcode
from table_a a
outer apply (
select top 1 *
from table_b b
where b.Postalcode LIKE a.Postalcode + '%'
order by b.id
) b1
order by a.PostalCode;
If you wish to omit results that have no matching table_b record, change the OUTER APPLY to a CROSS APPLY. A OUTER APPLY is like a LEFT JOIN while a CROSS APPLY is like an INNER JOIN.
See this db<>fiddle fr a demo.
(Credit Bernd Buffen for the data setup. Note that I changed PostalCode from INT to VARCHAR to simplify the match criteria.)
i have change the sample from #Ergest Basha with a virtual column and index
CREATE TABLE table_a (
Postalcode INT ,
KEY idx_sPortalcode (Postalcode)
);
INSERT INTO table_a VALUES
(1111),
(2222),
(3333),
(4444);
CREATE TABLE table_b (
id INT,
Postalcode VARCHAR(25),
sPostalcode INT AS ( 0 + Postalcode) STORED,
KEY idx_sPortalcode (sPostalcode)
);
INSERT INTO table_b (id,Postalcode) VALUES
(1,'1111AA'),
(2,'1111BB'),
(3,'1111CA'),
(4,'2222AA');
SELECT * FROM table_b;
-- EXPLAIN
SELECT b.Postalcode
FROM table_a a
INNER JOIN table_b b ON b.sPostalcode=a.Postalcode
WHERE a.Postalcode=1111
ORDER BY b.id ASC LIMIT 1;
Something like this: MySQL
select b.Postalcode
from table_a a
inner join table_b b on LEFT(b.Postalcode,4)=a.Postalcode
where a.Postalcode=1111
order by b.id asc limit 1;
Check the demo
SQL Server
select top(1) b.Postalcode
from table_a a
inner join table_b b on LEFT(b.Postalcode,4)=a.Postalcode
where a.Postalcode=1111
order by b.id ;
Demo
Edit based on comments*
I think you need something like below, but check #Bernd Buffen suggestion for performance:
WITH cte AS (
SELECT Postalcode, ROW_NUMBER() OVER ( PARTITION BY LEFT(Postalcode,4) ORDER BY id asc ) row_num
FROM table_b
)
SELECT cte.Postalcode
FROM table_a a
INNER JOIN cte on LEFT(cte.Postalcode,4)=a.Postalcode
WHERE row_num = 1 ;
Demo

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

Optional on condition in SQL Server

I want to update a table based on another table.
I want to update table b using table A. One Id can have multiple serial number in table B. If I want to update all the serial number for a ID then I will pass null in table A, If I want to update only particular serial number then I will pass that serial number. So Serial number is like optional. How to achieve this?
This covers all cases:
SELECT B.ID, B.SerialNumber
FROM TableA A
JOIN TableB B ON A.ID=B.ID AND (A.SerialNumber=B.SerialNumber OR A.SerialNumber IS NULL)
Demo:
WITH TableA AS
(
SELECT * FROM (VALUES
(1,'AB'),
(1,'BC'),
(2,NULL),
(3,'AB')
)T(ID,SerialNumber)
), TableB AS
(
SELECT * FROM (VALUES
(1,'AB'),
(1,'BC'),
(2,'AB'),
(2,'BC'),
(3,'AB'),
(3,'BC'),
(3,'DE')
)T(ID,SerialNumber)
)
SELECT B.ID, B.SerialNumber
FROM TableA A
JOIN TableB B ON A.ID=B.ID AND (A.SerialNumber=B.SerialNumber OR A.SerialNumber IS NULL)
Result
ID SerialNumber
----------- ------------
1 AB
1 BC
2 AB
2 BC
3 AB
You can join both tables :
SELECT [A].[ID]
,[B].[SerialNumber]
,[A].[Values]
FROM [TableA] A
LEFT JOIN [TableB] B ON ([B].[ID] = [A].[ID] AND ([B].[SerialNumber] = [A].[SerialNumber] OR [A].[SerialNumber] IS NULL))

SQL - Get one of each in a join

I've got 2 tables. Table A and B.
Table A has an id and some data which isn't important for the question.
Table B has an id and an A_id. The last one is used to combine the 2 of them. There can be either multiple rows with the same A_id, only 1 or none at all.
I need a query which will do the following:
Get only 1 of each row from table A
Join table B into it
No duplicates from table A
I know it might sound complicated, so here is an example
Table A
id other info
1 ...
2 ...
3 ...
4 ...
Table B
id A_id
1 2
2 3
3 3
4 3
Output
A.id other info B.id A_id
1 ... NULL NULL
2 ... 1 2
3 ... 2 3
4 ... NULL NULL
So, even though there are multiple rows in table B of which A_id is 3, I only need the one of them. And even though there is no row in table B of which the A_id is 1 or 4, I still need both of them to show up.
This is as clear as I can possibly describe my question, please give feedback on how I can improve this question.
I think the simplest way is to use a correlated subquery:
select a.*,
(select max(b.id) from b where b.a_id = a.id)
from a;
I can't test it right now but it seems that you want something like this
SELECT * FROM A
LEFT JOIN B ON A.ID = B.A_ID
UPDATE:
WITH tmp AS (
SELECT MIN(ID) ID FROM B GROUP BY A_id
)
SELECT A.*, B.* FROM B
INNER JOIN tmp ON B.Id = tmp.ID
RIGHT JOIN A ON A.Id = B.A_Id
Assuming your database supports ANSI SQL and when there are multiple rows in B you want the last one based on ID:
with lastB (B_Id) as (
select max(id) from tableB group by A_id
),
BRows as (
select * from tableB
where Id in (select B_Id from lastB)
)
select a.field1, a.field2, a.fieldN,
b.field1, b.field2, b.fieldN
from tableA a
left join BRows b on a.Id = b.A_Id
EDIT: Oops. You edited your question and wnat the first one. Then simply make max(), min().
with lastB (B_Id) as (
select min(id) from tableB group by A_id
),
BRows as (
select * from tableB
where Id in (select B_Id from lastB)
)
select a.field1, a.field2, a.fieldN,
b.field1, b.field2, b.fieldN
from tableA a
left join BRows b on a.Id = b.A_Id
EDIT: Here is the MS SQL sample I promised for:
DECLARE #tableA TABLE ( id INT, other VARCHAR(10) );
DECLARE #tableB TABLE
(
id INT ,
A_Id INT ,
other VARCHAR(10)
);
INSERT #tableA
( id, other )
VALUES ( 1, 'v1' ),
( 2, 'v2' ),
( 3, 'v3' ),
( 4, 'v4' );
INSERT #tableB
( id, A_Id, other )
VALUES ( 1, 2, 'v21' ),
( 2, 3, 'v31' ),
( 3, 3, 'v32' ),
( 4, 3, 'v33' );
WITH fromB ( B_Id )
AS ( SELECT MIN(id)
FROM #tableB
GROUP BY A_Id
),
BRows
AS ( SELECT *
FROM #tableB
WHERE id IN ( SELECT B_Id
FROM fromB )
)
SELECT a.id AS A_Id ,
a.other AS A_Other ,
b.id AS B_Id ,
b.other AS B_Other
FROM #tableA a
LEFT JOIN BRows b ON a.id = b.A_Id;
Result:
A_Id A_Other B_Id B_Other
1 v1 NULL NULL
2 v2 1 v21
3 v3 2 v31
4 v4 NULL NULL
Big thank you for Gordon Linoff for pushing me in the right direction. The initial answer was:
select A.*,
(select count(B.A_id) from B where B.A_id = A.id)
from A
I'm sorry for not telling this in my question, but all I actually needed was to get every row from A and at the same time check if there was any row in table B which had the same value in A_id as the A.id had.
This query counts all rows which have the same value of A_id as A.id.
To be clear, the output will give:
A.id other info count
1 ... 0
2 ... 1
3 ... 3
4 ... 0

Problem combining result of two different queries into one

I have two tables (TableA and TableB).
create table TableA
(A int null)
create table TableB
(B int null)
insert into TableA
(A) values (1)
insert into TableB
(B) values (2)
I cant join them together but still I would like to show the result from them as one row.
Now I can make select like this:
select
(select A from tableA) as A
, B from TableB
Result:
A B
1 2
But if I now delete from tableB:
delete tableB
Now when I run the same query as before:
select
(select A from tableA) as A
, B from TableB
I see this:
A B
But I was expecting seeing value from tableA
like this:
Expected Result:
A B
1
Why is this happening and how can I still see the value from TableA although selectB is returning 0 rows?
I am using MS SQL Server 2005.
Use a LEFT JOIN (although it's more of a cross join in your case).
If your db supports it:
SELECT a.a, b.b
FROM a
CROSS JOIN b
If not, do something like:
SELECT a.a, b.b
FROM a
LEFT JOIN b ON ( 1=1 )
However, once you have more rows in a or b, this will return the cartesian product:
1 1
1 2
2 1
2 2
This will actually give you what you're looking for, but if you only have one row per table:
select
(select A from tableA) as A
, (select B from TableB) as B
give this a try:
DECLARE #TableA table (A int null)
DECLARE #TableB table (B int null)
insert into #TableA (A) values (1)
insert into #TableB (B) values (2)
--this assumes that you don't have a Numbers table, and generates one on the fly with up to 500 rows, you can increase or decrease as necessary, or just join in your Numbers table instead
;WITH Digits AS
(
SELECT 0 AS nbr
UNION SELECT 1 UNION SELECT 2 UNION SELECT 3
UNION SELECT 4 UNION SELECT 5 UNION SELECT 6
UNION SELECT 7 UNION SELECT 8 UNION SELECT 9
)
, AllNumbers AS
(
SELECT u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 AS Number
FROM Digits u1, Digits u2, Digits u3
WHERE u3.nbr * 100 + u2.nbr * 10 + u1.nbr + 1 <= 500
)
, AllRowsA AS
(
SELECT
A, ROW_NUMBER() OVER (ORDER BY A) AS RowNumber
FROM #TableA
)
, AllRowsB AS
(
SELECT
B, ROW_NUMBER() OVER (ORDER BY B) AS RowNumber
FROM #TableB
)
SELECT
a.A,b.B
FROM AllNumbers n
LEFT OUTER JOIN AllRowsA a on n.Number=a.RowNumber
LEFT OUTER JOIN AllRowsB b on n.Number=b.RowNumber
WHERE a.A IS NOT NULL OR b.B IS NOT NULL
OUTPUT:
A B
----------- -----------
1 2
(1 row(s) affected)
if you DELETE #TableB, the output is:
A B
----------- -----------
1 NULL
(1 row(s) affected)
try this:
select a, (select b from b) from a
union
select b, (select a from a) from b
should retrieve you all the existing data.
you can filter it more by surrounding it with another select