How to select top x from with params? - sql

I'm using Sql-Server 2005
I have Users table with userID and gender. I want to select top 1000 males(0) and top 1000 females(1) order by userID desc.
If i create union only one result set is ordered by userID desc. What other way to do that?
SELECT top 1000 *
FROM Users
where gender=0
union
SELECT top 1000 *
FROM Users
where gender=1
order by userID desc

Another way of doing it
WITH TopUsers AS
(
SELECT UserId,
Gender,
ROW_NUMBER() OVER (PARTITION BY Gender ORDER BY UserId DESC) AS RN
FROM Users
WHERE Gender IN (0,1) /*I guess this line might well not be needed*/
)
SELECT UserId, Gender
FROM TopUsers
WHERE RN <= 1000
ORDER BY UserId DESC

Martin Smith's solution is better than the following.
SELECT UserID, Gender
FROM
(SELECT TOP 1000 UserId, Gender
FROM Users
WHERE gender = 0
ORDER BY UserId DESC) m
UNION ALL
SELECT UserID, Gender
FROM
(SELECT TOP 1000 UserId, Gender
FROM Users
WHERE gender = 1
ORDER BY UserId DESC) f
ORDER BY Gender, UserID DESC
This does what you want, just change the order by if you'd rather have the latest user first, but it will get you the top 1000 of each.

Done some testing, and the results are pretty strange. If you specify an order by in both parts of a union, SQL Server gives a syntax error:
select top 2 * from #users where gender = 0 order by id
union all
select top 2 * from #users where gender = 1 order by id
That makes sense, because the order by should only be at the end of the union. But if you use the same construct in a subquery, it compiles! And works as expected:
select * from (
select top 2 * from #users where gender = 0 order by id
union all
select top 2 * from #users where gender = 1 order by id
) sub
The strangest thing happens when you specify only one order by for the subquery union:
select * from (
select top 2 * from #users where gender = 0
union all
select top 2 * from #users where gender = 1 order by id
) sub
Now it orders the first half of the union at random, but the second half by id. That's pretty unexpected. The same thing happens with the order by in the first half:
select * from (
select top 2 * from #users where gender = 0 order by id desc
union all
select top 2 * from #users where gender = 1
) sub
I'd expect this to give a syntax error, but instead it orders the first half of the union. So it looks like union interacts with order by in a different way when the union is part of a subquery.
Like Chris Diver originally posted, a good way to get out of the confusion is not to rely on the order by in a union, and specify everything explicitly:
select *
from (
select *
from (
select top 2 *
from #users
where gender = 0
order by
id desc
) males
union all
select *
from (
select top 2 *
from #users
where gender = 1
order by
id desc
) females
) males_and_females
order by
id
Example data:
declare #users table (id int identity, name varchar(50), gender bit)
insert into #users (name, gender)
select 'Joe', 0
union all select 'Alex', 0
union all select 'Fred', 0
union all select 'Catherine', 1
union all select 'Diana', 1
union all select 'Esther', 1

You need to ensure that you create a sub-select for the union, then do the ordering outside over the combined results.
Something like this should work:
SELECT u.*
FROM (SELECT u1a.* FROM (SELECT TOP 1000 u1.*
FROM USERS u1
WHERE u1.gender = 0
ORDER BY u1.userid DESC) u1a
UNION ALL
SELECT u2a.* FROM (SELECT TOP 1000 u2.*
FROM USERS u2
WHERE u2.gender = 1
ORDER BY u2.userid DESC) u2a
) u
ORDER BY u.userid DESC
Also, using a UNION ALL will give better performance as the db won't bother checking for duplicates (which there won't be in this query) in the results.

Related

SELECT TOP (1) from DISTINCT values

I have data from machine PLCs and I am gathering that into SQL database.
I have several machines with "machineID" = 19, 21, 24, 25, .. .etc.
I have thousands of logs from these machines with actual realtime operating data.
I need to write the query to get the last query from each machine.
What I want to perform is something like this:
SELECT DISTINCT machineID
FROM [moh01dtd].[dbo].[MasterPLC_Data]
ORDER BY machineID;
This gives me the results of all machine IDs that are in the table.
Now I want to find the last row for each machine that was written to the table. So something like this:
SELECT TOP(1) *
FROM [moh01dtd].[dbo].[MasterPLC_Data]
WHERE machineID IN (SELECT DISTINCT machineID
FROM [moh01dtd].[dbo].[MasterPLC_Data]);
But as expected it is not working. Basically I would need to perform
SELECT TOP(1) *
FROM [moh01dtd].[dbo].[MasterPLC_Data]
WHERE machineID = <parameter>
and <parameter>
is the rows from the
SELECT DISTINCT machineID
FROM [moh01dtd].[dbo].[MasterPLC_Data]
ORDER BY machineID;
First you have to define what the last query means. Do you have an IDENTITY column in the table or a timestamp or something else.
Assuming you have an IDENTITY column, you can do:
SELECT *
FROM (
SELECT *, ROW_NUMBER() OVER (PARTITION BY machineID ORDER BY id DESC) AS rn
FROM moh01dtd.dbo.MasterPLC_Data
) AS q
WHERE rn=1
You can get last value from group by machineID
Example:
with MasterPLC_Data as(
select id=1,machineID = 1,field1='1',field2='1'
union all select id=2,machineID = 1,field1='2',field2='2'
union all select id=3,machineID = 1,field1='3',field2='3'
union all select id=4,machineID = 2,field1='4',field2='4'
union all select id=5,machineID = 2,field1='5',field2='5'
union all select id=6,machineID = 3,field1='4',field2='4'
union all select id=7,machineID = 3,field1='5',field2='5'
union all select id=8,machineID = 3,field1='6',field2='6'
)
SELECT m.*
FROM MasterPLC_Data m
join (select machineID,max_id = max(id)
from MasterPLC_Data
group by machineID
) t on m.id = t.max_id
result
id
machineID
field1
field2
3
1
3
3
5
2
5
5
8
3
6
6

