Union doesnt preserve order(not stable) - sql-server

I execute below query and expecting first set then second set distinctly but order is totally random.
I expected results: john,mark,dave,robert,kirk
select *
from (
select Name
from (values ('john'),('mark'),('dave')) X(Name)
union
select Name
from (values ('robert'),('mark'),('kirk')) X(Name)
) q
This is alternative query which i expected to have ordered(stable) results but i get same results. Union All append second set as i expected but applying Distinct later break ordering.
select Distinct Name
from (
select Name
from (values ('john'),('mark'),('dave')) X(Name)
union all
select Name
from (values ('robert'),('mark'),('kirk')) X(Name)
) q
What is solution for having ordered and distinct set ?

WITH q AS
(
-- Original data
SELECT Name FROM (VALUES ('john'),('mark'),('dave')) X(Name)
UNION ALL
SELECT Name FROM (VALUES ('robert'),('mark'),('kirk')) X(Name)
), r AS
(
-- Add the sequence column for ordering
-- ** It just use natual order **
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS Seq, * from q
), s AS
(
-- Use RN to filter out the duplicates
SELECT *, ROW_NUMBER() OVER (PARTITION BY Name ORDER BY Seq) AS RN FROM r
)
SELECT Name FROM s WHERE RN = 1 ORDER BY Seq

If you want the first set first, then every element in the second set that is not in the first set later, then you have to specify it. And you will also need to add an ORDER BY if you want to guarantee things in the first set being listed before the second set.
select q.name
from (
select Name,n
from
(values ('john'),('mark'),('dave')) X(Name)
cross join (values (1)) y(n)
union all
select Name,n
from
(
select Name
from (values ('robert'),('mark'),('kirk')) X(Name)
except
select Name
from
(values ('john'),('mark'),('dave')) X(Name)
) Z(Name)
Cross join (values (2)) y(n)
) q
order by q.n

If we are using values to select data then no need to use order by clause and and we won't have to worry about potential index changes from that particular query,so you will have the order always and moreover we are using union all it will just concatenate the result set is not sorted.
However, if you are using table, yes, the order can most certainly change depends on several factors such as table indexes, columns being returned, new data being introduced, etc. So if you want your results ordered in a particular way, you need to specify ORDER BY clause .
For your example you don't need to use order by clause like below
select Name
from (
select Name
from (values ('john'),('mark'),('dave')) X(Name)
union all
( select Name
from (values ('robert'),('mark'),('kirk')) X(Name)
except
select Name
from (values ('john'),('mark'),('dave')) X(Name))
) q

Is it reasonable for you to add a SortOrder column to your inner SELECT statements and then order by that? Something like:
select Distinct Name
from (
select SortOrder, Name
from (values (1, 'john'),(2, 'mark'),(3, 'dave')) X(SortOrder, Name)
union all
select SortOrder, Name
from (values (4, 'robert'),(2, 'mark'),(5, 'kirk')) X(SortOrder, Name)
) q
order by SortOrder ASC

The only way to guarantee order is by using ORDER BY clause, this is documented in BOL:
https://msdn.microsoft.com/en-us/library/ms188385.aspx
Order the result set of a query by the specified column list and, optionally, limit the rows returned to a specified range. The order in which rows are returned in a result set are not guaranteed unless an ORDER BY clause is specified.
If you want rows returned based on order in the UNION, then you could do something like this:
select Name
from (
select 1 as sort1, Name
from (values ('john'),('mark'),('dave')) X(Name)
union
select 2 as sort1, Name
from (values ('robert'),('mark'),('kirk')) X(Name)
) q
order by sort1, Name

Related

Transforming and repeating multiple rows

I have a table that has two IDs within it named FamilyID and PersonID. I need to be able to repeat these rows with all combinations, as the below screenshot shows noting that each of the numbers get an extra row.
Here is some SQL to create the table with some sample data. There is no set number of occurrences that could occur.
Anyone aware of how we could be achieved?
CREATE TABLE #TempStackOverflow
(
FamilyID int,
PersonID int
)
insert into #TempStackOverflow
(
FamilyID,
PersonID
)
select
1012,
1
union
select
1013,
1
union
select
1014,
1
union
select
1015,
2
union
select
14774,
3
union
select
1019,
5
I understand that you need some sort of a complete list of matches within groups, but honestly, it would be much better if you would explain the business context, using plain English, in the first place.
The following query seems to produce your sample result:
with cte as (
select a.FamilyID, a.PersonID, a.PersonID as [GroupId] from #TempStackOverflow a
union all
select b.PersonID, b.FamilyID, b.PersonID from #TempStackOverflow b
)
select distinct c.FamilyID, s.PersonID
from cte c
inner join cte s on s.GroupId = c.GroupId
where c.FamilyID != s.PersonID;
Here is the simplest version I can come up with that groups the items by PersonId, as you do above. Obviously if you don't want that, then you can remove the outer query.
SELECT FamilyId,
PersonID
FROM (
SELECT FamilyId, PersonId, PersonID as SortBy
FROM #TempStackOverflow t1
UNION
SELECT PersonId, FamilyId, PersonId as SortBy
FROM #TempStackOverflow t1
UNION
SELECT t1.FamilyID, t2.FamilyID, t1.PersonID as SortBy
FROM #TempStackOverflow t1
FULL OUTER JOIN #TempStackOverflow t2
ON t1.PersonID = t2.PersonID
WHERE t1.FamilyID != t2.FamilyID
) as Src
ORDER BY SortBy

