2015-05-10 6 views
0

Есть ли способ, которым я могу это сделать?PostgreSQL/Python - Получить последние N строк без повторения


Например. Если моя таблица содержит следующие элементы:

id | username | profile_photo 
---+----------+-------------- 
1 |  juan | urlphoto/juan 
2 | nestor | urlphoto/nestor 
3 | pablo | urlphoto/pablo 
4 | pablo | urlphoto/pablo 

И я хочу получить последние 2 (два) строки должны получить:

id 2 -> nestor | urlphoto/nestor 
id 3 -> pablo | urlphoto/pablo 

Спасибо за ваше время.

РЕШЕНИЕ:

Решение вставить элемент, если не уже в первых п элементов

import psycopg2, psycopg2.extras, json 
db = psycopg2.connect("") 

cursor = db.cursor(cursor_factory=psycopg2.extras.RealDictCursor) 
cursor.execute("SELECT * FROM users ORDER BY id DESC LIMIT n;") 
row = [item['user_id'] for item in cursor.fetchall()] 

if not user_id in row: 
    cursor.execute("INSERT..") 
    db.commit() 
cursor.close() 
db.close() 

ответ

0

Как насчет

SELECT id, username, profile_photo 
FROM (select min(id), username, profile_photo FROM table 
     GROUP BY username, profile_photo) tmp ORDER BY id DESC LIMIT 2 
0

Если вы не заботитесь о финале строка порядок, здесь вы идете

SELECT min(id), username, profile_photo 
FROM oh_my_table 
GROUP BY username, profile_photo 
ORDER BY min(id) DESC 
LIMIT 2 
0

Вы не описали, что представляет собой повторяющуюся строку (в вашем примере ничего не повторяется, потому что все строки уникальны благодаря 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) 
Смежные вопросы