SQL - How to Order By in UNION query

Is there a way to union two tables, but keep the rows from the first table appearing first in the result set? However orderby column is not in select query
For example:
Table 1
name surname
-------------------
John Doe
Bob Marley
Ras Tafari
Table 2
name surname
------------------
Lucky Dube
Abby Arnold
Result
Expected Result:
name surname
-------------------
John Doe
Bob Marley
Ras Tafari
Lucky Dube
Abby Arnold
I am bringing Data by following query
SELECT name,surname FROM TABLE 1 ORDER BY ID
UNION
SELECT name,surname FROM TABLE 2
The above query is not keeping track of order by after union.
P.S - I dont want to show ID in my select query
I am getting ORDER BY Column by joining tables. Following is my real query
SELECT tbl_Event_Type_Sort_Orders.Appraisal_Event_Type_ID AS Appraisal_Event_Type_ID , ISNULL(tbl_Appraisal_Event_Types.Appraisal_Event_Type_Display_Name, 'UnCategorized') AS Appraisal_Event_Type_Display_Name
INTO #temptbl
FROM tbl_Event_Type_Sort_Orders
INNER JOIN tbl_Appraisal_Event_Types
ON tbl_Event_Type_Sort_Orders.Appraisal_Event_Type_ID = tbl_Appraisal_Event_Types.Appraisal_Event_Type_ID
WHERE 1=1
AND User_Name='abc'
ORDER BY tbl_Event_Type_Sort_Orders.Sort_Order
SELECT * FROM #temptbl
UNION
SELECT DISTINCT (tbl_Appraisal_Event_Types.Appraisal_Event_Type_ID) AS Appraisal_Event_Type_ID , ISNULL(tbl_Appraisal_Event_Types.Appraisal_Event_Type_Display_Name, 'UnCategorized') AS Appraisal_Event_Type_Display_Name
FROM tbl_Appraisal_Event_Types
INNER JOIN tbl_Appraisal_Events
ON tbl_Appraisal_Event_Types.Appraisal_Event_Type_ID = tbl_Appraisal_Events.Event_Type_ID
INNER JOIN tbl_Appraisals
ON tbl_Appraisal_Events.Appraisal_ID = tbl_Appraisal_Events.Appraisal_ID
WHERE 1=1
AND ((tbl_Appraisals.Assigned_To_Staff_User) = 'abc' OR (tbl_Appraisals.Assigned_To_Staff_User2) = 'abc' OR (tbl_Appraisals.Assigned_To_Staff_User3) = 'abc')
Put a UNION ALL in a derived table. To keep duplicate elimination, do select distinct and also add a NOT EXISTS to second select to avoid returning same person twice if found in both tables:
select name, surname
from
(
select distinct name, surname, 1 as tno
from table1
union all
select distinct name, surname, 2 as tno
from table2 t2
where not exists (select * from table1 t1
where t2.name = t1.name
and t2.surname = t1.surname)
) dt
order by tno, surname, name
You can use a column for the table and one for the ID to order by:
SELECT x.name, x.surname FROM (
SELECT ID, TableID = 1, name, surname
FROM table1
UNION ALL
SELECT ID = -1, TableID = 2, name, surname
FROM table2
) x
ORDER BY x.TableID, x.ID
You can write as below, if you are ok with duplicate data then please use UNION ALL it will be faster:
SELECT NAME, surname FROM (
SELECT ID,name,surname FROM TABLE 1
UNION
SELECT ID,name,surname FROM TABLE 2 ) t ORDER BY ID
this will order the first row sets first then by anything you need
(haven't tested the code)
;with cte_1
as
(SELECT ID,name,surname,1 as table_id FROM TABLE 1
UNION
SELECT ID,name,surname,2 as table_id FROM TABLE 2 )
SELECT name, surname
FROM cte_1
ORDER BY table_id,ID
simply use a UNION clause with out order by.
SELECT name,surname FROM TABLE 1
UNION
SELECT name,surname FROM TABLE 2
if you wanted to order first table use the below query.
;WITH cte_1
AS
(SELECT name,surname,ROW_NUMBER()OVER(ORDER BY Id)b FROM TABLE 1 )
SELECT name,surname
FROM cte_1
UNION
SELECT name,surname
FROM TABLE 2

Use SQL to query in order except the first record

I have an idea, e.g. I have a table containing name(Ann, Ben, Chris, Tom, John),
I want to query it using sql from letter a first, z last.
But I have a condition that I want to put John in the first record.
select name
from names
order by
case when name = 'John' then 0 else 1 end,
name
(SELECT * FROM atable WHERE username = 'John')
UNION ALL
(SELECT * FROM atable WHERE username <> 'John' ORDER BY username)
Or more general:
(SELECT * FROM atable ORDER BY username DESC LIMIT 1)
UNION ALL
(SELECT * FROM atable WHERE id NOT IN (
SELECT id FROM atable ORDER BY username DESC LIMIT 1)
ORDER BY username)
If you have to avoid the union for some reason, this slower code will also work:
SELECT * FROM atable
ORDER BY
CASE WHEN id IN (SELECT id FROM atable ORDER BY username DESC LIMIT 1)
THEN 0 ELSE 1 END
, username
In SQL-server the syntax is slightly different, the subquery is:
SELECT TOP 1 id FROM atable ORDER BY username DESC
It is simple:
(SELECT Name
FROM Users
WHERE Name = 'John')
UNION ALL
(SELECT *
FROM Users
WHERE Name <> 'John'
ORDER BY Name)
Placing a case statement in the order by clause does not work with select distinct. I find the following more intuitive and works if you also need select distinct.Although it does return an extra column in the result set.
DECLARE #names TABLE
(
Name varchar(20)
)
INSERT INTO #names
SELECT 'Tom'
INSERT INTO #names
SELECT 'John'
INSERT INTO #names
SELECT 'Chris'
INSERT INTO #names
SELECT 'Ann'
INSERT INTO #names
SELECT 'Ben'
select Name, case when Name = 'John' then 1 else 0 end AS IsTopRow
from #names
order by IsTopRow DESC, Name
Results:
Name IsTopRow
John 1
Ann 0
Ben 0
Chris 0
Tom 0

How do I get records before and after given one?

I have the following table structure:
Id, Message
1, John Doe
2, Jane Smith
3, Error
4, Jane Smith
Is there a way to get the error record and the surrounding records? i.e. find all Errors and the record before and after them.
;WITH numberedlogtable AS
(
SELECT Id,Message,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM logtable
)
SELECT Id,Message
FROM numberedlogtable
WHERE RN IN (SELECT RN+i
FROM numberedlogtable
CROSS JOIN (SELECT -1 AS i UNION ALL SELECT 0 UNION ALL SELECT 1) n
WHERE Message='Error')
WITH err AS
(
SELECT TOP 1 *
FROM log
WHERE message = 'Error'
ORDER BY
id
),
p AS
(
SELECT TOP 1 l.*
FROM log
WHERE id <
(
SELECT id
FROM err
)
ORDER BY
id DESC
)
SELECT TOP 3 *
FROM log
WHERE id >
(
SELECT id
FROM p
)
ORDER BY
id
Adapt this routine to pick out your target.
DECLARE #TargetId int
SET #TargetId = 3
select *
from LogTable
where Id in (-- "before"
select max(Id)
from LogTable
where Id < #TargetId
-- target
union all select #TargetId
-- "after"
union all select min(Id)
from LogTable
where Id > #TargetId)
select id,messag from
(Select (Row_Number() over (order by ID)) as RNO, * from #Temp) as A,
(select SubRNO-1 as A,
SubRNO as B,
SubRNO+1 as C
from (Select (Row_Number() over (order by ID)) as SubRNO, * from #Temp) as C
where messag = 'Error') as B
where A.RNO = B.A or A.RNO = B.B or A.RNO = B.C
;WITH Logs AS
(
SELECT ROW_NUMBER() OVER (ORDER BY id), id, message as rownum FROM LogTable lt
)
SELECT curr.id, prev.id, next.id
FROM Logs curr
LEFT OUTER JOIN Logs prev ON curr.rownum+1=prev.rownum
RIGHT OUTER JOIN Logs next ON curr.rownum-1=next.rownum
WHERE curr.message = 'Error'
select id, message from tbl where id in (
select id from tbl where message = "error"
union
select id-1 from tbl where message = "error"
union
select id+1 from tbl where message = "error"
)
Get fixed number of rows before & after target
Using UNION for a simple, high performance query (I found selected answer WITH query above to be extremely slow)
Here is a high performance alternative to the WITH top selected answer, when you know an ID or specific identifier for a given record, and you want to select a fixed number of records BEFORE and AFTER that record. Requires a number field for ID, or something like date that can be sorted ascending / descending.
Example: You want to select the 10 records before and after a specific error was recorded, you know the error ID, and can sort by date or ID.
The following query gets (inclusive) the 1 result above, the identified record itself, and the 1 record below. After the UNION, the results are sorted again in descending order.
SELECT q.*
FROM(
SELECT TOP 2
id, content
FROM
the_table
WHERE
id >= [ID]
ORDER BY id ASC
UNION
SELECT TOP 1
id, content
FROM
the_table
WHERE
id < [ID]
ORDER BY id DESC
) q
ORDER BY q.id DESC

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