Max with other fields in SQL Server - sql

Here is my query :
SELECT id, id_gamut, position from operation
It returns :
N°1) id id_gamut position
--------------------------
1 19 1
2 19 2
3 19 3
4 25 1
5 25 2
6 12 1
I need to group it by id_gamut with the max position to get a result like this :
N°2) id id_gamut position
--------------------------
3 19 3
5 25 2
6 12 1
Then i tried something like this :
SELECT gamut, Max(position) from(
SELECT id, gamut, position from operation) as req
GROUP BY gamut
It works, but my problem is i really need to have the field 'id' in my query, but if i add it like this :
SELECT id, gamut, Max(position) from(
SELECT id, gamut, position from operation) as req
GROUP BY gamut,id
My group by is broken and i have a result like result N°1
How can i group by id_gamut with max position and having the 'id' field too ?

using top with ties with row_number()
select top 1 with ties
id
, id_gamut
, position
from operation
order by row_number() over (
partition by id_gamut
order by position desc
)
Or using a common table expression with row_number()
;with cte as (
select *
, rn = row_number() over (
partition by id_gamut
order by position desc
)
from operation
)
select
id
, id_gamut
, position
from cte
where rn = 1
Or as a subquery without the cte
select
id
, id_gamut
, position
from (
select *
, rn = row_number() over (
partition by id_gamut
order by position desc
)
from operation
) s
where rn = 1
Or with cross apply()
select distinct
x.id
, o.id_gamut
, x.position
from operation o
cross apply (
select top 1
id
, position
from operation i
where i.id_gamut = o.id_gamut
order by position desc
) x
Or with not exists() (This will return more than 1 row per id_gamut if there is more than one row with the same max position)
select *
from operation o
where not exists (
select 1
from operation i
where i.id_gamut = o.id_gamut
and i.position > o.position
)
Or with not exists() (Additional clause to return highest id in the event of multiple rows with the same max position)
select *
from operation o
where not exists (
select 1
from operation i
where i.id_gamut = o.id_gamut
and (i.position > o.position
or (i.position = o.position and i.id > o.id)
)
)
rextester demo: http://rextester.com/INV77202

Using ROW_NUMBER() inside Common Table Expression
WITH CTE AS
(
SELECT id, id_gamut, position
, ROW_NUMBER() OVER (PARTITION BY id_gamut ORDER BY position DESC) AS Rn
FROM operation
)
SELECT * FROM CTE WHERE Rn = 1

Use MAX function and GROUP BY clause :
SELECT *
FROM table1
JOIN
(
SELECT id_gamut,MAX(position) position
GROUP BY id_gamut
) A ON A.id_gamut = table1.id_gamut AND A.position = table1.position

Related

SQL get entries where on attribute is max

I have the following dataset:
id
id_rev
time
1
1
08.01.2022
1
0
31.02.2021
2
2
28.01.2017
2
1
25.07.2021
2
0
25.07.2021
I am looking for a SQL query that can return an entry per id but only the one where the id_rev is maximum. So in this case it should return these two rows:
(id=1, id_rev=1,time)
(id=2, id_rev=2, time)
One canonical approach uses ROW_NUMBER:
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (PARTITION BY id ORDER BY id_rev DESC) rn
FROM yourTable t
)
SELECT id, id_rev, time
FROM cte
WHERE rn = 1
ORDER BY id;
Another approach would be to use exists logic:
SELECT id, id_rev, time
FROM yourTable t1
WHERE NOT EXISTS (
SELECT 1
FROM yourTable t2
WHERE t2.id = t1.id AND t2.id_rev > t1.id_rev
);
#result =
SELECT
*,
RANK()
OVER (PARTITION BY id ORDER BY id_rev DESC) AS Rank
FROM dataset ORDER BY Rank;
#result =
SELECT *
FROM #result
WHERE Rank = 1;

Find the latest 3 records with the same status

