Вы не описали, что представляет собой повторяющуюся строку (в вашем примере ничего не повторяется, потому что все строки уникальны благодаря id), но я предполагаю, что вы хотите, чтобы строки были разными во всех столбцах, кроме id, t заботитесь, какой id из нескольких возможных дубликатов это могло бы быть.
Давайте начнем с некоторых тестовых данных:
CREATE UNLOGGED TABLE profile_photos (id int, username text, profile_photo text);
Time: 417.014 ms
INSERT INTO profile_photos
SELECT g.id, r.username, 'urlphoto/' || r.username
FROM generate_series(1, 10000000) g (id)
CROSS JOIN substr(md5(g.id::text), 0, 8) r (username);
INSERT 0 10000000
Time: 24497.335 ms
Я проверить два возможных решения, и эти два индекса для каждого решения:
CREATE INDEX id_btree ON profile_photos USING btree (id);
CREATE INDEX
Time: 8139.347 ms
CREATE INDEX username_profile_photo_id_btree ON profile_photos USING btree (username, profile_photo, id DESC);
CREATE INDEX
Time: 81667.411 ms
VACUUM ANALYZE profile_photos;
VACUUM
Time: 1338.034 ms
Таким образом, первое решение, которое дает Сами и Клементом (их запросы по существу одинаковы):
SELECT min(id), username, profile_photo
FROM profile_photos
GROUP BY username, profile_photo
ORDER BY min(id) DESC
LIMIT 2;
min | username | profile_photo
----------+----------+------------------
10000000 | d1ca3aa | urlphoto/d1ca3aa
9999999 | 283f427 | urlphoto/283f427
(2 rows)
Time: 5088.611 ms
Результат выглядит правильно, но этот запрос может привести к нежелательным результатам, если кто-либо из этих пользователей разместил фотографию профиля раньше. Давайте подражем этому:
UPDATE profile_photos
SET (username, profile_photo) = ('d1ca3aa', 'urlphoto/d1ca3aa')
WHERE id = 1;
UPDATE 1
Time: 1.313 ms
SELECT min(id), username, profile_photo
FROM profile_photos
GROUP BY username, profile_photo
ORDER BY min(id) DESC
LIMIT 2;
min | username | profile_photo
---------+----------+------------------
9999999 | 283f427 | urlphoto/283f427
9999998 | facf1f3 | urlphoto/facf1f3
(2 rows)
Time: 5032.213 ms
Таким образом, запрос игнорирует все, что может добавить пользователь. Он не похож на то, что вы хотите, так что я предлагаю заменить мин (ID) с макс (ID):
SELECT max(id), username, profile_photo
FROM profile_photos
GROUP BY username, profile_photo
ORDER BY max(id) DESC
LIMIT 2;
max | username | profile_photo
----------+----------+------------------
10000000 | d1ca3aa | urlphoto/d1ca3aa
9999999 | 283f427 | urlphoto/283f427
(2 rows)
Time: 5068.507 ms
право, но это выглядит медленно. План запроса:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=655369.97..655369.98 rows=2 width=29) (actual time=6215.284..6215.285 rows=2 loops=1)
-> Sort (cost=655369.97..678809.36 rows=9375755 width=29) (actual time=6215.282..6215.282 rows=2 loops=1)
Sort Key: (max(id))
Sort Method: top-N heapsort Memory: 25kB
-> GroupAggregate (cost=0.56..561612.42 rows=9375755 width=29) (actual time=0.104..4945.534 rows=9816449 loops=1)
-> Index Only Scan using username_profile_photo_id_btree on profile_photos (cost=0.56..392855.43 rows=9999925 width=29) (actual time=0.089..1849.036 rows=10000000 loops=1)
Heap Fetches: 0
Total runtime: 6215.344 ms
(8 rows)
вещь заметить здесь, что нет никакого законного использования агрегата, что повлечет за собой GROUP BY: в GROUP BY в данном случае используется для фильтрации дубликатов и единственный агрегат здесь это обход, чтобы выбрать любой из них.Postgres имеет расширение, которое позволяет отказаться от дубликатов на множестве столбцов:
SELECT *
FROM (
SELECT DISTINCT ON (username, profile_photo) *
FROM profile_photos
) X
ORDER BY id DESC
LIMIT 2;
id | username | profile_photo
----------+----------+------------------
10000000 | d1ca3aa | urlphoto/d1ca3aa
9999999 | 283f427 | urlphoto/283f427
(2 rows)
Time: 3779.723 ms
Это немного быстрее, и вот почему:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=630370.16..630370.17 rows=2 width=29) (actual time=4921.031..4921.031 rows=2 loops=1)
-> Sort (cost=630370.16..653809.55 rows=9375755 width=29) (actual time=4921.030..4921.030 rows=2 loops=1)
Sort Key: profile_photos.id
Sort Method: top-N heapsort Memory: 25kB
-> Unique (cost=0.56..442855.06 rows=9375755 width=29) (actual time=0.114..4220.410 rows=9816449 loops=1)
-> Index Only Scan using username_profile_photo_id_btree on profile_photos (cost=0.56..392855.43 rows=9999925 width=29) (actual time=0.111..2040.601 rows=10000000 loops=1)
Heap Fetches: 0
Total runtime: 4921.081 ms
(8 rows)
Что если бы мы могли каким-то образом извлечь последнюю строку с простым ORDER BY id DESC LIMIT 1, и найдите еще одну строку из конца таблицы, которая не будет дублировать?
WITH first AS (
SELECT *
FROM profile_photos
ORDER BY id DESC
LIMIT 1
)
SELECT *
FROM first
UNION ALL
(SELECT *
FROM profile_photos p
WHERE EXISTS (
SELECT 1
FROM first
WHERE (first.username, first.profile_photo) <> (p.username, p.profile_photo))
ORDER BY id DESC
LIMIT 1);
id | username | profile_photo
----------+----------+------------------
10000000 | d1ca3aa | urlphoto/d1ca3aa
9999999 | 283f427 | urlphoto/283f427
(2 rows)
Time: 1.217 ms
Это очень быстрый, но ручной дизайн, чтобы получить только два ряда. Давайте заменим его чем-то более «автоматическим»:
WITH RECURSIVE last (id, username, profile_photo, a) AS (
(SELECT id, username, profile_photo, ARRAY[ROW(username, profile_photo)] a
FROM profile_photos
ORDER BY id DESC
LIMIT 1)
UNION ALL
(SELECT older.id, older.username, older.profile_photo, last.a || ROW(older.username, older.profile_photo)
FROM last
JOIN profile_photos older ON last.id > older.id AND NOT ROW(older.username, older.profile_photo) = ANY(last.a)
WHERE array_length(a, 1) < 10
ORDER BY id DESC
LIMIT 1)
)
SELECT id, username, profile_photo
FROM last;
id | username | profile_photo
----------+----------+------------------
10000000 | d1ca3aa | urlphoto/d1ca3aa
9999999 | 283f427 | urlphoto/283f427
9999998 | facf1f3 | urlphoto/facf1f3
9999997 | 305ebab | urlphoto/305ebab
9999996 | 74ab43a | urlphoto/74ab43a
9999995 | 23f2458 | urlphoto/23f2458
9999994 | 6b465af | urlphoto/6b465af
9999993 | 33ee85a | urlphoto/33ee85a
9999992 | c0b9ef4 | urlphoto/c0b9ef4
9999991 | b63d5bf | urlphoto/b63d5bf
(10 rows)
Time: 2706.837 ms
Это быстрее, чем предыдущие запросы, но, как вы можете видеть в плане запроса ниже, для каждой дали грести он должен сканировать индекс по идентификатору.
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CTE Scan on last (cost=6.52..6.74 rows=11 width=68) (actual time=0.104..4439.807 rows=10 loops=1)
CTE last
-> Recursive Union (cost=0.43..6.52 rows=11 width=61) (actual time=0.098..4439.780 rows=10 loops=1)
-> Limit (cost=0.43..0.47 rows=1 width=29) (actual time=0.095..0.095 rows=1 loops=1)
-> Index Scan Backward using id_btree on profile_photos (cost=0.43..333219.47 rows=9999869 width=29) (actual time=0.093..0.093 rows=1 loops=1)
-> Limit (cost=0.43..0.58 rows=1 width=61) (actual time=443.965..443.966 rows=1 loops=10)
-> Nested Loop (cost=0.43..1406983.38 rows=9510977 width=61) (actual time=443.964..443.964 rows=1 loops=10)
Join Filter: ((last_1.id > older.id) AND (ROW(older.username, older.profile_photo) <> ALL (last_1.a)))
Rows Removed by Join Filter: 8
-> Index Scan Backward using id_btree on profile_photos older (cost=0.43..333219.47 rows=9999869 width=29) (actual time=0.008..167.755 rows=1000010 loops=10)
-> WorkTable Scan on last last_1 (cost=0.00..0.25 rows=3 width=36) (actual time=0.000..0.000 rows=0 loops=10000102)
Filter: (array_length(a, 1) < 10)
Rows Removed by Filter: 1
Total runtime: 4439.907 ms
(14 rows)
С Postgres 9.3 имеется новый тип JOIN, доступный по времени. Это позволяет вам принять решение о соединении на уровне строки (т. Е. Работает «для каждой строки»). Мы можем использовать это для реализации следующей логики: «до тех пор, пока у нас нет N строк, для каждой из сгенерированных строк посмотрите, есть ли более старая строка, чем последняя, и если есть, добавьте эту строку в сгенерированный результат ».
WITH RECURSIVE last (id, username, profile_photo, a) AS (
(SELECT id, username, profile_photo, ARRAY[ROW(username, profile_photo)] a
FROM profile_photos
ORDER BY id DESC
LIMIT 1)
UNION ALL
(SELECT older.id, older.username, older.profile_photo, last.a || ROW(older.username, older.profile_photo)
FROM last
CROSS JOIN LATERAL (
SELECT *
FROM profile_photos older
WHERE last.id > older.id AND NOT ROW(older.username, older.profile_photo) = ANY(last.a)
ORDER BY id DESC
LIMIT 1
) older
WHERE array_length(a, 1) < 10
ORDER BY id DESC
LIMIT 1)
)
SELECT id, username, profile_photo
FROM last;
id | username | profile_photo
----------+----------+------------------
10000000 | d1ca3aa | urlphoto/d1ca3aa
9999999 | 283f427 | urlphoto/283f427
9999998 | facf1f3 | urlphoto/facf1f3
9999997 | 305ebab | urlphoto/305ebab
9999996 | 74ab43a | urlphoto/74ab43a
9999995 | 23f2458 | urlphoto/23f2458
9999994 | 6b465af | urlphoto/6b465af
9999993 | 33ee85a | urlphoto/33ee85a
9999992 | c0b9ef4 | urlphoto/c0b9ef4
9999991 | b63d5bf | urlphoto/b63d5bf
(10 rows)
Time: 1.966 ms
Теперь это быстро ... до тех пор, пока N не станет слишком большим.
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CTE Scan on last (cost=18.61..18.83 rows=11 width=68) (actual time=0.074..0.359 rows=10 loops=1)
CTE last
-> Recursive Union (cost=0.43..18.61 rows=11 width=61) (actual time=0.070..0.346 rows=10 loops=1)
-> Limit (cost=0.43..0.47 rows=1 width=29) (actual time=0.067..0.068 rows=1 loops=1)
-> Index Scan Backward using id_btree on profile_photos (cost=0.43..333219.47 rows=9999869 width=29) (actual time=0.065..0.065 rows=1 loops=1)
-> Limit (cost=1.79..1.79 rows=1 width=61) (actual time=0.026..0.026 rows=1 loops=10)
-> Sort (cost=1.79..1.80 rows=3 width=61) (actual time=0.025..0.025 rows=1 loops=10)
Sort Key: older.id
Sort Method: quicksort Memory: 25kB
-> Nested Loop (cost=0.43..1.77 rows=3 width=61) (actual time=0.020..0.021 rows=1 loops=10)
-> WorkTable Scan on last last_1 (cost=0.00..0.25 rows=3 width=36) (actual time=0.001..0.001 rows=1 loops=10)
Filter: (array_length(a, 1) < 10)
Rows Removed by Filter: 0
-> Limit (cost=0.43..0.49 rows=1 width=29) (actual time=0.017..0.017 rows=1 loops=9)
-> Index Scan Backward using id_btree on profile_photos older (cost=0.43..161076.14 rows=3170326 width=29) (actual time=0.016..0.016 rows=1 loops=9)
Index Cond: (last_1.id > id)
Filter: (ROW(username, profile_photo) <> ALL (last_1.a))
Rows Removed by Filter: 0
Total runtime: 0.439 ms
(19 rows)