How can I combine two result sets by a common column? - sql-server

I feel like there should be an easy way to do this.
Given two tables (ID is primary key, no duplicates):
TblQtyNew TblQtyUsed
ID | QtyNew ID | QtyUsed
1 15 1 7
2 18 3 21
How can I obtain the following result?
ID | QtyNew | QtyUsed
1 15 7
2 18 NULL
3 NULL 21
The only solution I have come up with involves a UNION on the ID column then two left joins:
(SELECT ID FROM TblQtyNew) UNION (SELECT ID FROM TblQtyUsed) as IDs
LEFT JOIN
(SELECT QtyNew FROM TblQtyNew) ON TblQtyNew.ID = IDs.ID
LEFT JOIN
(SELECT QtyUsed FROM TblQtyUsed) ON TblQtyUsed.ID = IDs.ID
Is there a more simple way to do this?

You can use full join and coalesce in id as below:
select coalesce(t1.id,t2.id) as Id, Qtynew, QtYused
from #table1 t1 full join #table2 t2
on t1.id = t2.id
Output:
+----+--------+---------+
| Id | Qtynew | QtYused |
+----+--------+---------+
| 1 | 15 | 7 |
| 2 | 18 | NULL |
| 3 | NULL | 21 |
+----+--------+---------+

You want a FULL OUTER JOIN:
SELECT COALESCE(T1.ID,T2.ID) as ID, T1.QtyNew, T2.QtyUsed
FROM TblQtyNew T1
FULL OUTER JOIN TblQtyUsed T2 on T1.ID = T2.ID

1.
SELECT ID,SUM(ISNULL(QtyNew)) AS QtyNew,SUM(ISNULL(QtyUsed)) AS QtyUsed
FROM (
SELECT ID,QtyNew,NULL AS QtyUsed FROM TblQtyNew
UNION ALL
SELECT ID,NULL QtyNew, QtyUsed FROM TblQtyUsed
) AS t
GROUP BY ID
2.
SELECT COALESCE(n.ID,U.ID) AS ID,n.QtyNew,u.QtyUsed
FROM TblQtyNew AS n FULL OUTER JOIN TblQtyUsed AS u ON n.ID=u.ID

Use FULL JOIN and Case:
DECLARE #T1 TABLE (id int , QtyNew int);
DECLARE #T2 TABLE( id int , QtyUsed int);
insert into #T1 values (1,15),(2,18);
insert into #T2 values (1,7),(3,21);
SELECT ID = case when T1.ID IS not Null then T1.ID else T2.ID end, T1.QtyNew, T2.QtyUsed
FROM #T1 T1
full JOIN #T2 T2 on T1.ID = T2.ID
Order by ID;
Demo

I intentionally trying alternate method.
declare #TblQtyNew table (ID int,QtyNew int)
insert into #TblQtyNew VALUES
(1,15)
,(2,18)
declare #TblQtyUsed table ( ID int, QtyUsed int)
insert into #TblQtyUsed VALUES
(1 ,7 )
,(3 ,21)
;with CTE as
(
select a.id,QtyNew,b.QtyUsed from #TblQtyNew a
inner join #TblQtyUsed b on a.id=b.ID
)
select * from CTE
union all
select a.id,QtyNew,null
from #TblQtyNew a
where not exists(select id from cte c where c.id=a.id)
union all
select a.id,null,QtyUsed
from #TblQtyUsed a
where not exists(select id from cte c where c.id=a.id)

select Id, QtyNew, QtyUsed
from TblQtyNew Full outer join TblQtyUsed
on TblQtyNew.ID=TBLQtyUsed.ID

Related

Join two tables in SQL and select certain columns