SQL Server 2014 UNION in CROSS APPLY

I have the following query
SELECT DISTINCT
d.UserName,
i.itemID,
d.Score,
d.StoreCode,
d.Location
FROM
G.dbo.Users d
LEFT JOIN
G.dbo.Emails s on d.UserName=s.UserName
CROSS APPLY
(
SELECT TOP (1)
ii.ItemID
FROM
G.dbo.Dump ii
WHERE
ii.Username=d.UserName
AND
ii.endTime>DATEADD(hh,3,getDate())
) i
WHERE
s.serName is null
AND
d.Score>#_Score
AND
(d.processed=0)
GROUP BY
d.UserName,
i.itemID,
d.Score,
d.StoreCode,
d.Location
ORDER BY
d.UserName ASC
Now I need to modify it since Table G.dbo.Dump has been splitted into 20 smaller tables and now I have Dump_00 to Dump_19
I try to modify part of the CROSS APPLY section using UNION in this way
CROSS APPLY
(
SELECT TOP (1)
ii.ItemID
FROM
(
SELECT TOP (1) FROM G.dbo.Dump_00
UNION
SELECT TOP (1) FROM G.dbo.Dump_01
UNION
.....
SELECT TOP (1) FROM G.dbo.Dump_19
) ii
WHERE
ii.UserName=d.UserName
AND
ii.EndTime>DATEADD(hh,3,getDate())
) i
but result is not working as expected
can suggest if UNION is the right way and in case how to apply, or another solution?
Thanks!
Remove the TOP 1 from the union elements. Not sure why that was added. Logically, you are after a set that is the union of all tables.
Also, I don't think you want a union at all. You want the concatenation.
CROSS APPLY
(
SELECT TOP (1) ii.ItemID
FROM
(
SELECT FROM G.dbo.Dump_00 --changed
UNION ALL --changed
SELECT FROM G.dbo.Dump_01 --changed
.....
) ii
) i

Why does window functions not work in CROSS APPLY?

There is a simple code. I always thought that both outside ROW_NUMBER and the one in CROSS APPLY clause are supposed to generate the same output (in my example I excepct rn = crn). Could you please explain why isn't it like that?
CREATE TABLE #tmp ( id INT, name VARCHAR(200) );
INSERT INTO #tmp
VALUES ( 1, 'a' ),
( 2, 'a' ),
( 3, 'a' ),
( 4, 'b' ),
( 5, 'b' ),
( 6, 'c' ),
( 7, 'a' );
SELECT name,
ROW_NUMBER() OVER ( PARTITION BY name ORDER BY id ) AS rn,
a.crn
FROM #tmp
CROSS APPLY (
SELECT ROW_NUMBER() OVER ( PARTITION BY name ORDER BY id ) AS crn
) a;
OUTPUT:
name rn crn
a 1 1
a 2 1
a 3 1
a 4 1
b 1 1
b 2 1
c 1 1
The query in the CROSS APPLY is applied to each row in #tmp. The query selects for that one row it is applied to, the row number for that one row which is of course one.
Maybe this article on Microsoft's Technet would give you more insight into how CROSS APPLY works. An excerpt that highlights what I wrote in previous paragraph:
The APPLY operator allows you to invoke a table-valued function for each row returned by an outer table expression of a query. The table-valued function acts as the right input and the outer table expression acts as the left input. The right input is evaluated for each row from the left input and the rows produced are combined for the final output. The list of columns produced by the APPLY operator is the set of columns in the left input followed by the list of columns returned by the right input.
Note that APPLY is using the fields from you main query as the parameters.
SELECT ROW_NUMBER() OVER ( PARTITION BY name ORDER BY id ) AS crn
The above query does not have a FROM clause. So it's treating the name and id as literals. To illustrate, for the first row of #tmp, the resulting query of the CROSS APPLY is:
SELECT ROW_NUMBER() OVER ( PARTITION BY (SELECT 'a') ORDER BY (SELECT 1)) AS crn
which returns:
crn
--------------------
1
This is the result of your CROSS APPLY for every rows.
To achieve the desired result:
SELECT
t.name,
ROW_NUMBER() OVER ( PARTITION BY t.name ORDER BY t.id ) AS rn,
a.crn
FROM #tmp t
CROSS APPLY(
SELECT id, ROW_NUMBER() OVER (PARTITION BY name ORDER BY id ) AS crn
FROM #tmp
) a
WHERE t.id = a.id

