Unioned queries with subqueries disobeys order by clauses - sql

I have a query:
select * from (select top 100 percent ... from [table]
where [field1] is not null
order by [field1]) a
union all
select * from (select top 100 percent ... from [table]
where [field1] is null
order by [field2]) b
The subqueries are ordering their results correctly. I want the exact results of the first subquery, THEN the exact results of the second subquery. However, I'm actually getting the concatenated results ordered in some odd way that I can't even discern a pattern from. I have [field2] values scattered about the result set apparently at random.
How can I get the first query results on top, and the second query results on bottom?
Edit: To clarify, I want the top query ordered by its own conditions, and then I want the bottom query ordered by its own conditions.

I am not sure, whether or not UNION ALL is meant to preserve the order of rows.
You can force the order you want by using the following statement:
select [field1] from
(
select * from
(select 1 as query_number, [field1], [field1] as [order_field] from [table]
where [field1] is not null) a
union all
select * from
(select 2 as query_number, [field1], [field2] as [order_field] from [table]
where [field1] is null) b
)
order by query_number, [order_field];

select * from (select top 100 percent ..., 1 AS q from [table]
where [field1] is not null
order by [field1]) a
union all
select * from (select top 100 percent ..., 2 AS q from [table]
where [field1] is null
order by [field2]) b
order by q
, CASE q
WHEN 1 THEN fieldX
WHEN 2 THEN fieldY
END

Related

SQL - Insert NULL entry with Union then order by results