I have a table contacts which has the PK fields name and idref
and another table called links that has the PK fields contactFK, type and groupid
For example
contacts
pk name idref
1 john 5634
2 jen 4525
3 james 5656
links
pk contactfk type groupid
1 1 primary 555
2 2 worker 555
3 3 primary 666
I want to show
name id groupid primaryid
john 5634 555 5634
jen 4525 555 5634
james 5656 666 5656
You can use a CTE to pull out the Primary Group IDs first and then join back to it for each user as such:
CREATE TABLE #Contacts(pk int, name varchar(50), idref int)
INSERT INTO #Contacts VALUES(1,'john', 5634),
(2,'jen', 4525),
(3,'james', 5656)
CREATE TABLE #links(pk int, contactfk int,type varchar(20),groupid int)
INSERT INTO #links VALUES(1,1,'primary', 555),
(2,2,'worker', 555),
(3,3,'primary',666)
;WITH CTE AS
(SELECT t1.idref, GROUPID
from #Contacts t1
INNER JOIN #links t2 on t2.contactfk = t1.pk
where t2.[type] = 'primary'
)
SELECT t1.name, t1.idref,t2.groupid, t3.idref PrimaryID
from #Contacts t1
INNER JOIN #links t2 on t1.pk = t2.contactfk
INNER JOIN CTE t3 on t3.groupid = t2.groupid
Select a.name, a.idref id, b.groupid primaryid from contacts a left outer join links b on a.pk = b.pk

Making right joins and using STUFF

I have 2 tables.
Tabel T1:
-----ID-----Name----
1 P1
2 P2
3 P3
Tabel T2:
-----ID-----PID----Type
1 1 T1
1 2 T2
1 3 T1
2 4 T3
2 5 T3
What I want:
----Name----Different_Types-----Type_Names
P1 2 T1,T2
What I have tried:
Select distinct T1.NAME,
count(distinct T2.Type) as Different_Types,
from T1
left join T2
on T2.ID = T1.ID
having count(distinct T2.PID) > 2
order by Different_Types desc
Whit this query I have the two first columns in my desired table, but having trouble adding the third....Any idea's ?
Think this should do what you are after
DECLARE #T1 TABLE
(
ID INT NOT NULL,
Name CHAR(2) NOT NULL
);
INSERT INTO #T1
VALUES (1,'P1'),(2,'P2'),(3,'P3');
DECLARE #T2 TABLE
(
ID INT NOT NULL,
PID INT NOT NULL,
TypeName CHAR(2) NOT NULL
);
INSERT INTO #T2
VALUES (1,1,'T1'),(1,2,'T2'),(1,3,'T1'),(2,4,'T3'),(2,5,'T3');
SELECT T1.Name,
COUNT(DISTINCT T2.TypeName) AS Different_Types,
STUFF((
SELECT ',' + T.TypeName
FROM #T2 AS T
WHERE T1.ID = T.ID
GROUP BY T.TypeName
FOR XML PATH(''), TYPE).value('.', 'varchar(max)')
,1,1,'')
FROM #T1 AS T1
INNER
JOIN #T2 AS T2
ON T1.ID = T2.ID
GROUP BY T1.ID, T1.Name
HAVING COUNT(DISTINCT T2.PID) > 2;
Edit:
Changed your LEFT to an INNER join as you are referencing the T2 table in the having clause anyway.

Avoid Cross Joins in SQL Server

