ozat
ozat

Reputation: 110

Get closest date to given date for a group of records

Hi guys I'm having problems with a sql query, look, this is my scenario:

I have an students table and a table where I store dates when students enter or leave the school, so I want to get the nearest date to a given date for every student, I can't find the way to do this.

Students Data:

|idstudent|name  |
------------------
|    1    | John |
|    2    | Bob  |
------------------

Dates Data: 

|id|idstudent|   date   |type|
------------------------------
|1 |   1     |20-01-2015| 1  |
|2 |   2     |20-01-2015| 1  |
|3 |   2     |15-08-2015| 2  |
|4 |   1     |31-08-2015| 2  |
------------------------------

Desired Date = 01-08-2015 

|idstudent| name  | date       |type|
-------------------------------------
|    1    | John  | 31-08-2015 | 2  |
|    2    | Bob   | 15-08-2015 | 2  |

Students Table:

CREATE TABLE students
(
  idstudent serial NOT NULL,
  name character varying(200),
  CONSTRAINT idstudent PRIMARY KEY (idstudent)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE students
  OWNER TO postgres;

Dates Table:

CREATE TABLE students_dates
(
  idstudent_date serial NOT NULL,
  idstudent bigint,
  date_ date,
  type smallint,
  CONSTRAINT idstudent_date PRIMARY KEY (idstudent_date)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE students_dates
  OWNER TO postgres;

Can anyone help me?

Thank you so much.

Upvotes: 0

Views: 8969

Answers (4)

user330315
user330315

Reputation:

Using the proprietary distinct on () is usually faster in Postgres than using window functions.

Building on Gordon's idea with the abs():

select distinct on (s.idstudent) s.*, sd.date_, sd.type
from students s
  join students_dates sd on s.idstudent = sd.idstudent
order by s.idstudent, abs(sd.date_ - date '2015-09-26');

This can also be solved using a Window function:

select idstudent, name, date_, type
from (
  select s.idstudent, s.name, sd.date_, sd.type, 
         row_number() over (partition by s.idstudent order by sd.date_ - date '2015-09-26' desc) as rn
  from students s
    join students_dates sd on s.idstudent = sd.idstudent
) t
where rn = 1;

SQLFiddle: http://sqlfiddle.com/#!15/25fef/4

Upvotes: 3

sagi
sagi

Reputation: 40481

If i understood your request , this is the answer. It joins the students table with the students dates table after its RANKED and takes only the date nearest to your GIVEN_DATE.

SELECT s.* FROM students s
INNER JOIN
(SELECT date,type FROM (
 SELECT sd2.*,RANK() OVER(PARTITION BY sd2.idstudent ORDER BY abs(sd2.date - GIVEN_DATE) ASC) as sdrank
 FROM students_dates sd2
 ) where sdrank = 1) sd on sd.idstudent = s.idstudent

Upvotes: 1

Gordon Linoff
Gordon Linoff

Reputation: 1269593

This is a bit tricky. I think the best method is distinct on. You don't describe the data in your question, but this is the idea:

select distinct on (studentid) s.*
from students s
order by studentid, abs(studentdate - '2015-09-26');

Upvotes: 1

i486
i486

Reputation: 6564

Use DATEDIFF to get difference between dates, get ABS value. Then sort by ABS(DATEDIFF()) and get top record.

Upvotes: 1

Related Questions