2010-09-08 10 views
16

Chcę wybrać niektóre wiersze na podstawie pewnych kryteriów, a następnie pobrać jeden wpis z tego zestawu i 5 wierszy przed nim i po nim.Jak wybrać sąsiadujące wiersze do dowolnego rzędu (w sql lub postgresql)?

Teraz mogę to zrobić numerycznie, jeśli istnieje klucz podstawowy na stole (np. Klucze podstawowe, które są numerycznie o 5 mniejsze niż klucz rzędu docelowego i o 5 więcej niż klucz rzędu docelowego).

Więc wybierz wiersz z klucza podstawowego z dnia 7 i pobliskich rzędów:

select primary_key from table where primary_key > (7-5) order by primary_key limit 11; 

2 
3 
4 
5 
6 
-=7=- 
8 
9 
10 
11 
12 

Ale jeśli mogę wybrać tylko niektóre wiersze na początku, tracę że metoda numeryczna użyciu kluczy podstawowych (i to było zakładając, że klucze nie mają żadnych przerw w ich zamówieniu) i potrzebują innego sposobu na uzyskanie najbliższych wierszy przed i po pewnym ukierunkowanym wierszu.

Kluczem wyjście podstawowy taki select może wyglądać bardziej przypadkowy, a tym samym mniej succeptable do matematycznego lokalizowania (ponieważ niektóre wyniki będą filtrowane, na zewnątrz, na przykład z where active=1):

select primary_key from table where primary_key > (34-5) 
    order by primary_key where active=1 limit 11; 

30 
-=34=- 
80 
83 
100 
113 
125 
126 
127 
128 
129 

zauważyć, jak ze względu na luki w kluczach podstawowych spowodowane przez przykład, w którym warunek (na przykład becaseu jest wiele nieaktywnych pozycji), już nie jestem najbliższy 5 powyżej i 5 poniżej, zamiast tego otrzymuję najbliższy 1 poniżej i najbliżej 9 powyżej.

+0

Myślę, że napisana instrukcja SELECT działałaby w obu przykładach. – LatinSuD

+1

Błąd, instrukcja select zwróci wartości, tak, ale wiersze, które powróciły, będą zasadniczo losowe, w przeciwieństwie do 5 powyżej i 5 poniżej, jest problem. – Kzqai

Odpowiedz

19

Istnieje wiele sposobów, aby to zrobić, jeśli uruchomić dwie kwerendy z językiem programowania, ale tutaj jest jednym ze sposobów, aby to zrobić w jednym zapytaniu SQL:

(SELECT * FROM table WHERE id >= 34 AND active = 1 ORDER BY id ASC LIMIT 6) 
UNION 
(SELECT * FROM table WHERE id < 34 AND active = 1 ORDER BY id DESC LIMIT 5) 
ORDER BY id ASC 

byłby to powrót z 5 rzędów wyżej, docelowy wiersz i 5 wierszy poniżej.

+0

Prosty i skuteczny, działa w tak wielu sytuacjach, to jest to, czego użyłem. – Kzqai

0

Można to zrobić wykorzystując row_number() (dostępny od 8.4). Nie może to być właściwa składni (nie zna postgresql), lecz z nadzieją idei zostanie zilustrowany:

SELECT * 
FROM (SELECT ROW_NUMBER() OVER (ORDER BY primary_key) AS r, * 
     FROM table 
     WHERE active=1) t 
WHERE 25 < r and r < 35 

ten generuje pierwszą kolumnę o numery sekwencyjne. Możesz użyć tego do identyfikacji pojedynczego wiersza i wierszy nad i pod nim.

0

Jeśli chcesz zrobić to w sposób "relatywnie czysty", możesz napisać zapytanie, które sortuje i numeruje wiersze. Podobnie jak:

select (
    select count(*) from employees b 
    where b.name < a.name 
) as idx, name 
from employees a 
order by name 

Następnie użyj tego jako wspólnego wyrażenia tabeli. Napisz zaznaczenie, które odfiltrowuje je do wierszy, które Cię interesują, a następnie dołącz do niego z powrotem za pomocą kryterium, że indeks prawej kopii tabeli jest nie większy niż k większy lub mniejszy niż indeks rząd po lewej. Rzuć po prostu rzędy po prawej stronie. Na przykład:

with numbered_emps as (
    select (
    select count(*) 
    from employees b 
    where b.name < a.name 
) as idx, name 
    from employees a 
    order by name 
) 
select b.* 
from numbered_emps a, numbered_emps b 
where a.name like '% Smith' -- this is your main selection criterion 
and ((b.idx - a.idx) between -5 and 5) -- this is your adjacency fuzzy-join criterion 

Co może być prostsze!

Sądzę, że rozwiązania oparte na liczbie rzędów będą szybsze.

6

Oto kolejny sposób, aby to zrobić za pomocą funkcji analitycznych ołowiu i opóźnienia. Byłoby miło, gdybyśmy mogli użyć funkcji analitycznych w klauzuli WHERE. Zamiast tego należy użyć podkwerend lub CTE. Oto przykład, który będzie działał z przykładową bazą danych pagila.

WITH base AS (
    SELECT lag(customer_id, 5) OVER (ORDER BY customer_id) lag, 
     lead(customer_id, 5) OVER (ORDER BY customer_id) lead, 
     c.* 
    FROM customer c 
    WHERE c.active = 1 
    AND c.last_name LIKE 'B%' 
) 
SELECT base.* FROM base 
JOIN (
    -- Select the center row, coalesce so it still works if there aren't 
    -- 5 rows in front or behind 
    SELECT COALESCE(lag, 0) AS lag, COALESCE(lead, 99999) AS lead 
    FROM base WHERE customer_id = 280 
) sub ON base.customer_id BETWEEN sub.lag AND sub.lead 

Problem roztworem sgriffinusa jest to, że nie wiem, która row_number Twoje centrum rząd zakończy się mecz. Zakładał, że będzie to wiersz 30.

1

Dla podobnych zapytań używam funkcji analitycznych bez CTE. Coś jak:

select ..., LEAD(gm.id) OVER (ORDER BY Cit DESC) as leadId, LEAD(gm.id, 2) OVER (ORDER BY Cit DESC) as leadId2, LAG(gm.id) OVER (ORDER BY Cit DESC) as lagId, LAG(gm.id, 2) OVER (ORDER BY Cit DESC) as lagId2 ... where id = 25912 or leadId = 25912 or leadId2 = 25912 or lagId = 25912 or lagId2 = 25912

taka kwerenda działa szybciej dla mnie więcej niż CTE z przyłączyć (odpowiedź od Scott Bailey). Ale oczywiście mniej elegancki

+0

... poza tym, że nie możesz użyć wartości analitycznych w klauzuli "WHERE", więc to, co napisałeś, nie zadziała (przynajmniej nie w MS SQL). – feetwet