I have 2 tables T1 and T2.
T1:
ID | Name
----+-------
A | A1
A | C1
T2:
ID | Name
-----+------
A | A1
A | B1
I want to retrieve records that have same ID and Name with flag 1 and Same ID and Different Name with flag 0. However, while joining the table in SQL Server, I am getting the a cross join which is:
A | A1 | A1 | 1
A | A1 | B1 | 0
A | C1 | A1 | 0
A | C1 | B1 | 0
But I need the answer as:
A | A1 | A1 | 1
A | C1 | B1 | 0
The above result is giving me the same information about name mismatch but in limited no. of rows and no repetition.
Could somebody let me know how can do this in SQL Server?
Is this what you're after:
SELECT T1.ID, T1.Name Name1, T2.Name Name2, case T1.Name when T2.Name then 1 else 0 end Result
from T1
inner join T2 on T1.ID = T2.ID
where T1.Name = T2.Name
or (not exists (select 1 from T2 where T1.Name = Name and T1.ID = ID)
and not exists (select 1 from T1 where T2.Name = Name and T2.ID = ID))
Use a union to keep things simple:
select T1.ID, T1.Name Name1, T2.Name Name2, 1 flag
from T1
join T2 on T1.ID = T2.ID
and T1.Name = T2.Name
union all
select T1.ID, T1.Name, T2.Name, 0
from T1
join T2 on T1.ID = T2.ID
and T1.Name != T2.Name
Using a union is not the most efficient way, but it’s much easier to understand and unless you have millions of rows, it will still run very fast (and union all is quite a bit faster than union)
you can use ROW_NUMBER and FULL JOIN
DECLARE #T1 TABLE (ID VARCHAR(5), Name VARCHAR(5))
INSERT INTO #T1 VALUES ('A', 'A1')
INSERT INTO #T1 VALUES('A', 'C1')
DECLARE #T2 TABLE (ID VARCHAR(5), Name VARCHAR(5))
INSERT INTO #T2 VALUES ('A', 'A1')
INSERT INTO #T2 VALUES ('A', 'B1')
SELECT T1.ID, T1.Name, T2.Name, CASE WHEN T1.Name = T2.Name THEN 1 ELSE 0 END
FROM
( SELECT ROW_NUMBER()OVER(PARTITION BY ID ORDER BY Name) AS RN, * FROM #T1 ) T1
FULL JOIN
( SELECT ROW_NUMBER()OVER(PARTITION BY ID ORDER BY Name) AS RN, * FROM #T2 ) T2
ON T1.ID = T2.ID AND T1.RN = T2.RN
Result:
ID Name Name
----- ----- ----- -----------
A A1 A1 1
A C1 B1 0

T-SQL Left join twice

Query below works as planned, it shows exactly the way i joined it, and that is fine, but problem with it, is that if you have more "specialization" tables for users, something like "Mail type" or anything that user can have more then one data ... you would have to go two left joins for each and "give priority" via ISNULL (in this case)
I am wondering, how could I avoid using two joins and "give" priority to TypeId 2 over TypeId 1 in a single join, is that even possible?
if object_id('tempdb..#Tab1') is not null drop table #Tab1
create table #Tab1 (UserId int, TypeId int)
if object_id('tempdb..#Tab2') is not null drop table #Tab2
create table #Tab2 (TypeId int, TypeDescription nvarchar(50))
insert into #Tab1 (UserId, TypeId)
values
(1, 1),
(1, 2)
insert into #Tab2 (TypeId, TypeDescription)
values
(1, 'User'),
(2, 'Admin')
select *, ISNULL(t2.TypeDescription, t3.TypeDescription) [Role]
from #Tab1 t1
LEFT JOIN #Tab2 t2 on t1.TypeId = t2.TypeId and
t2.TypeId = 2
LEFT JOIN #Tab2 t3 on t1.TypeId = t3.TypeId and
t3.TypeId = 1
The first problem is determining priority. In this case, you could use the largest TypeId, but that does not seem like a great idea. You could add another column to serve as a priority ordinal instead.
From there, it is a top 1 per group query:
using top with ties and row_number():
select top 1 with ties
t1.UserId, t1.TypeId, t2.TypeDescription
from #Tab1 t1
left join #Tab2 t2
on t1.TypeId = t2.TypeId
order by row_number() over (
partition by t1.UserId
order by t2.Ordinal
--order by t1.TypeId desc
)
using common table expression and row_number():
;with cte as (
select t1.UserId, t1.TypeId, t2.TypeDescription
, rn = row_number() over (
partition by t1.UserId
order by t2.Ordinal
--order by t1.TypeId desc
)
from #Tab1 t1
left join #Tab2 t2
on t1.TypeId = t2.TypeId
)
select UserId, TypeId, TypeDescription
from cte
where rn = 1
rextester demo for both: http://rextester.com/KQAV36173
both return:
+--------+--------+-----------------+
| UserId | TypeId | TypeDescription |
+--------+--------+-----------------+
| 1 | 2 | Admin |
+--------+--------+-----------------+
Actually I don't think you don't need a join at all. But you have to take the max TypeID without respect to the TypeDescription, since these differences can defeat a Group By. So a workaround is to take the Max without TypeDescription initially, then subquery the result to get the TypeDescription.
SELECT dT.*
,(SELECT TypeDescription FROM #Tab2 T2 WHERE T2.TypeId = dT.TypeId) [Role] --2. Subqueries TypeDescription using the Max TypeID
FROM (
select t1.UserId
,MAX(T1.TypeId) [TypeId]
--, T1.TypeDescription AS [Role] --1. differences will defeat group by. Subquery for value later in receiving query.
from #Tab1 t1
GROUP BY t1.UserId
) AS dT
Produces Output:
UserId TypeId Role
1 2 Admin

How to find the cumulative sum in SubQuery? [duplicate]

declare #t table
(
id int,
SomeNumt int
)
insert into #t
select 1,10
union
select 2,12
union
select 3,3
union
select 4,15
union
select 5,23
select * from #t
the above select returns me the following.
id SomeNumt
1 10
2 12
3 3
4 15
5 23
How do I get the following:
id srome CumSrome
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
select t1.id, t1.SomeNumt, SUM(t2.SomeNumt) as sum
from #t t1
inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.SomeNumt
order by t1.id
SQL Fiddle example
Output
| ID | SOMENUMT | SUM |
-----------------------
| 1 | 10 | 10 |
| 2 | 12 | 22 |
| 3 | 3 | 25 |
| 4 | 15 | 40 |
| 5 | 23 | 63 |
Edit: this is a generalized solution that will work across most db platforms. When there is a better solution available for your specific platform (e.g., gareth's), use it!
The latest version of SQL Server (2012) permits the following.
SELECT
RowID,
Col1,
SUM(Col1) OVER(ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
or
SELECT
GroupID,
RowID,
Col1,
SUM(Col1) OVER(PARTITION BY GroupID ORDER BY RowId ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Col2
FROM tablehh
ORDER BY RowId
This is even faster. Partitioned version completes in 34 seconds over 5 million rows for me.
Thanks to Peso, who commented on the SQL Team thread referred to in another answer.
For SQL Server 2012 onwards it could be easy:
SELECT id, SomeNumt, sum(SomeNumt) OVER (ORDER BY id) as CumSrome FROM #t
because ORDER BY clause for SUM by default means RANGE UNBOUNDED PRECEDING AND CURRENT ROW for window frame ("General Remarks" at https://msdn.microsoft.com/en-us/library/ms189461.aspx)
Let's first create a table with dummy data:
Create Table CUMULATIVESUM (id tinyint , SomeValue tinyint)
Now let's insert some data into the table;
Insert Into CUMULATIVESUM
Select 1, 10 union
Select 2, 2 union
Select 3, 6 union
Select 4, 10
Here I am joining same table (self joining)
Select c1.ID, c1.SomeValue, c2.SomeValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Order By c1.id Asc
Result:
ID SomeValue SomeValue
-------------------------
1 10 10
2 2 10
2 2 2
3 6 10
3 6 2
3 6 6
4 10 10
4 10 2
4 10 6
4 10 10
Here we go now just sum the Somevalue of t2 and we`ll get the answer:
Select c1.ID, c1.SomeValue, Sum(c2.SomeValue) CumulativeSumValue
From CumulativeSum c1, CumulativeSum c2
Where c1.id >= c2.ID
Group By c1.ID, c1.SomeValue
Order By c1.id Asc
For SQL Server 2012 and above (much better performance):
Select
c1.ID, c1.SomeValue,
Sum (SomeValue) Over (Order By c1.ID )
From CumulativeSum c1
Order By c1.id Asc
Desired result:
ID SomeValue CumlativeSumValue
---------------------------------
1 10 10
2 2 12
3 6 18
4 10 28
Drop Table CumulativeSum
A CTE version, just for fun:
;
WITH abcd
AS ( SELECT id
,SomeNumt
,SomeNumt AS MySum
FROM #t
WHERE id = 1
UNION ALL
SELECT t.id
,t.SomeNumt
,t.SomeNumt + a.MySum AS MySum
FROM #t AS t
JOIN abcd AS a ON a.id = t.id - 1
)
SELECT * FROM abcd
OPTION ( MAXRECURSION 1000 ) -- limit recursion here, or 0 for no limit.
Returns:
id SomeNumt MySum
----------- ----------- -----------
1 10 10
2 12 22
3 3 25
4 15 40
5 23 63
Late answer but showing one more possibility...
Cumulative Sum generation can be more optimized with the CROSS APPLY logic.
Works better than the INNER JOIN & OVER Clause when analyzed the actual query plan ...
/* Create table & populate data */
IF OBJECT_ID('tempdb..#TMP') IS NOT NULL
DROP TABLE #TMP
SELECT * INTO #TMP
FROM (
SELECT 1 AS id
UNION
SELECT 2 AS id
UNION
SELECT 3 AS id
UNION
SELECT 4 AS id
UNION
SELECT 5 AS id
) Tab
/* Using CROSS APPLY
Query cost relative to the batch 17%
*/
SELECT T1.id,
T2.CumSum
FROM #TMP T1
CROSS APPLY (
SELECT SUM(T2.id) AS CumSum
FROM #TMP T2
WHERE T1.id >= T2.id
) T2
/* Using INNER JOIN
Query cost relative to the batch 46%
*/
SELECT T1.id,
SUM(T2.id) CumSum
FROM #TMP T1
INNER JOIN #TMP T2
ON T1.id > = T2.id
GROUP BY T1.id
/* Using OVER clause
Query cost relative to the batch 37%
*/
SELECT T1.id,
SUM(T1.id) OVER( PARTITION BY id)
FROM #TMP T1
Output:-
id CumSum
------- -------
1 1
2 3
3 6
4 10
5 15
Select
*,
(Select Sum(SOMENUMT)
From #t S
Where S.id <= M.id)
From #t M
You can use this simple query for progressive calculation :
select
id
,SomeNumt
,sum(SomeNumt) over(order by id ROWS between UNBOUNDED PRECEDING and CURRENT ROW) as CumSrome
from #t
There is a much faster CTE implementation available in this excellent post:
http://weblogs.sqlteam.com/mladenp/archive/2009/07/28/SQL-Server-2005-Fast-Running-Totals.aspx
The problem in this thread can be expressed like this:
DECLARE #RT INT
SELECT #RT = 0
;
WITH abcd
AS ( SELECT TOP 100 percent
id
,SomeNumt
,MySum
order by id
)
update abcd
set #RT = MySum = #RT + SomeNumt
output inserted.*
For Ex: IF you have a table with two columns one is ID and second is number and wants to find out the cumulative sum.
SELECT ID,Number,SUM(Number)OVER(ORDER BY ID) FROM T
Once the table is created -
select
A.id, A.SomeNumt, SUM(B.SomeNumt) as sum
from #t A, #t B where A.id >= B.id
group by A.id, A.SomeNumt
order by A.id
The SQL solution wich combines "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" and "SUM" did exactly what i wanted to achieve.
Thank you so much!
If it can help anyone, here was my case. I wanted to cumulate +1 in a column whenever a maker is found as "Some Maker" (example). If not, no increment but show previous increment result.
So this piece of SQL:
SUM( CASE [rmaker] WHEN 'Some Maker' THEN 1 ELSE 0 END)
OVER
(PARTITION BY UserID ORDER BY UserID,[rrank] ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Cumul_CNT
Allowed me to get something like this:
User 1 Rank1 MakerA 0
User 1 Rank2 MakerB 0
User 1 Rank3 Some Maker 1
User 1 Rank4 Some Maker 2
User 1 Rank5 MakerC 2
User 1 Rank6 Some Maker 3
User 2 Rank1 MakerA 0
User 2 Rank2 SomeMaker 1
Explanation of above: It starts the count of "some maker" with 0, Some Maker is found and we do +1. For User 1, MakerC is found so we dont do +1 but instead vertical count of Some Maker is stuck to 2 until next row.
Partitioning is by User so when we change user, cumulative count is back to zero.
I am at work, I dont want any merit on this answer, just say thank you and show my example in case someone is in the same situation. I was trying to combine SUM and PARTITION but the amazing syntax "ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW" completed the task.
Thanks!
Groaker
Above (Pre-SQL12) we see examples like this:-
SELECT
T1.id, SUM(T2.id) AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < = T1.id
GROUP BY
T1.id
More efficient...
SELECT
T1.id, SUM(T2.id) + T1.id AS CumSum
FROM
#TMP T1
JOIN #TMP T2 ON T2.id < T1.id
GROUP BY
T1.id
Try this
select
t.id,
t.SomeNumt,
sum(t.SomeNumt) Over (Order by t.id asc Rows Between Unbounded Preceding and Current Row) as cum
from
#t t
group by
t.id,
t.SomeNumt
order by
t.id asc;
Try this:
CREATE TABLE #t(
[name] varchar NULL,
[val] [int] NULL,
[ID] [int] NULL
) ON [PRIMARY]
insert into #t (id,name,val) values
(1,'A',10), (2,'B',20), (3,'C',30)
select t1.id, t1.val, SUM(t2.val) as cumSum
from #t t1 inner join #t t2 on t1.id >= t2.id
group by t1.id, t1.val order by t1.id
Without using any type of JOIN cumulative salary for a person fetch by using follow query:
SELECT * , (
SELECT SUM( salary )
FROM `abc` AS table1
WHERE table1.ID <= `abc`.ID
AND table1.name = `abc`.Name
) AS cum
FROM `abc`
ORDER BY Name

Resources