I need to find the latest 3 records for each user that has a particular status on 'Fail'. At first it seems easy but I just can't seem to get it right.
So in a table of:
ID Date Status
1 2017-01-01 Fail
1 2017-01-02 Fail
1 2017-02-04 Fail
1 2015-03-21 Pass
1 2014-02-19 Fail
1 2016-10-23 Pass
2 2017-01-01 Fail
2 2017-01-02 Pass
2 2017-02-04 Fail
2 2016-10-23 Fail
I would expect ID 1 to be returned as the most recent 3 records are fails, but not ID 2, as they have a pass within their three fails. Each user may have any number of Pass and Fail records. There are thousands of different IDs
So far I've tried a CTE with ROW_NUMBER() to order the attempts but can't think of a way to ensure that the latest three results all have the same status of Fail.
Expected Results
ID Latest Fail Date Count
1 2017-02-04 3
Maybe try something like this:
WITH cte
AS
(
SELECT id,
date,
status,
ROW_NUMBER () OVER (PARTITION BY id ORDER BY date DESC) row
FROM #table
),cte2
AS
(
SELECT id, max(date) as date, count(*) AS count
FROM cte
WHERE status = 'fail'
AND row <= 3
GROUP BY id
)
SELECT id,
date AS latest_fail,
count
FROM cte2
WHERE count = 3
Check This.
Demo : Here
with CTE as
(
select *,ROW_NUMBER () over( partition by id order by date desc) rnk
from temp
where Status ='Fail'
)
select top 1 ID,max(DATE) as Latest_Fail_Date ,COUNT(rnk) as count
from CTE where rnk <=3
group by ID
Ouptut :
I think you can do this using cross apply:
select i.id
from (select distinct id from t) i cross apply
(select sum(case when t.status = 'Fail' then 1 else 0 end) as numFails
from (select top 3 t.*
from t
where t.id = i.id
order by date desc
) ti
) ti
where numFails = 3;
Note: You probably have a table with all the ids. If so, you an use that instead of the select distinct subquery.
Or, similarly:
select i.id
from (select distinct id from t) i cross apply
(select top 3 t.*
from t
where t.id = i.id
order by date desc
) ti
group by i.id
having min(ti.status) = 'Fail' and max(ti.status) = 'Fail' and
count(*) = 3;
Here you go:
declare #numOfTries int = 3;
with fails_nums as
(
select *, row_number() over (partition by ID order by [Date] desc) as rn
from #fails
)
select ID, max([Date]) [Date], count(*) as [count]
from fails_nums fn1
where fn1.rn <= #numOftries
group by ID
having count(case when [Status]='Fail' then [Status] end) = #numOfTries
Example here

Oracle Database Rownum Between

There are 25 records in this sql query
I want to get between 5 and 10. How can I do it ?
I use 11g
select
(
select count(*) as sayfasayisi
from konular t
where t.kategori is not null
) as sayfasayisi,
t.id,
t.uye,
t.baslik,t.mesaj,t.kategori,t.tarih,
t.edittarih,t.aktif,t.indirimpuani,t.altkategori,t.link,
nvl(
(select case when t.id = f.konuid and f.uye = 'test' then '1' else '0' end
from takipkonu f where t.id = f.konuid and f.uye = 'test'), '0') as takip
from konular t
where t.kategori is not null
You can use ROW_NUMBER() to assign a row number based on some ordering logic contained in your current query, e.g. a certain column. Then, retain only the 5th to 10th records:
select t.*
from
(
select
(
select count(*) as sayfasayisi
from konular t
where t.kategori is not null
) as sayfasayisi,
ROW_NUMBER() OVER (ORDER BY some_col) rn,
t.id,
t.uye,
...
) t
where t.rn between 5 and 10;

Sql Select top 2 , bottom 2 and 6 random records

How to select top 2 , bottom 2 and 6 random (not in Top 2 and Bottom 2) records of the table using one SQL select query?
In MS SQL 2005/2008:
with cte
as
(
select
row_number() over (order by name) RowNumber,
row_number() over (order by newid()) RandomOrder,
count(*) over() Total,
*
from sys.tables
)
select *
from cte
where RowNumber <= 2 or Total - RowNumber + 1 <= 2
union all
select *
from
(
select top 6 *
from cte
where RowNumber > 2 and Total - RowNumber > 2
order by RandomOrder
) tt
Replace sys.tables with your table name and alter order by name to specify order condition for top 2 and bottom 2.
Perhaps not a single select statment, but it can be executed in one call:
/* Top 2 - change order by to get the 'proper' top 2 */
SELECT * from table ORDER BY id DESC LIMIT 2
UNION ALL
/* Random 6.. You may want to add a WHERE and random data to get the random 6 */
/* Old Statement before edit - SELECT * from table LIMIT 6 */
SELECT * from table t
LEFT JOIN (SELECT * from table ORDER BY id DESC LIMIT 2) AS top ON top.id = t.id
LEFT JOIN (SELECT * from table ORDER BY id DESC LIMIT 2) AS bottom ON bottom.id = t.id
WHERE ISNULL(top.id ) AND ISNULL(bottom.id)
ORDER BY RANDOM()
LIMIT 6
UNION ALL
/* Bottom 2 - change order by to get the 'proper' bottom 2 */
SELECT * from table ORDER BY id ASC LIMIT 2
Something along those lines. Basically the UNION All is the trick.
Assuming the "order" is by the id column:
select * from (select id, id from my_table order by id limit 2) t1
union
select * from (select id, id from my_table where id not in (
select * from (select id from my_table order by id asc limit 2) t22
union
select * from (select id from my_table order by id desc limit 2 ) t23)
order by rand()
limit 6) t2
union
select * from (select id, id from my_table order by id desc limit 2) t3
EDIT: Fixed syntax and tested query - it works

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