'How select results with a range of at most 300 points difference

I have this registers:

ID, Points
----------
1, 100
2, 130
3, 400
4, 299
5, 50
6, 600
7, 800

How select first 4 results with a range of at most 300 points difference? Example of results I expect:

ID, Points
----------
--------------->
5, 50 -----
1, 100 ----
2, 130 ----     first 4 ids with points range 300
4, 299 ----
--------------->
...

Other example:

ID, Points
----------
22, 700
20, 800
21, 850
23, 970
sql


Solution 1:[1]

(Not quite sure what's more important: the fact that you need 4 ids or that you need a range that it close to 300. In your question you have the value 800 twice, with different ids.
However, ...)

Maybe you can use a combination of a analytic functions (window functions) and a self join for this.

TABLE (see also: dbfiddle)

ID  POINTS
1   100
2   130
3   400
4   299
5   50
6   600
7   800
22  700
20  800
21  850
23  970

Row numbers and ordering

select id, points
, row_number() over ( order by points ) rn_
from t ;

-- result
ID  POINTS  RN_
5   50      1
1   100     2
2   130     3
4   299     4
3   400     5
6   600     6
22  700     7
7   800     8
20  800     9
21  850     10
23  970     11

Use this query for a self join that satisfies the required conditions ..

select 
  t1.id, t1.points as from_
, t2.id, t2.points as to_
from (
  select id, points
  , row_number() over ( order by points ) rn_
  from t
) t1 join (
  select id, points
  , row_number() over ( order by points ) rn_
  from t
) t2 on t2.points <= t1.points + 300 and t2.rn_ - t1.rn_ = 3 ;

-- result
ID  FROM_   ID  TO_
5   50      4   299
1   100     3   400
6   600     20  800
22  700     21  850
7   800     23  970

If you only want to have the first row of this output, just add a "limit" (using Oracle eg fetch first row only -> see dbfiddle).

EDIT

Considering your comments, it appears that you need 4 player IDs, which will be removed from the database (table) if they are playing, which depends on their scores being in the range (of 300 points). In order to achieve this, maybe the following views will be helpful (Why views? Because they will make the DELETE statement easier to read.)

VIEW 1 - get the IDs of the 4 players with the lowest points

create or replace view fourplayers 
as
select id, points 
from (
  select id, points
  , row_number() over ( order by points ) rn_
  from t
)
where rn_ <= 4 ;

-- test
select * from fourplayers ;

-- result
ID  POINTS
5   50
1   100
2   130
4   299

VIEW 2 - should the 4 players play?

create or replace view rangeandplay
as
select 
  count(*) numberofplayers
, min( points ) minpoints
, max( points ) maxpoints
, max( points ) - min( points ) range
, case when max( points ) - min( points ) <= 300 then 'Y' else 'N' end play
from (
  select id, points from fourplayers
) ;

-- test
select * from rangeandplay ;

-- result
NUMBEROFPLAYERS MINPOINTS   MAXPOINTS   RANGE   PLAY
4               50          299         249     Y

DELETE the 4 players, if the PLAY flag is set to 'Y'

delete from t
where id in ( 
  select id from fourplayers 
) and (
  ( select play from rangeandplay ) = 'Y'
) ;

commit ;

If we now run SELECT * FROM rangeandplay, we see ...

select * from rangeandplay ;

-- result
NUMBEROFPLAYERS MINPOINTS   MAXPOINTS   RANGE   PLAY
4               400         800         400     N

-> The DELETE would not execute now, as PLAY is set to 'N'. If another player eg with 450 points is INSERTed into the table, DELETE would remove the first 4 players again. See dbfiddle.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1