How to select RANK over condition (depending previous row) - sql

What we have
I have table like this
id PlayerId Amount
----------- ----------- -----------
1 1 10
2 1 20
3 1 30
4 1 40
5 1 40
11 1 20
13 1 20
15 1 40
14 2 19
12 2 10
6 2 1
7 2 5
8 2 10
9 2 20
10 2 30
I have to select only rows where amount greater than previous row amount (per player).
So here is a query
SELECT a.id,
a.PlayerId,
a.Amount,
a.PreVval,
a.NextVal
FROM (SELECT id,
PlayerId,
Amount,
LAG(Amount) OVER (PARTITION BY PlayerId ORDER BY id) PreVval,
lead(Amount) OVER (PARTITION BY PlayerId ORDER BY id) NextVal
FROM dbo.Bets ) a
WHERE a.Amount > a.PreVval OR a.Amount < a.NextVal OR (a.PreVval IS NULL AND a.Amount < a.NextVal)
ORDER BY a.PlayerId, a.id
id PlayerId Amount PreVval NextVal
----------- ----------- ----------- ----------- -----------
1 1 10 NULL 20
2 1 20 10 30
3 1 30 20 40
4 1 40 30 40
13 1 20 20 40
15 1 40 20 NULL
6 2 1 NULL 5
7 2 5 1 10
8 2 10 5 20
9 2 20 10 30
10 2 30 20 10
12 2 10 30 19
14 2 19 10 NULL
Question
So now i need to select sets where increase step count > 4 , i mean 1,2,3,4 for player 1 and 6,7,8,9,10 for player 2
Query should run over 15m rows

The following query is an example where you can set the "step_count".
WITH Bets(id,PlayerId,Amount)
AS
(
SELECT 1,1,10 UNION ALL
SELECT 2,1,20 UNION ALL
SELECT 3,1,30 UNION ALL
SELECT 4,1,40 UNION ALL
SELECT 5,1,40 UNION ALL
SELECT 11,1,20 UNION ALL
SELECT 13,1,20 UNION ALL
SELECT 15,1,40 UNION ALL
SELECT 14,2,19 UNION ALL
SELECT 12,2,10 UNION ALL
SELECT 6,2,1 UNION ALL
SELECT 7,2,5 UNION ALL
SELECT 8,2,10 UNION ALL
SELECT 9,2,20 UNION ALL
SELECT 10,2,30
)
,split_ranges
as(
select *,case when lag(amount) over(partition by playerid order by id) > amount
or lag(amount) over(partition by playerid order by id) is null
then row_number() over(partition by playerid order by id)
end as rnk_val
from bets
)
,groups_data
as(
select *
,max(rnk_val) over(partition by playerid order by id) as fill_ranges
from split_ranges
)
select * from (
select *,count(*) over(partition by playerid,fill_ranges) as cnt
from groups_data
)x
where x.cnt>=4
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=6bd815da2cbfa8f65bc999e5736f2041

The following logic is bit tricky, but you can check this out-
DEMO HERE
WITH CTE
AS
(
SELECT * FROM
(
SELECT id,
PlayerId,
Amount,
LAG(Amount) OVER (PARTITION BY PlayerId ORDER BY id) PreVval,
lead(Amount) OVER (PARTITION BY PlayerId ORDER BY id) NextVal,
ISNULL(LAG(ID,3) OVER (PARTITION BY PlayerId ORDER BY id),0) LAG3,
ISNULL(LAG(ID,2) OVER (PARTITION BY PlayerId ORDER BY id),0) LAG2,
ISNULL(LAG(ID,1) OVER (PARTITION BY PlayerId ORDER BY id),0) LAG1,
ISNULL(LEAD(ID,1) OVER (PARTITION BY PlayerId ORDER BY id),0) LEAD1,
ISNULL(LEAD(ID,2) OVER (PARTITION BY PlayerId ORDER BY id),0) LEAD2,
ISNULL(LEAD(ID,3) OVER (PARTITION BY PlayerId ORDER BY id),0) LEAD3
FROM Bets
) a
WHERE a.Amount > a.PreVval
OR a.Amount < a.NextVal
OR (a.PreVval IS NULL AND a.Amount < a.NextVal)
)
SELECT id,PlayerId,Amount,PreVval,NextVal
FROM CTE A
WHERE ID-LAG3 = 3
OR LEAD1 - LAG2 = 3
OR LEAD2 - LAG1 = 3
OR LEAD3 - ID = 3

Related

