Search code examples
postgresqlgreatest-n-per-grouppostgresql-12

Get last logout for every user for every day (MAX(), GROUP BY) [postgresql]


I have multiple logouts for many users. I want to get the last logout for every user for every day.

SELECT t_logouts.log_id,
t_users.last_name || ' ' || t_users.first_name,
t_logouts.log_date,
MAX(t_logouts.log_time),
(DATE_PART('hour', '18:00:00'::time - t_logouts.log_time ) * 60 +
 DATE_PART('minute', '18:00:00'::time - t_logouts.log_time )) / 60
FROM t_logouts
LEFT JOIN t_users
ON t_users.user_id = t_logouts.user_id
GROUP BY DATE(t_logouts.log_date), t_users.user_id
ORDER BY t_logouts.log_date  DESC, t_logouts.log_time  ASC ;

Throws error:

LINE 1: SELECT t_logouts.log_id,
               ^
SQL state: 42803
Character: 8

There are some problem with multiple aggregation as far as I understand. I've tried to use this manual: https://www.postgresqltutorial.com/postgresql-group-by/ https://www.postgresqltutorial.com/postgresql-max-function/

Tables I am trying to work with.

CREATE TABLE t_users (
    user_id SERIAL PRIMARY KEY,
    nickname name UNIQUE not null check (length(nickname) < 64),
    first_name VARCHAR NOT NULL check (length(first_name) < 64),
    last_name VARCHAR NOT NULL check (length(last_name) < 64),
    email VARCHAR DEFAULT NULL check (email ~* '^.+@.+\..+$' )
);
CREATE TABLE t_logins (
    log_id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL,
        FOREIGN KEY (user_id)
        REFERENCES t_users (user_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    log_date DATE NOT NULL,
    log_time TIME NOT NULL
);
CREATE TABLE t_logouts (
    log_id SERIAL PRIMARY KEY,
    user_id INTEGER NOT NULL,
        FOREIGN KEY (user_id)
        REFERENCES t_users (user_id)
        ON UPDATE CASCADE ON DELETE RESTRICT,
    log_date DATE NOT NULL,
    log_time TIME NOT NULL
);

Solution

  • This is typically done using distinct on () in Postgres:

    SELECT distinct on (t_users.user_id) 
             t_logouts.log_id,
             t_users.last_name || ' ' || t_users.first_name,
             t_logouts.log_date,
             t_logouts.log_time,
             extract(epoch from '18:00:00'::time - t_logouts.log_time) / 3600
    FROM t_logouts
      LEFT JOIN t_users ON t_users.user_id = t_logouts.user_id
    ORDER BY t_users.user_id, t_logouts.log_date DESC;