2009-09-23 12 views
5

Mam tabelę, w której chciałbym przedstawić dane "uszeregowane X z Y" dla. W szczególności chciałbym móc przedstawić te dane dla pojedynczego wiersza w stosunkowo wydajny sposób (tj. Bez wybierania każdego wiersza w tabeli). Sam ranking jest dość prosty, to proste ORDER BY na jednej kolumnie w tabeli.Jak powinienem obsłużyć dane "rankingu x z Y" w PostgreSQL?

Postgres wydaje się przedstawiać wyjątkowe wyzwania w tym zakresie; AFAICT nie ma RANKU lub ROW_NUMBERa lub równoważnej funkcji (przynajmniej w 8.3, na którym utknąłem na chwilę). Odpowiedź kanoniczna w archiwach listy dyskusyjnej wydaje się być stworzenie sekwencji czasowej i wybierz z niej:

test=> create temporary sequence tmp_seq; 
CREATE SEQUENCE 
test=*> select nextval('tmp_seq') as row_number, col1, col2 from foo; 

Wydaje się, że to rozwiązanie wciąż nie pomoże, gdy chcę wybrać tylko jeden wiersz z tabeli (i chcę wybrać go przez PK, a nie według rangi).

Mogę denormalizować i przechowywać rangę w osobnej kolumnie, co sprawia, że ​​prezentowanie danych jest banalne, ale po prostu przesuwa mój problem. AKTUALIZACJA nie obsługuje ORDER BY, więc nie jestem pewien, w jaki sposób utworzyć zapytanie UPDATE, aby ustawić rangi (nie trzeba wybierać każdego wiersza i uruchamiać oddzielnej aktualizacji dla każdego wiersza, co wydaje się zbyt dużą uruchamiać za każdym razem, gdy rangi wymagają aktualizacji).

Czy brakuje mi czegoś oczywistego? W jaki sposób to zrobić?

EDIT: Najwyraźniej nie byłem wystarczająco jasny. Jestem świadomy OFFSET/LIMIT, ale nie widzę, jak to pomaga rozwiązać ten problem. Nie próbuję wybrać pozycji w rankingu X, próbuję wybrać dowolną pozycję (według jej PK, powiedzmy), a następnie być w stanie wyświetlić użytkownikowi coś w rodzaju "pozycja 43 na 312."

Odpowiedz

6

Jeśli chcesz rangę, coś jak

SELECT id,num,rank FROM (
    SELECT id,num,rank() OVER (ORDER BY num) FROM foo 
) AS bar WHERE id=4 

Albo jeśli rzeczywiście chcesz numer wiersza, użyj

SELECT id,num,row_number FROM (
    SELECT id,num,row_number() OVER (ORDER BY num) FROM foo 
) AS bar WHERE id=4 

Będą się różnić, gdy masz gdzieś takie same wartości.Istnieje również dense_rank(), jeśli tego potrzebujesz.

Wymaga to oczywiście PostgreSQL 8.4.

+0

Ta składnia jest pewnie o wiele ładniejsza. Może będę musiał rozważyć, co trzeba zrobić, aby uaktualnić. –

+0

Na razie nigdy nie spotkałem się z problemem związanym z aktualizacją z wcześniej zaprojektowaną bazą danych, a korzyści są liczne. Jeden z moich klientów odkrył jednak, że dzięki nowej metodzie 'HashAggregate'' DISTINCT' niekoniecznie sortuje, co zepsuło niektóre z jego zapytań. To on winien, oczywiście, ale upewnij się, że twoje pytania nie polegają na tych trikach. – Quassnoi

+0

Pierwotne pytanie określone 8.3, ale zdecydowałem, że warto było uaktualnić do 8,4, aby uzyskać dostęp do tych funkcji. Działa świetnie, dzięki za odpowiedź! –

3

ROW_NUMBER funkcjonalność w PostgreSQL jest realizowana za pośrednictwem LIMIT n OFFSET skip.

EDYCJA: Ponieważ pytasz o ROW_NUMBER() zamiast prostego rankingu: row_number() jest wprowadzany do PostgreSQL w wersji 8.4. Możesz rozważyć aktualizację. W przeciwnym razie pomocny może być this workaround.

