Postgres, table1 покинул присоединиться к table2 с только 1 строкой на ID в table1 - PullRequest
5 голосов
/ 22 июля 2010

Хорошо, так что название немного запутанное. По сути, это проблема типа «наибольшие числа групп», но я не могу понять, насколько я понимаю.

У меня есть таблица user_stats:

------------------+---------+---------------------------------------------------------
 id               | bigint  | not null default nextval('user_stats_id_seq'::regclass)
 user_id          | bigint  | not null
 datestamp        | integer | not null
 post_count       | integer | 
 friends_count    | integer | 
 favourites_count | integer |  
Indexes:
    "user_stats_pk" PRIMARY KEY, btree (id)
    "user_stats_datestamp_index" btree (datestamp)
    "user_stats_user_id_index" btree (user_id)
Foreign-key constraints:
    "user_user_stats_fk" FOREIGN KEY (user_id) REFERENCES user_info(id)

Я хочу получить статистику для каждого идентификатора по последней дате. Это большая таблица, где-то около 41 м строк, поэтому я создал временную таблицу user_id, last_date, используя:

CREATE TEMP TABLE id_max_date AS
    (SELECT user_id, MAX(datestamp) AS date FROM user_stats GROUP BY user_id);

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

SELECT user_stats.user_id, user_stats.datestamp, user_stats.post_count,
       user_stats.friends_count, user_stats.favorites_count
  FROM id_max_date JOIN user_stats
    ON id_max_date.user_id=user_stats.user_id AND date=datestamp;

Если бы я делал это как подвыборы, я бы мог ограничиться 1, но я всегда слышал, что это ужасно неэффективно. Мысли?

Ответы [ 3 ]

24 голосов
/ 22 июля 2010

ОТЛИЧАЕТСЯ НА ваш друг.

select distinct on (user_id) * from user_stats order by datestamp desc;
3 голосов
/ 22 июля 2010

По сути, вам нужно решить, как разрешить связи, и вам нужен какой-то другой столбец, кроме datestamp, который гарантированно будет уникальным (по крайней мере, для данного пользователя), чтобы его можно было использовать в качестве прерывателя связей. Если ничего другого, вы можете использовать столбец первичного ключа id.

Еще одним решением, если вы используете PostgreSQL 8.4, являются функции управления окнами:

WITH numbered_user_stats AS (
    SELECT *, ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY datestamp DESC) AS RowNum
    FROM user_stats) AS numbered_user_stats
) SELECT u.user_id, u.datestamp, u.post_count, u.friends_count, u.favorites_count
FROM numbered_user_stats AS u
WHERE u.RowNum = 1;
0 голосов
/ 22 июля 2010

Используя существующую инфраструктуру, вы можете использовать:

SELECT u.user_id, u.datestamp,
       MAX(u.post_count)      AS post_count,
       MAX(u.friends_count)   AS friends_count,
       MAX(u.favorites_count) AS favorites_count
  FROM id_max_date AS m JOIN user_stats AS u
    ON m.user_id = u.user_id AND m.date = u.datestamp
 GROUP BY u.user_id, u.datestamp;

Это дает вам одно значение для каждого из столбцов «не обязательно уникальных».Однако это не является абсолютно гарантией того, что все три максимума появились в одном ряду (хотя есть хотя бы умеренный шанс того, что они это сделают - и что все они будут взяты из последней записи, созданной в данный день).

Для этого запроса индекс только по метке даты не поможет;индекс по идентификатору пользователя и отметке даты может значительно ускорить этот запрос - или, возможно, более точно, он может ускорить запрос, который генерирует таблицу id_max_date.

Очевидно, вы также можете написать выражение id_max_date какподзапрос в предложении FROM:

SELECT u.user_id, u.datestamp,
       MAX(u.post_count)      AS post_count,
       MAX(u.friends_count)   AS friends_count,
       MAX(u.favorites_count) AS favorites_count
  FROM (SELECT u2.user_id, MAX(u2.datestamp) AS date
          FROM user_stats AS u2
         GROUP BY u2.user_id) AS m
  JOIN user_stats AS u ON m.user_id = u.user_id AND m.date = u.datestamp
 GROUP BY u.user_id, u.datestamp;
...