SELECT TOP (1) from DISTINCT values - sql

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

Related

Get single column each row difference in SQL Server (alternative of LEAD function)

Get single column each row difference in SQL Server.
In my table, ORIGINAL_DATA is my current column and want to generate new EXPECTED_OUTPUT column with show the difference of each row.
like 40-30 = 10 , 30-25 = 5, 25-10 = 15.
SELECT 10 ORIGINAL_DATA,0 EXPECTED_OUTPUT UNION
SELECT 25, 15 UNION
SELECT 30, 5 UNION
SELECT 40, 10
I have used the LEAD function but it is not supported by my current version of SQL Server.
So can you please help me to solve this without LEAD and SELF JOIN?
As my query already taking too much time, here i have mention only sample data only.
Try this
WITH rows AS
(SELECT Column1 ,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS rn
FROM (
SELECT 10 Column1 UNION
SELECT 25 UNION
SELECT 30 UNION
SELECT 40
)M)
SELECT
--mp.Column1 ,
mc.Column1,
--mc.rn,
--mp.rn,
CAST(mc.Column1 AS float) - CAST(mp.Column1 AS float) EXPECTED_OUTPUT
FROM rows mc
LEFT JOIN rows mp
ON mp.rn = mc.rn - 1;
Try this, change SQL Script as per table structure Partition By A and Order By B
SELECT ORIGINAL_DATA,
ORIGINAL_DATA-LEAD(ORIGINAL_DATA, 1, 0) OVER (PARTITION BY A ORDER BY B DESC) AS EXPECTED_OUTPUT
FROM Table1

Union Select Only One Row

I Have a query with Two Select Clause combines with UNION.I want to select only top first row. How can i do that Using Union ?
Select Fault,OccurredOn From ATMStatus Where Ticket=189703 // This Will retrieve single record as the primary key is applied
Union
Select Fault,OccurredOn From ATMStatusHistory Where Resolved=0 AND Ticket=189703 Order By OccurredOn Desc
select top 1 * from
(
Select Fault,OccurredOn
From ATMStatus
Where Ticket=189703
Union
Select Fault,OccurredOn
From ATMStatusHistory
Where Resolved=0 AND Ticket=189703
) x
Order By OccurredOn Desc
This returns 2 rows:
select 1 as id
union
select 2 as id
This returns 1 row:
select top 1 * from (
select 1 as id
union
select 2 as id
) as x
order by id

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

How to select top x from with params?

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.

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