SQL - Comma Delimited subset results of multiple column in its own column - sql-server

I have a view with a union all table, a person may belong to one or many table.
how do i create a query that will add a column with a ';' delimited where the person belong to, the ID is unique per person.
here's the example
--table1
PID fName tableMem
1 test group1
2 test2 group1
--table2
PID fName tableMem
1 test group2
3 test3 group2
--table3
PID fName tableMem
1 test group3
3 test3 group3
Here's the output I wanted
--compiled table after union of all the 3 tables
PID fname tableMem
1 test group1;group2;group3
2 test2 group1
3 test3 group2;group3
Here's the query I built from reading here for the past 2 days.I'm using STUFF and partition because I need the row to be distinct and this query will run as view.
SELECT *
FROM
(SELECT
*,
ROW_NUMBER() OVER(PARTITION BY PIP ORDER BY Fname) AS rownum
FROM
(SELECT
*,
STUFF((SELECT ';'+ di.tablemem
FROM DPI di <<-- alias table from union
WHERE DPI.PID = di.PID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '') tablemem
FROM
(SELECT *
FROM
(--table1
SELECT 'group1' AS tableMem, * FROM table1
UNION ALL
--table2
SELECT 'group2' AS tableMem, * FROM table2
UNION ALL
--table3
SELECT 'group3' AS tableMem, * FROM table3) AS DPI <<--alias table name
) AS innertable
) AS distinctTable
) AS outerTable
WHERE
rownum = 1
what am I missing or what is wrong with the query. I'm guessing its because Im using a derived table name of the union sub select. is there any workaround?
Thank you in advance

You need GROUP BY data by PID, fName, using the FOR XML aggregation it would be
WITH DPI AS (
--table1
Select 'group1' as tableMem,* from table1
UNION ALL
--table2
Select 'group2' as tableMem,* from table2
UNION ALL
--table3
Select 'group3' as tableMem,* from table3
)
SELECT PID, fName
, STUFF((
SELECT ';'+ di.tablemem
FROM DPI di
WHERE di.PID = di1.PID
FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'') tablemem
FROM DPI di1
GROUP BY PID, fName;

Here is a possible solution using CTE:
;with cte as (
select * from #tbl1
union all
select * from #tbl2
union all
select * from #tbl3
)
select distinct
pid
,fname
,stuff(
(select '; ' + tableMem
from cte
where pid = a.pid
and fname = a.fname
FOR XML PATH(''),TYPE)
.value('text()[1]','nvarchar(max)'),1,2,N'') as tableMem
from cte a

Given that you have a finite number of tables, I think I find this simpler:
select p.pid, p.fname,
trim(';' from
(case when t1.pid is not null then 'group1;' else '' end) +
(case when t2.pid is not null then 'group2;' else '' end) +
(case when t3.pid is not null then 'group3;' else '' end)
) as groups
from (select pid, fname from table1 union -- on purpose to remove duplicates
select pid, fname from table2 union
select pid, fname from table3
) p left join
table1 t1
on t1.pid = p.pid left join
table2 t2
on t2.pid = p.pid left join
table3 t3
on t3.pid = p.pid;
The most recent version of SQL Server supports string_agg(), which would make this simpler still.

Try this:
DECLARE #table1 TABLE
(
[PID] TINYINT
,[fname] VARCHAR(12)
,[tableMem] VARCHAR(12)
);
DECLARE #table2 TABLE
(
[PID] TINYINT
,[fname] VARCHAR(12)
,[tableMem] VARCHAR(12)
);
DECLARE #table3 TABLE
(
[PID] TINYINT
,[fname] VARCHAR(12)
,[tableMem] VARCHAR(12)
);
INSERT INTO #table1 ([PID], [fname], [tableMem])
VALUES (1, 'test', 'group1')
,(2, 'test2', 'group1');
INSERT INTO #table2 ([PID], [fname], [tableMem])
VALUES (1, 'test', 'group2')
,(3, 'test3 ', 'group2');
INSERT INTO #table3 ([PID], [fname], [tableMem])
VALUES (1, 'test', 'group3')
,(3, 'test3 ', 'group3');
WITH DataSource AS
(
SELECT *
FROM #table1
UNION ALL
SELECT *
FROM #table2
UNION ALL
SELECT *
FROM #table3
)
SELECT DISTINCT DS.[PID]
,DS.fname
,CSVvalue.[tableMem]
FROM DataSource DS
CROSS APPLY
(
SELECT STUFF
(
(
SELECT ',' + DS1.[tableMem]
FROM DataSource DS1
WHERE DS.[PID] = DS1.[PID]
AND DS.[fname] = DS1.[fname]
ORDER BY DS1.[tableMem]
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1
,1
,''
)
) CSVvalue ([tableMem]);

Related

T-SQL - Compare two tables and display result as Available on Both, table1 only and table2 only

I have 2 tables and structure is same on both tables.
Table 1:
ID Name Phone
1 xxx 111
2 yyy 222
Table 2:
ID Name Phone
1 xxx 111
3 zzz 333
I need to compare these two tables and display the results (based on ID column- where condition ID) as
Available in both tables
Table1 only
Table2 only
This should be like this,
ID Name Phone Status
----------------------------------------
1 xxx 111 Available in both
2 yyy 222 Available in T1 only
3 zzz 333 Available in T2 only
using HASHBYTES :Demo Here ..you dont need to consume table mutiple times,but only once
;with cte
as
(
select id,name,phone,hashbytes('sha1',concat(id,name,phone) )as tb1
from #t1
)
select isnull(c.id,b.id) as id,
isnull(c.name,b.name) as name,
isnull(c.phone,b.phone) as phone,
case when c.tb1 is null then 'exists in second table only'
when c.tb1 is not null and b.tb1 is not null then 'exists in both'
when b.tb1 is null then 'exists in first table only'
end as 'exidts' from cte c
full join
(
select id,name,phone,hashbytes('sha1',concat(id,name,phone) )as tb1
from #t2
) b
on
b.tb1=c.tb1
Try this:
declare #table1 table
(
name varchar(10),
phone varchar(10)
)
declare #table2 table
(
name varchar(10),
phone varchar(10)
)
INSERT INTO #table1 VALUES('xxx','111')
INSERT INTO #table1 VALUES('yyy','222')
INSERT INTO #table2 VALUES('xxx','111')
INSERT INTO #table2 VALUES('zzz','333')
SELECT t1.name, t1.phone, 'Available on both' FROM
#table1 t1 INNER JOIN #table2 t2
ON t1.name = t2.name and t1.phone = t2.phone
UNION
SELECT name, phone, 'Available on T1 only' FROM
#table1 t1 WHERE NOT EXISTS
(SELECT 1 FROM #table2 t2
WHERE t1.name = t2.name and t1.phone = t2.phone)
UNION
SELECT name, phone, 'Available on T2 only' FROM
#table2 t2 WHERE NOT EXISTS
(SELECT 1 FROM #table1 t1
WHERE t1.name = t2.name and t1.phone = t2.phone)
You can use combination of FULL JOIN and IS NULL to check availability from both tables -
SELECT ISNULL(t1.id, t2.id) AS Id
, ISNULL(t1.name, t2.name) AS Name
, ISNULL(t1.phone, t2.phone) AS Phone
, CASE
WHEN t1.id IS NULL THEN 'Available in T2 only'
WHEN t2.id IS NULL THEN 'Available in T1 only'
ELSE 'Available in both'
END AS Status
FROM Table1 AS t1
FULL JOIN Table2 AS t2 ON (t2.id = t1.id);
As this query uses only one JOIN operation and no sub queries it is very fast.
This could work:
CREATE TABLE #T1 ( ID INT, Name VARCHAR(10), Phone VARCHAR(10) )
CREATE TABLE #T2 ( ID INT, Name VARCHAR(10), Phone VARCHAR(10) )
INSERT INTO #T1 VALUES ( 1, 'xxx', '111' ), ( 2, 'yyy', '222' )
INSERT INTO #T2 VALUES ( 1, 'xxx', '111' ), ( 3, 'zzz', '333' )
SELECT #T1.ID, #T1.Name, #T1.Phone, 'Available on both' AS Status
FROM #T1 INNER JOIN #T2 ON #T1.ID = #T2.ID
UNION
SELECT #T1.ID, #T1.Name, #T1.Phone, 'Available on T1 only' AS Status
FROM #T1 LEFT JOIN #T2 ON #T1.ID = #T2.ID
WHERE #T2.ID IS NULL
UNION
SELECT #T2.ID, #T2.Name, #T2.Phone, 'Available on T2 only' AS Status
FROM #T1 RIGHT JOIN #T2 ON #T1.ID = #T2.ID
WHERE #T1.ID IS NULL
DROP TABLE #T1
DROP TABLE #T2

SQL Server: How to show list of manager name separated by comma

i have to show employee name and his manager name hierarchy separated by comma.
if ram is top manager and ram is sam manager and sam is rahim manager then i would like to have output like
Desired output
EMP Name Manager's Name
--------- ---------------
Rahim Sam,Ram
Sam Ram
Ram No manager
i got script which show the employee and his manager name. here is script
;WITH EmployeeCTE AS
(
Select ID,Name,MgrID, 0 as level FROM #Employee
WHERE ID=3
UNION ALL
Select r.ID,r.Name,r.MgrID, level+1 as level
FROM #Employee r
JOIN EmployeeCTE p on r.ID = p.MgrID
)
Select e1.Name
,ISNULL(e2.Name,'Top BOSS') as [Manager Name]
,row_number() over (order by e1.level desc) as [Level]
from EmployeeCTE e1
left join EmployeeCTE e2 on e1.MgrID=e2.ID
Output
Name Manager Name Level
Simon Top BOSS 1
Katie Simon 2
John Katie 3
i also know how to show comma separated list. here is one sample script.
SELECT
PNAME,
STUFF
(
(
SELECT ',' + Mname
FROM Myproducts M
WHERE M.PNAME = P.PNAME
ORDER BY Mname
FOR XML PATH('')
), 1, 1, ''
) AS Models
FROM
Myproducts p
GROUP BY PNAME
now some tell me how could i merge two script to get the desired output. thanks
CREATE TABLE #EMP (
EmpID INT
, ManagerID INT
, Name NVARCHAR(50) NULL
);
INSERT INTO #EMP (EmpID, ManagerID, Name)
VALUES
( 1, NULL, 'John')
, (2, 1, 'Katie')
, (3, 2, 'Simon');
SELECT *
FROM
#EMP;
WITH a AS (
SELECT
EmpID
, Name
, ManagerID
, CONVERT(NVARCHAR(MAX),'') AS ManagerChain
FROM
#Emp
WHERE
ManagerID IS NULL
UNION ALL
SELECT
e.EmpID
, e.Name
, e.ManagerID
, CASE
WHEN a.ManagerChain ='' THEN a.Name
ELSE CONCAT(a.Name, CONCAT(',',a.ManagerChain))
END
FROM
#Emp e
JOIN a ON e.ManagerID = a.EmpID
)
SELECT
a.Name
, IIF(a.ManagerChain='','No Manager',a.ManagerChain) AS ManagerChain
FROM
a;
DROP TABLE #EMP;
Assuming a table structure of
DECLARE #Employee TABLE(
ID INT,
Name VARCHAR(10),
MgrID INT)
INSERT INTO #Employee
VALUES (1,'Ram',NULL),
(2,'Sam',1),
(3,'Rahim',2);
You can use
WITH EmployeeCTE
AS (SELECT ID,
Name,
MgrID,
0 AS level,
CAST('No manager' AS VARCHAR(8000)) AS [Managers Name]
FROM #Employee
WHERE MgrID IS NULL
UNION ALL
SELECT r.ID,
r.Name,
r.MgrID,
level + 1 AS level,
CAST(P.Name + CASE
WHEN level > 0
THEN ',' + [Managers Name]
ELSE ''
END AS VARCHAR(8000))
FROM #Employee r
JOIN EmployeeCTE p
ON r.MgrID = p.ID)
SELECT *
FROM EmployeeCTE

How to create a compound field in group by in sqlserver

I have a table that contains 2 fields (for simplicity). the first one is the one that I want to group by on, and the second one is the one that I want to show as a comma separated text field. How to do it?
So my data is like this:
col 1 col2
------ ------
Ashkan s1
Ashkan s2
Ashkan s3
Hasan k1
Hasan k2
Hasan k3
Hasan kachal
I want this
col1 count combination
------ ------ -------
Ashkan 3 s1, s2,s3
Hasan 4 k1, k2,k3,kachal
I can do the group by like below, but how to do the combination?
select [col1],count(*)
FROM mytable
group by [col1]
order by count(*)
You can use FOR XML PATH for this:
select col1, count(*) ,
STUFF((SELECT ',' + col2
FROM mytable AS t2
WHERE t2.col1 = t1.col1
FOR XML PATH('')), 1, 1, '')
FROM mytable AS t1
group by col1
order by count(*)
You can use FOR XML PATH('') to concatenate the strings:
WITH Tbl(col1, col2) AS(
SELECT * FROM(VALUES
('Ashkan', 's1'),
('Ashkan', 's2'),
('Ashkan', 's3'),
('Hasan', 'k1'),
('Hasan', 'k2'),
('Hasan', 'k3'),
('Hasan', 'kachal')
) t(a,b)
)
SELECT
col1,
[count] = COUNT(*),
x.combination
FROM Tbl t
CROSS APPLY(
SELECT STUFF((
SELECT ', ' + col2
FROM Tbl
WHERE col1 = t.col1
FOR XML PATH('')
), 1, 2, '') AS combination
) x
GROUP BY t.col1, x.combination;

Combine multiple rows into list for multiple columns

I'm aware that the "combine multiple rows into list" question has been answered a million times, and here's a reference to an awesome article: Concatenating row values in transact sql
I have a need to combine multiple rows into lists for multiple columns at the same time
ID | Col1 | Col2 ID | Col1 | Col2
------------------ => ------------------
1 A X 1 A X
2 B Y 2 B,C Y,Z
2 C Z
I tried to use the xml method, but this has proven to be very slow over large tables
SELECT DISTINCT
[ID],
[Col1] = STUFF((SELECT ',' + t2.[Col1]
FROM #Table t2
WHERE t2.ID = t.ID
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),1,1,''),
[Col2] = STUFF((SELECT ',' + t2.[Col2]
FROM #Table t2
WHERE t2.ID = t.ID
FOR XML PATH(''), TYPE).value('.', 'nvarchar(max)'),1,1,''),
FROM #Table t
My current solution is to use a stored procedure that builds each ID row separately. I'm wondering if there's another approach I could use (other than using a loop)
For each column, rank the rows to combine (partition by the key column)
End up with a table like
ID | Col1 | Col2 | Col1Rank | Col2Rank
1 A X 1 1
2 B Y 1 1
2 C Z 2 2
Create a new table containing top rank columns for each ID
ID | Col1Comb | Col2Comb
1 A X
2 B Y
Loop through each remaining rank in increasing order (in this case 1 iteration)
for irank = 0; irank <= 1; irank++
update n set
n.col1Comb = n.Col1Comb + ',' + o.Col1, -- so append the rank 2 items
n.col2comb = n.Col2Comb + ',' + o.Col2 -- if they are not null
from #newtable n
join #oldtable o
on o.ID = n.ID
where o.col1rank = irank or o.col2rank = irank
A CTE trick can be used where you update the CTE.
Method 1: a new parallel table to which the data is copied and then concatenated:
CREATE TABLE #Table1(ID INT, Col1 VARCHAR(1), Col2 VARCHAR(1), RowID INT IDENTITY(1,1));
CREATE TABLE #Table1Concat(ID INT, Col3 VARCHAR(MAX), Col4 VARCHAR(MAX), RowID INT);
GO
INSERT #Table1 VALUES(1,'A','X'), (2,'B','Y'), (2,'C','Z');
GO
INSERT #Table1Concat
SELECT * FROM #Table1;
GO
DECLARE #Cat1 VARCHAR(MAX) = '';
DECLARE #Cat2 VARCHAR(MAX) = '';
; WITH CTE AS (
SELECT TOP 2147483647 t1.*, t2.Col3, t2.Col4, r = ROW_NUMBER()OVER(PARTITION BY t1.ID ORDER BY t1.Col1, t1.Col2)
FROM #Table1 t1
JOIN #Table1Concat t2 ON t1.RowID = t2.RowID
ORDER BY t1.ID, t1.Col1, t1.Col2
)
UPDATE CTE
SET #Cat1 = Col3 = CASE r WHEN 1 THEN ISNULL(Col1,'') ELSE #Cat1 + ',' + Col1 END
, #Cat2 = Col4 = CASE r WHEN 1 THEN ISNULL(Col2,'') ELSE #Cat2 + ',' + Col2 END;
GO
SELECT ID, Col3 = MAX(Col3)
, Col4 = MAX(Col4)
FROM #Table1Concat
GROUP BY ID
Method 2: Add the concatenation columns directly to the original table and concatenate the new columns:
CREATE TABLE #Table1(ID INT, Col1 VARCHAR(1), Col2 VARCHAR(1), Col1Cat VARCHAR(MAX), Col2Cat VARCHAR(MAX));
GO
INSERT #Table1(ID,Col1,Col2) VALUES(1,'A','X'), (2,'B','Y'), (2,'C','Z');
GO
DECLARE #Cat1 VARCHAR(MAX) = '';
DECLARE #Cat2 VARCHAR(MAX) = '';
; WITH CTE AS (
SELECT TOP 2147483647 t1.*, r = ROW_NUMBER()OVER(PARTITION BY t1.ID ORDER BY t1.Col1, t1.Col2)
FROM #Table1 t1
ORDER BY t1.ID, t1.Col1, t1.Col2
)
UPDATE CTE
SET #Cat1 = Col1Cat = CASE r WHEN 1 THEN ISNULL(Col1,'') ELSE #Cat1 + ',' + Col1 END
, #Cat2 = Col2Cat = CASE r WHEN 1 THEN ISNULL(Col2,'') ELSE #Cat2 + ',' + Col2 END;
GO
SELECT ID, Col1Cat = MAX(Col1Cat)
, Col2Cat = MAX(Col2Cat)
FROM #Table1
GROUP BY ID;
GO
Try this one -
Query1:
DECLARE #temp TABLE
(
ID INT
, Col1 VARCHAR(30)
, Col2 VARCHAR(30)
)
INSERT INTO #temp (ID, Col1, Col2)
VALUES
(1, 'A', 'X'),
(2, 'B', 'Y'),
(2, 'C', 'Z')
SELECT
r.ID
, Col1 = STUFF(REPLACE(REPLACE(CAST(d.x.query('/t1/a') AS VARCHAR(MAX)), '<a>', ','), '</a>', ''), 1, 1, '')
, Col2 = STUFF(REPLACE(REPLACE(CAST(d.x.query('/t2/a') AS VARCHAR(MAX)), '<a>', ','), '</a>', ''), 1, 1, '')
FROM (
SELECT DISTINCT ID
FROM #temp
) r
OUTER APPLY (
SELECT x = CAST((
SELECT
[t1/a] = t2.Col1
, [t2/a] = t2.Col2
FROM #temp t2
WHERE r.ID = t2.ID
FOR XML PATH('')
) AS XML)
) d
Query 2:
SELECT
r.ID
, Col1 = STUFF(REPLACE(CAST(d.x.query('for $a in /a return xs:string($a)') AS VARCHAR(MAX)), ' ,', ','), 1, 1, '')
, Col2 = STUFF(REPLACE(CAST(d.x.query('for $b in /b return xs:string($b)') AS VARCHAR(MAX)), ' ,', ','), 1, 1, '')
FROM (
SELECT DISTINCT ID
FROM #temp
) r
OUTER APPLY (
SELECT x = CAST((
SELECT
[a] = ',' + t2.Col1
, [b] = ',' + t2.Col2
FROM #temp t2
WHERE r.ID = t2.ID
FOR XML PATH('')
) AS XML)
) d
Output:
ID Col1 Col2
----------- ---------- ----------
1 A X
2 B,C Y,Z
One solution, one that is at least syntactically straight-forward, is to use a User-Defined Aggregate to "Join" the values together. This does require SQLCLR and while some folks are reluctant to enable it, it does provide for a set-based approach that does not need to re-query the base table per each column. Joining is the opposite of Splitting and will create a comma-separated list of what was individual rows.
Below is a simple example that uses the SQL# (SQLsharp) library which comes with a User-Defined Aggregate named Agg_Join() that does exactly what is being asked for here. You can download the Free version of SQL# from http://www.SQLsharp.com/ and the example SELECTs from a standard system view. (And to be fair, I am the author of SQL# but this function is available for free).
SELECT sc.[object_id],
OBJECT_NAME(sc.[object_id]) AS [ObjectName],
SQL#.Agg_Join(sc.name) AS [ColumnNames],
SQL#.Agg_Join(DISTINCT sc.system_type_id) AS [DataTypes]
FROM sys.columns sc
GROUP BY sc.[object_id]
I recommend testing this against your current solution(s) to see which is the fastest for the volume of data you expect to have in at least the next year or two.

SQL Server: How to use UNION with two queries that BOTH have a WHERE clause?

Given:
Two queries that require filtering:
select top 2 t1.ID, t1.ReceivedDate
from Table t1
where t1.Type = 'TYPE_1'
order by t1.ReceivedDate desc
And:
select top 2 t2.ID
from Table t2
where t2.Type = 'TYPE_2'
order by t2.ReceivedDate desc
Separately, these return the IDs I'm looking for: (13, 11 and 12, 6)
Basically, I want the two most recent records for two specific types of data.
I want to union these two queries together like so:
select top 2 t1.ID, t2.ReceivedDate
from Table t1
where t1.Type = 'TYPE_1'
order by ReceivedDate desc
union
select top 2 t2.ID
from Table t2
where t2.Type = 'TYPE_2'
order by ReceivedDate desc
Problem:
The problem is that this query is invalid because the first select cannot have an order by clause if it is being unioned. And it cannot have top 2 without having order by.
How can I fix this situation?
You should be able to alias them and use as subqueries (part of the reason your first effort was invalid was because the first select had two columns (ID and ReceivedDate) but your second only had one (ID) - also, Type is a reserved word in SQL Server, and can't be used as you had it as a column name):
declare #Tbl1 table(ID int, ReceivedDate datetime, ItemType Varchar(10))
declare #Tbl2 table(ID int, ReceivedDate datetime, ItemType Varchar(10))
insert into #Tbl1 values(1, '20010101', 'Type_1')
insert into #Tbl1 values(2, '20010102', 'Type_1')
insert into #Tbl1 values(3, '20010103', 'Type_3')
insert into #Tbl2 values(10, '20010101', 'Type_2')
insert into #Tbl2 values(20, '20010102', 'Type_3')
insert into #Tbl2 values(30, '20010103', 'Type_2')
SELECT a.ID, a.ReceivedDate FROM
(select top 2 t1.ID, t1.ReceivedDate
from #tbl1 t1
where t1.ItemType = 'TYPE_1'
order by ReceivedDate desc
) a
union
SELECT b.ID, b.ReceivedDate FROM
(select top 2 t2.ID, t2.ReceivedDate
from #tbl2 t2
where t2.ItemType = 'TYPE_2'
order by t2.ReceivedDate desc
) b
select * from
(
select top 2 t1.ID, t1.ReceivedDate
from Table t1
where t1.Type = 'TYPE_1'
order by t1.ReceivedDate de
) t1
union
select * from
(
select top 2 t2.ID
from Table t2
where t2.Type = 'TYPE_2'
order by t2.ReceivedDate desc
) t2
or using CTE (SQL Server 2005+)
;with One as
(
select top 2 t1.ID, t1.ReceivedDate
from Table t1
where t1.Type = 'TYPE_1'
order by t1.ReceivedDate de
)
,Two as
(
select top 2 t2.ID
from Table t2
where t2.Type = 'TYPE_2'
order by t2.ReceivedDate desc
)
select * from One
union
select * from Two
declare #T1 table(ID int, ReceivedDate datetime, [type] varchar(10))
declare #T2 table(ID int, ReceivedDate datetime, [type] varchar(10))
insert into #T1 values(1, '20010101', '1')
insert into #T1 values(2, '20010102', '1')
insert into #T1 values(3, '20010103', '1')
insert into #T2 values(10, '20010101', '2')
insert into #T2 values(20, '20010102', '2')
insert into #T2 values(30, '20010103', '2')
;with cte1 as
(
select *,
row_number() over(order by ReceivedDate desc) as rn
from #T1
where [type] = '1'
),
cte2 as
(
select *,
row_number() over(order by ReceivedDate desc) as rn
from #T2
where [type] = '2'
)
select *
from cte1
where rn <= 2
union all
select *
from cte2
where rn <= 2
The basic premise of the question and the answers are wrong. Every Select in a union can have a where clause. It's the ORDER BY in the first query that's giving yo the error.
The answer is misleading because it attempts to fix a problem that is not a problem. You actually CAN have a WHERE CLAUSE in each segment of a UNION. You cannot have an ORDER BY except in the last segment. Therefore, this should work...
select top 2 t1.ID, t1.ReceivedDate
from Table t1
where t1.Type = 'TYPE_1'
-----remove this-- order by ReceivedDate desc
union
select top 2 t2.ID, t2.ReceivedDate --- add second column
from Table t2
where t2.Type = 'TYPE_2'
order by ReceivedDate desc
Create views on two first "selects" and "union" them.
Notice that each SELECT statement within the UNION must have the same number of columns. The columns must also have similar data types. Also, the columns in each SELECT statement must be in the same order.
you are selecting
t1.ID, t2.ReceivedDate
from Table t1
union
t2.ID
from Table t2
which is incorrect.
so you have to write
t1.ID, t1.ReceivedDate from Table t1
union
t2.ID, t2.ReceivedDate from Table t1
you can use sub query here
SELECT tbl1.ID, tbl1.ReceivedDate FROM
(select top 2 t1.ID, t1.ReceivedDate
from tbl1 t1
where t1.ItemType = 'TYPE_1'
order by ReceivedDate desc
) tbl1
union
SELECT tbl2.ID, tbl2.ReceivedDate FROM
(select top 2 t2.ID, t2.ReceivedDate
from tbl2 t2
where t2.ItemType = 'TYPE_2'
order by t2.ReceivedDate desc
) tbl2
so it will return only distinct values by default from both table.
select top 2 t1.ID, t2.ReceivedDate, 1 SortBy
from Table t1
where t1.Type = 'TYPE_1'
union
select top 2 t2.ID, 2 SortBy
from Table t2
where t2.Type = 'TYPE_2'
order by 3,2

Resources