+0

Jestem świadomy podwójnych problemów i środków ostrożności. To wcale nie odpowiada na moje pytanie.Używanie LIMIT i OFFSET jest łatwe, ale nie daje mi numeru rankingowego do wyświetlenia na stronie ("pozycja ta jest na 43 miejscu spośród 312"), co jest tym punktem. –

4

Czy to nie jest właśnie to:

SELECT * 
FROM mytable 
ORDER BY 
     col1 
OFFSET X LIMIT 1 

Albo ja czegoś brakuje?

Aktualizacja:

Jeśli chcesz pokazać rangę, użyj tego:

SELECT mi.*, values[1] AS rank, values[2] AS total 
FROM (
     SELECT (
       SELECT ARRAY[SUM(((mi.col1, mi.ctid) < (mo.col1, mo.ctid))::INTEGER), COUNT(*)] 
       FROM mytable mi 
       ) AS values 
     FROM mytable mo 
     WHERE mo.id = @myid 
     ) q 
+0

Pobity o 1 sek. Niemniej jednak masz rację. +1 –

+0

Wygląda na to, że jestem pokonany: twoja odpowiedź "id" jest mniejsza o '1' :) – Quassnoi

+0

Patrz komentarz powyżej; Nie sądzę, że zrozumiałeś moje pytanie. PRZESUNIĘCIE/LIMIT jest świetny, jeśli chcę wybrać, powiedzmy, pozycję w 12. rankingu. Ale ja nie. Chcę wybrać, powiedzmy, przedmiot o numerze 37 i wyświetlić "ten przedmiot w rankingu 43 na 312" w witrynie. Nie widzę, jak pomaga OFFSET/LIMIT. –

1

Poprzednie rozwiązania odpowiedzi na pytanie „zaznaczyć wszystkie wiersze i uzyskać ich rangę”, który nie jest to, co chcesz ...

  • masz wiersz
  • chcesz poznać jego ranga

Wystarczy zrobić:

SELECT count (*) FROM tabela WHERE wynik> $ 1

Gdzie $ 1 to wynik właśnie wybranego rzędu (przypuszczam, że chcesz go wyświetlić, abyś mógł go wybrać ...).

Albo zrobić:

wybrać. , (SELECT count () z tabeli B, jeżeli wynik> b.score) W rankingu FROM tabela WHERE jako pk = ...

Jeśli jednak zaznaczyć wiersz, który jest ostatnim miejscu, tak trzeba będzie aby zliczyć wszystkie wiersze, które są w rankingu przed nim, więc musisz przeskanować całą tabelę i będzie bardzo wolno.

Rozwiązanie:

SELECT count (*) FROM (SELECT 1 FROM tabela WHERE wynik> $ 1 LIMIT 30)

Będziesz uzyskać dokładną pozycję dla 30 najlepszych wyników, a to będzie szybko. Kogo obchodzi przegrany?

OK, Jeśli naprawdę dbają o przegranych, trzeba wykonać histogram:

Załóżmy wynik może przejść od 0 do 100, a masz 1000000 przegranych z wynikiem < 80 i 10 zwycięzców z wynik> 80.

Tworzysz histogram, ile wierszy ma wynik X, to prosty mały stolik z 100 rzędami. Dodaj wyzwalacz do głównej tabeli, aby zaktualizować histogram.

Teraz jeśli chcesz rangi przegrany, który ma zdobyć X, jego ranga jest suma (histo) gdzie histo_score> X.

Ponieważ wynik chyba nie jest między 0 a 100, ale (powiedzmy) pomiędzy 0 i 1000000000, będziesz musiał trochę krówki, na przykład powiększyć pojemniki na histogram. więc potrzebujesz tylko 100 bins max, lub użyj funkcji log-histogram dystrybucji.

przy okazji PostgreSQL robi to kiedy przeanalizować tabelę, więc jeśli ustawisz statistics_target do 100 lub 1000 na wynik analizy, a następnie uruchomić:

EXPLAIN SELECT * FROM tabela WHERE wynik> $ 1

otrzymasz kosztorys szacunkowy.

Kto potrzebuje dokładnych odpowiedzi?

Powiązane problemy