Определение первой активности за день для пользователя - PullRequest
2 голосов
/ 11 марта 2020

У меня есть таблица "UserData" со следующей информацией:

User    Date    DateTime        Input
1   8/4/2019    8/4/2019 0:55   Request
1   8/4/2019    8/4/2019 0:56   Ticket
1   8/4/2019    8/4/2019 2:08   Submit
1   8/4/2019    8/4/2019 2:21   Submit
2   8/4/2019    8/4/2019 13:10  Submit
2   8/20/2019   8/20/2019 2:10  Ticket
2   8/20/2019   8/20/2019 2:12  Ticket
2   8/20/2019   8/20/2019 2:13  Request
3   8/20/2019   8/20/2019 2:15  Request
3   8/19/2019   8/19/2019 2:16  Ticket
3   6/12/2020   6/12/2020 2:22  Submit
3   6/12/2020   6/12/2020 2:26  Submit
3   6/12/2020   6/12/2020 3:26  Ticket

Я хочу вернуть первый ввод от каждого пользователя (на основе времени), но только в самый новый день. Таким образом, мой вывод будет примерно таким:

User    DateTime    Input
1   8/4/2019 0:55   Request
2   8/20/2019 2:10  Ticket
3   6/12/2020 2:22  Submit

Я думаю, что мне нужно присвоить ранг каждой дате, но я не совсем уверен, с чего начать.

Ответы [ 4 ]

2 голосов
/ 11 марта 2020

Я понимаю, что вы хотите самую раннюю запись для пользователя и в день. В Postgres вы можете просто использовать distinct on для решения этой проблемы топ-1 на группу:

select distinct on (u.user, u.date) u.*
from userData u
order by u.user, u.date, u.datetime

Если вам нужна самая ранняя запись для пользователя, независимо от дня, то просто:

select distinct on (u.user) u.*
from userData u
order by u.user, u.datetime

Редактировать: если вы хотите самую раннюю запись в последний день, то:

select distinct on (u.user) u.*
from userData u
order by u.user, u.date desc, u.datetime
0 голосов
/ 11 марта 2020

Я понимаю, что вы хотите:

строка с самым ранним временем последнего дня для каждого пользователя

Дизайн таблицы

Для начала: опустите столбец Date. Хранение этого избыточно добавляет больше затрат и сложностей, чем стоит:

CREATE TABLE userdata (
  user_id  int
, datetime timestamp
, input    text
);

input действительно должна быть дешевой реализацией перечисления (enum, FK, ...).
timestamptz может быть подходящим типом для datetime. Зависит. См .:

Индекс

В любом случае, чтобы сделать вашу операцию быстро, это идеальный показатель:

CREATE INDEX userdata_special_idx ON userdata
(user_id, (datetime::date) DESC NULLS LAST, datetime);

datetime::date - это очень дешевое приведение, заменяющее ваш лишний столбец дат. Я по-прежнему добавляю дату в индекс выражений из нескольких столбцов для повышения производительности. ( дата зависит от часового пояса при работе с timestamptz. Если вы работаете с несколькими часовыми поясами, вам нужно сделать больше.)

Обратите внимание на добавленный NULLS LAST: так как ничего в вашем вопросе говорится, что отметка времени равна NOT NULL, это необходимо в запросе, чтобы предотвратить бессмысленные результаты - и индекс должен совпадать для достижения наилучших результатов. См .:

Запрос

Только для несколько строк на пользователя , DISTINCT ON должен быть лучшим выбором (как уже предлагал GMB) - просто и быстро:

SELECT DISTINCT ON (user_id)
       user_id, datetime, input 
FROM   userdata
ORDER  BY user_id, datetime::date DESC NULLS LAST, datetime;

См .:

Для много строк на пользователя , этот альтернативный запрос должно быть (существенно) быстрее:

SELECT u.user_id, d.*
FROM   users u
LEFT   JOIN LATERAL (
   SELECT d.datetime, d.input 
   FROM   userdata d
   WHERE  d.user_id = u.user_id         -- lateral reference
   ORDER  BY d.datetime::date DESC NULLS LAST, d.datetime
   LIMIT  1
   ) d ON true;

Обычно - это путь к go для вашего сценария.

Обратите внимание на LEFT JOIN: возвращается строка для каждого пользователя, даже без записей в userdata. Если это нежелательно, используйте CROSS JOIN. Похожие:

Это предполагает существование таблицы users , который обычно существует. Если этого не произойдет, я предлагаю вам добавить его (по нескольким причинам). Если это не вариант, все еще есть быстрые обходные пути. См .:

db <> fiddle здесь

В сторону: я настоятельно рекомендую всегда использовать формат даты ISO (, как и руководство ). Региональный формат зависит от настроек текущего сеанса и может привести к ошибкам.

0 голосов
/ 11 марта 2020
SELECT * 
FROM ( User,
       Date,
       Datetime, 
       Input,
       ROW_NUMBER() OVER (
           PARTITION BY User 
           ORDER BY Datetime DESC) dataOrder
       FROM UserData) z
WHERE z.dataOrder = 1
0 голосов
/ 11 марта 2020

С row_number() оконной функцией:

select t."User", t."Date", t."DateTime", t."Input"
from (
  select *, row_number() over (partition by "User" order by "Date" desc, "DateTime") rn
  from UserData
) t
where t.rn = 1

Для каждого пользователя строка сортируется по "Date" по убыванию , чтобы найти самую последнюю дату, а затем по "DateTime" * 1009. * по возрастанию , чтобы получить 1-й вход этого дня. Смотрите демо . Результаты:

| User | Date       | DateTime         | Input   |
| ---- | ---------- | ---------------- | ------- |
| 1    | 2019-08-04 | 2019-08-04 00:55 | Request |
| 2    | 2019-08-20 | 2019-08-20 02:10 | Ticket  |
| 3    | 2020-06-12 | 2020-06-12 02:22 | Submit  |
...