What is the most efficient SQL query to find the max N values for every entities in a table

I wrote these 2 queries, the first one is keeping duplicates and the second one is dropping them
Does anyone know a more efficient way to achieve this?
Queries are for MSSQL, returning the top 3 values
1-
SELECT TMP.entity_id, TMP.value
FROM(
SELECT TAB.entity_id, LEAD(TAB.entity_id, 3, 0) OVER(ORDER BY TAB.entity_id, TAB.value) AS next_id, TAB.value
FROM mytable TAB
) TMP
WHERE TMP.entity_id <> TMP.next_id
2-
SELECT TMP.entity_id, TMP.value
FROM(
SELECT TMX.entity_id, LEAD(TMX.entity_id, 3, 0) OVER(ORDER BY TMX.entity_id, TMX.value) AS next_id, TMX.value
FROM(
SELECT TAB.entity_id, LEAD(TAB.entity_id, 1, 0) OVER(ORDER BY TAB.entity_id, TAB.value) AS next_id, TAB.value, LEAD(TAB.value, 1, 0) OVER(ORDER BY TAB.entity_id, TAB.value) AS next_value
FROM mytable TAB
) TMX
WHERE TMP.entity_id <> TMP.next_id OR TMX.value <> TMX.next_value
) TMP
WHERE TMP.entity_id <> TMP.next_id
Example:
Table:
entity_id value
--------- -----
1 9
1 11
1 12
1 3
2 25
2 25
2 5
2 37
3 24
3 9
3 2
3 15
Result Query 1 (25 appears twice for entity_id 2):
entity_id value
--------- -----
1 9
1 11
1 12
2 25
2 25
2 37
3 9
3 15
3 24
Result Query 2 (25 appears only once for entity_id 2):
entity_id value
--------- -----
1 9
1 11
1 12
2 5
2 25
2 37
3 9
3 15
3 24
You can use the ROW_NUMBER which will allow duplicates as follows:
select entity_id, value from
(select t.*, row_number() over (partition by entity_id order by value desc) as rn
from your_Table) where rn <= 3
You can use the rank to remove the duplicate as follows:
select distinct entity_id, value from
(select t.*, rank() over (partition by entity_id order by value desc) as rn
from your_Table) where rn <= 3

How to select top 2 values for each id

I have a table with values
id sales date
1 5 "2015-01-04"
1 3 "2015-01-03"
1 1 "2015-01-01"
1 1 "2015-01-01"
2 7 "2015-01-05"
2 6 "2015-01-04"
2 4 "2015-01-03"
3 11 "2015-01-08"
3 10 "2015-01-07"
3 9 "2015-01-06"
3 8 "2015-01-05"
I want to select top two values of each id as shown in desired output.
Desired output:
id sales date
1 5 "2015-01-04"
1 3 "2015-01-03"
2 7 "2015-01-05"
2 6 "2015-01-04"
3 11 "2015-01-08"
3 10 "2015-01-07"
My attempt:
can someone help me with this. Thank you in advance!
select transactions.salesperson_id, transactions.id, transactions.date
from transactions
ORDER BY transactions.salesperson_id ASC, transactions.date DESC;
This can be done using window functions:
select id, sales, "date"
from (
select id, sales, "date",
dense_rank() over (partition by id order by "date" desc) as rnk
from transactions
) t
where rnk <= 2;
If there are multiple rows on the same date this might return more than two rows for the same ID. If you don't want that, use row_number() instead of dense_rank()
row_number() will get what you want.
select * from
(select row_number() over (partition by id order by date) as rn, sales, date from transactions) t1
where t1.rn <= 2

Remove minimum rank rows in SQL Server

I have a table like below.
Customer Order Rank
1 12 3
1 14 7
2 15 6
2 16 4
2 17 2
2 21 1
3 24 5
3 25 6
3 27 7
Now, I want to select all rows except for rows with minimum ranks for each customer. It should look like below.
Customer Order Rank
1 14 7
2 15 6
2 16 4
2 17 2
3 25 6
3 27 7
You can use a CTE + ROW_NUMBER:
WITH CTE AS
(
SELECT Customer, [Order], Rank,
RN = ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Rank)
FROM dbo.Customers
)
SELECT Customer, [Order], Rank
FROM CTE
WHERE RN > 1
ORDER BY Customer, Rank DESC
Demo: http://sqlfiddle.com/#!6/444be/3/0
WITH CTE AS (
SELECT Customer,Order,Rank,
ROW_NUMBER() OVER (PARTITION BY Customer ORDER BY Rank ) as rn FROM t
)
SELECT Customer,Order,Rank FROM CTE
WHERE rn >1