Im trying to order a list of results by their description (not id) but i also need to insert another result with a null Id and description of 'ALL'.
To do this I use UNION SELECT NULL, 'ALL'
It should be a pretty simple query but i cant figure out how to insert 'ALL' before the query orders the actual result set.
Here is what I have so far:
SELECT *
FROM [dbo].[HouseType]
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
UNION SELECT NULL, 'ALL'
ORDER BY [TypeDesc]
Ive also tried calling UNION SELECT before the results are ordered but it doesnt look SQL allows this.
UNION SELECT NULL, 'ALL'
SELECT * FROM
(
SELECT *
FROM [dbo].[HouseType]
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
ORDER BY [TypeDesc]
)types
Basically I want to see the following results (ordered):
ALL
Bungalow
Detached
Semi-Detached
Terrace
Try using another column:
SELECT t.ID, t.description, 2 AS o
FROM [dbo].[HouseType] AS t
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
UNION ALL
SELECT NULL, 'ALL', 1 AS o
ORDER BY o, [TypeDesc]
This way ALL will always precede the results of your original query.
Note: As already noted in a comment there must be a match between the number and type of fields of the subqueries used in the UNION operation.
Note 2: As noted in the other comment by #A ツ using UNION ALL is always preferable over UNION, so use this instead when you have no worries about duplicate values.
Try it in a subquery
SELECT *
FROM(
SELECT *
FROM [dbo].[HouseType]
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
UNION
SELECT NULL, 'ALL')
ORDER BY [TypeDesc]
DECLARE #HouseType TABLE (HouseType VARCHAR(30))
INSERT INTO #HouseType
SELECT HouseType
FROM [dbo].[HouseType]
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
ORDER BY [TypeDesc]
INSERT INTO #HouseType
SELECT 'ALL'
SELECT HouseType FROM #HouseType ORDER BY HouseType
I think you are looking for conditional sorting:
SELECT *
FROM [dbo].[HouseType]
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
UNION ALL
SELECT NULL, 'ALL'
ORDER BY CASE WHEN [TypeDesc] = 'ALL' THEN 0 ELSE 1 END, [TypeDesc]
btw, I would suggest agains the use of * and explicitly specify the column names in the select list.
This is always best practice, espcially when using UNION - this query will raise an error if you ever add (or remove) a column from the HouseType table.
Assigns a Row to each TypeDesc using ROW_NUMBER function (+1 to go after the 'ALL') sorted by TypeDesc:
select 1 as [Row], 'All' as [TypeDesc]
union
SELECT ROW_NUMBER() OVER (ORDER BY TypeDesc) + 1 AS [Row], [TypeDesc]
FROM [dbo].[HouseType]
WHERE [TypeId] = COALESCE(#TypeId, [TypeId])
ORDER BY [Row]

SELECT TOP ... FROM UNION

What is the best way to SELECT TOP N records from UNION of 2 queries?
I can't do
SELECT TOP N ... FROM
(SELECT ... FROM Table1
UNION
SELECT ... FROM Table2)
because both queries return huge results I need every bit of optimization possible and would like to avoid returning everything. For the same reason I cannot insert results into #TEMP table first either.
I can't use SET ROWCOUNT N either because I may need to group results and this command will limit number of grouped rows, and not underlying row selections.
Any other ideas? Thanks!
Use the Top keyword for inner queries also:
SELECT TOP N ... FROM
(SELECT TOP N... FROM Table1
UNION
SELECT TOP N... FROM Table2) as result
You could try the following. It uses a CTE which would give you the results of both queries first and then you would select your TOP N from the CTE.
WITH table_cte (
(
[col1],
[col2],
...
)
AS
(
SELECT *
FROM table1
UNION ALL
SELECT *
FROM table2
)
SELECT TOP 1000 * FROM table_cte

Using different order by with union

I want to write a query like
select top 10 * from A
order by price
union
select top 3 * from A
order by price
or sth like that
select top 10 * from A
where name like '%smt%'
order by price
union
select top 3 * from A
where name not like '%smt%'
order by price
Can you please help me?
This should work:
SELECT *
FROM (SELECT TOP 10 A.*, 0 AS Ordinal
FROM A
ORDER BY [Price]) AS A1
UNION ALL
SELECT *
FROM (SELECT TOP 3 A.*, 1 AS Ordinal
FROM A
ORDER BY [Name]) AS A2
ORDER BY Ordinal
From MSDN:
In a query that uses UNION, EXCEPT, or INTERSECT operators, ORDER BY
is allowed only at the end of the statement. This restriction applies
only to when you specify UNION, EXCEPT and INTERSECT in a top-level
query and not in a subquery.
Edited: to force the order you need to apply an ORDER BY to the outer query. I've added a constant value column to both queries.
This is a real hacky way to do this. You probably want these as separate queries in reality, but this should give you the result you want...
select *
from (
select top 10 *, 1 as 'ord', price as 'ordprice' from A
union
select top 3 *, 2 as 'ord', 0 as 'ordprice' from A
) a
order by ord, ordprice, name
UNION doesn't like ORDER by clauses in the UNIONed expressions.
Try this:
SELECT * FROM
(SELECT TOP 10 * FROM A ORDER BY Price) SetA
UNION
SELECT * FROM
(SELECT TOP 3 * FROM a ORDER BY name) Setb
[ORDER BY something]
This spoofs the UNION operator into ignoring the ORDER BYs, which still operate correctly on the TOP operator.
You can apply a final ORDER BY to order the UNIONed set, if you like.
[No longer applies exactly to your question now that it's edited!]
select top 10 *,0 as RS from A
union
select top 3 *,1 as RS from A
order by
RS,
CASE WHEN RS=0 THEN price END, --Don't affect RS 1
name
cmd.CommandText = "SELECT 0 AS Employee_ID, 'No Employees' as Employee_FullName , 'id1' Orderkey
UNION ALL
SELECT Employee_ID, Employee_FullName, 'id2' Orderkey
FROM tblEmployee
ORDER BY Orderkey, Employee_FullName"
ds = dbconn.SelectQuery(cmd)
ds.Tables(0).Columns.Remove(ds.Tables(0).Columns("Orderkey"))

Sql inner query issue

I have a table tbl_test:
create table tbl_test (
tabid int identity
)
with the values:
Insert into tbl_test values 1 union 2 union 3 .... union 1000
Query:
select MAX(b.tabid) from
(
select top 100 * from tbl_test
) as b
I expect this query to return 100 but instead it returns 1000.
select top 100 * from tbl_test
There is no explicit order on the inner statement, so there is no guarentee in which order the rows are read. If you order it by tabid ASC you should see the expected 100.
You're not including an order by clause in your subquery (which is allowed in conjunction with TOP), so there's no telling what records will come back. 1000 is obviously being included in the data returned from the subquery, which means it will be returned by MAX.

SQL Server Top 1

In Microsoft SQL Server 2005 or above, I would like to get the first row, and if there is no matching row, then return a row with default values.
SELECT TOP 1 ID,Name
FROM TableName
UNION ALL
SELECT 0,''
ORDER BY ID DESC
This works, except that it returns two rows if there is data in the table, and 1 row if not.
I'd like it to always return 1 row.
I think it has something to do with EXISTS, but I'm not sure.
It would be something like:
SELECT TOP 1 * FROM Contact
WHERE EXISTS(select * from contact)
But if not EXISTS, then SELECT 0,''
What happens when the table is very full and you might want to specify which row of your top 1 to get, such as the first name? OMG Ponies' query will return the wrong answer in that case if you just change the ORDER BY clause. His query also costs about 8% more CPU than this modification (though it has equal reads)
SELECT TOP 1 *
FROM (
SELECT TOP 1 ID,Name
FROM TableName
ORDER BY Name
UNION ALL
SELECT 0,''
) X
ORDER BY ID DESC
The difference is that the inner query has a TOP 1 also, and which TOP 1 can be specified there (as shown).
Just for fun, this is another way to do it which performs very closely to the above query (-15ms to +30ms). While it's more complicated than necessary for such a simple query, it demonstrates a technique that I don't see other SQL folks using very often.
SELECT
ID = Coalesce(T.ID, 0),
Name = Coalesce(T.Name, '')
FROM
(SELECT 1) X (Num)
LEFT JOIN (
SELECT TOP 1 ID, Name
FROM TableName
ORDER BY ID DESC
) T ON 1 = 1 -- effective cross join but does not limit rows in the first table
Use:
SELECT TOP 1
x.id,
x.name
FROM (SELECT t.id,
t.name
FROM TABLENAME t
UNION ALL
SELECT 0,
'') x
ORDER BY id DESC
Using a CTE equivalent:
WITH query AS (
SELECT t.id,
t.name
FROM TABLENAME t
UNION ALL
SELECT 0,
'')
SELECT TOP 1
x.id,
x.name
FROM query x
ORDER BY x.id DESC
CREATE TABLE #sample(id INT, data VARCHAR(10))
SELECT TOP 1 id, data INTO #temp FROM #sample
IF ##ROWCOUNT = 0 INSERT INTO #temp VALUES (null, null)
SELECT * FROM #temp
put the top oustide of the UNION query
SELECT TOP 1 * FROM(
SELECT ID,Name
FROM TableName
UNION ALL
SELECT 0,''
) z
ORDER BY ID DESC
IF EXISTS ( SELECT TOP 1 ID, Name FROM TableName )
BEGIN
SELECT TOP 1 ID, Name FROM TableName
END
ELSE
BEGIN
--exists returned no rows
--send a default row
SELECT 0, ''
END