How to find the maximum value in join without using if in sql stored procedure

I have a two tables like below
A
Id Name
1 a
2 b
B
Id Name
1 t
6 s
My requirement is to find the maximum id from table and display the name and id for that maximum without using case and if.
i findout the maximum by using below query
SELECT MAX(id)
FROM (SELECT id,name FROM A
UNION
SELECT id,name FROM B) as c
I findout the maximum 6 using the above query.but i can't able to find the name.I tried the below query but it's not working
SELECT MAX(id)
FROM (SELECT id,name FROM A
UNION
SELECT id,name FROM B) as c
How to find the name?
Any help will be greatly appreciated!!!
First combine the tables, since you need to search both. Next, determine the id you need. JOIN the id back with the temporarily created table to retreive the name that belongs to that id:
WITH tmpTable AS (
SELECT id,name FROM A
UNION
SELECT id,name FROM B
)
, max AS (
SELECT MAX(id) id
FROM tmpTable
)
SELECT t.id, t.name
FROM max m
JOIN tmpTable t ON m.id = t.id
You could use ROW_NUMBER(). You have to UNION ALL TableA and TableB first.
WITH TableA(Id, Name) AS(
SELECT 1, 'a' UNION ALL
SELECT 2, 'b'
)
,TableB(Id, Name) AS(
SELECT 1, 't' UNION ALL
SELECT 6, 's'
)
,Combined(Id, Name) AS(
SELECT * FROM TableA UNION ALL
SELECT * FROM TableB
)
,CTE AS(
SELECT *, RN = ROW_NUMBER() OVER(ORDER BY ID DESC) FROM Combined
)
SELECT Id, Name
FROM CTE
WHERE RN = 1
Just order by over the union and take first row:
SELECT TOP 1 * FROM (SELECT * FROM A UNION SELECT * FROM B) x
ORDER BY ID DESC
This won't show ties though.
For you stated that you used SQL Server 2008. Therefore,I used FULL JOIN and NESTED SELECT to get what your looking for. See below:
SELECT
(SELECT
1,
ISNULL(A.Id,B.Id)Id
FROM A FULL JOIN B ON A.Id=B.Id) AS Id,
(SELECT
1,
ISNULL(A.Name,B.Name)Name
FROM A FULL JOIN B ON A.Id=B.Id) AS Name
It's possible to use ROW_NUMBER() or DENSE_RANK() functions to get new numiration by Id, and then select value with newly created orderId equal to 1
Use:
ROW_NUMBER() to get only one value (even if there are some repetitions of max id)
DENSE_RANK() to get all equal max id values
Here is an example:
DECLARE #tb1 AS TABLE
(
Id INT
,[Name] NVARCHAR(255)
)
DECLARE #tb2 AS TABLE
(
Id INT
,[Name] NVARCHAR(255)
)
INSERT INTO #tb1 VALUES (1, 'A');
INSERT INTO #tb1 VALUES (7, 'B');
INSERT INTO #tb2 VALUES (4, 'C');
INSERT INTO #tb1 VALUES (7, 'D');
SELECT * FROM
(SELECT Id, Name, ROW_NUMBER() OVER (ORDER BY Id DESC) AS [orderId]
FROM
(SELECT Id, Name FROM #tb1
UNION
SELECT Id, Name FROM #tb2) as tb3) AS TB
WHERE [orderId] = 1
SELECT * FROM
(SELECT Id, Name, DENSE_RANK() OVER (ORDER BY Id DESC) AS [orderId]
FROM
(SELECT Id, Name FROM #tb1
UNION
SELECT Id, Name FROM #tb2) as tb3) AS TB
WHERE [orderId] = 1
Results are:

need empty row in output between rows with data Report Builder 3.0 T-SQL

I have reports that pull 50 random records. i would like to insert a blank Row in the output between each row of data. for example, Rows 1,3,5,7... are populated with data, and even number rows are empty.
Thanks
CROSS APPLY with NULL valued table gives equal number of rows as original table.
Now we can generate ROW_NUMBER for two SELECTs and sort it by row number to get alternate values.
select C.id, C.name, ROW_NUMBER() OVER ( ORDER BY C.id) as seq from TableC C
UNION ALL
SELECT T.id, T.name, seq as seq
FROM
(
select T.id, T.name ,ROW_NUMBER() OVER ( ORDER by C.id ) as seq from TableC C
cross apply ( select NULL as id,NULL as name ) T
) T
ORDER BY seq
A very simple solution could be :
Select id+Temp id, name+Temp name from TableA A
Cross Apply
(select '' as 'Temp'
union all
Select null) X
If you have large number of columns, then create the concatenation of column_name + temp dynamically and use that in dynamic sql
for Demo Click on --> DEMO

Resources