Aggregate within a group of unchanged values

I have sample data:
RowId TypeId Value
1 1 34
2 1 53
3 1 34
4 2 43
5 2 65
6 16 54
7 16 34
8 1 45
9 6 43
10 6 34
11 16 64
12 16 63
I want to count row for each type (The Value does not matter to me), but only for... neighbor TypeId
TypeId Count
1 3
2 2
16 2
1 1
6 2
16 2
How to achieve this result?
This should give you COUNT of rows within a group of unchanged values:
SELECT TypeId, grp, COUNT(*) FROM (
SELECT RowId, TypeId , Value, gap, SUM(gap) over (ORDER BY RowId ) grp
FROM (SELECT RowId, TypeId , Value,
CASE WHEN TypeId = lag(TypeId) over (ORDER BY RowId )
THEN 0
ELSE 1
END gap
FROM dummy
) t
) tt
GROUP BY TypeId, grp;
If you prefer WITH over endless sub-query inclusions:
WITH dummy_with_groups AS (
SELECT RowId, TypeId , Value, SUM(gap) OVER (ORDER BY RowId) grp
FROM (SELECT RowId, TypeId , Value,
CASE WHEN TypeId = lag(TypeId) OVER (ORDER BY RowId)
THEN 0 ELSE 1 END gap
FROM dummy) t
)
SELECT TypeId, COUNT(*) as Result
FROM dummy_with_groups
GROUP BY TypeId, grp;
http://www.sqlfiddle.com/#!6/f16e9/34
Check this fiddle demo. I have renamed your columns a little.
WITH myCTE AS
(SELECT row_id,
type_id,
ROW_NUMBER () OVER (PARTITION BY type_id ORDER BY row_id)
AS cnt,
CASE LEAD (type_id) OVER (ORDER BY row_id)
WHEN type_id THEN 0
ELSE 1
END
AS show
FROM dummy),
innerQuery AS
(SELECT row_id, type_id, cnt
FROM myCTE
WHERE show = 1)
SELECT iq1.type_id, iq1.cnt - ISNULL (iq2.cnt, 0) CNT
FROM innerQuery iq1
LEFT OUTER JOIN innerQuery iq2
ON iq1.type_id = iq2.type_id
AND EXISTS
(SELECT 1
FROM innerQuery iq3
WHERE iq3.type_id = iq1.type_id
AND iq3.row_id < iq1.row_id
HAVING MAX (iq3.row_id) = iq2.row_id)
The output is exactly as expected.

Select and aggregate last records base on order

I have different versions of the charges in a table. I want to grab and sum the last charge grouped by Type.
So I want to add 9.87, 9.63, 1.65.
I want the Parent ID , sum(9.87 + 9.63 + 1.65) as the results of this query.
We use MSSQL
ID ORDER CHARGES TYPE PARENT ID
1 1 6.45 1 1
2 2 1.25 1 1
3 3 9.87 1 1
4 1 6.54 2 1
5 2 5.64 2 1
6 3 0.84 2 1
7 4 9.63 2 1
8 1 7.33 3 1
9 2 5.65 3 1
10 3 8.65 3 1
11 4 5.14 3 1
12 5 1.65 3 1
WITH recordsList
AS
(
SELECT Type, Charges,
ROW_NUMBER() OVER (PArtition BY TYPE
ORDER BY [ORDER] DESC) rn
FROM tableName
)
SELECT SUM(Charges) totalCharge
FROM recordsLIst
WHERE rn = 1
SQLFiddle Demo
Use row_number() to identify the rows to be summed, and then sum them:
select SUM(charges)
from (select t.*,
ROW_NUMBER() over (PARTITION by type order by id desc) as seqnum
from t
) t
where seqnum = 1
Alternatively you could use a window aggregate MAX():
SELECT SUM(Charges)
FROM (
SELECT
[ORDER],
Charges,
MaxOrder = MAX([ORDER]) OVER (PARTITION BY [TYPE])
FROM atable
) s
WHERE [ORDER] = MaxOrder
;
SELECT t.PARENT_ID, SUM(t.CHARGES)
FROM dbo.test73 t
WHERE EXISTS (
SELECT 1
FROM dbo.test73
WHERE [TYPE] = t.[TYPE]
HAVING MAX([ORDER]) = t.[ORDER]
)
GROUP BY t.PARENT_ID
Demo on SQLFiddle