2010-09-08 8 views
16

Desidero selezionare alcune righe in base a determinati criteri e quindi prendere una voce da quella serie e le 5 righe prima e dopo di essa.Come posso selezionare righe adiacenti a una riga arbitraria (in sql o postgresql)?

Ora, posso farlo numericamente se c'è una chiave primaria sul tavolo, (ad esempio chiavi primarie che sono numericamente 5 in meno della chiave della riga di destinazione e 5 in più della chiave della riga di destinazione).

Quindi selezionare la riga con la chiave primaria della 7 e le righe nelle vicinanze:

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 

Ma se seleziono solo alcune righe per cominciare, perdo quel metodo numerico di utilizzo di chiavi primarie (e che è stato supponendo che le chiavi non avessero comunque lacune nel loro ordine), e hanno bisogno di un altro modo per ottenere le file più vicine prima e dopo una determinata riga mirata.

L'uscita chiave primaria di tale select potrebbe apparire più casuale e quindi meno succeptable di localizzazione matematica (dal momento che alcuni risultati sarebbero filtrati, fuori, ad esempio con un 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 

nota come causa le lacune nelle chiavi primarie causate dall'esempio dove condizione (ad esempio perché ci sono molti elementi inattivi), non sto ottenendo più 5 sopra e 5 sotto, invece sto ottenendo il più vicino 1 sotto e il il 9 più vicino sopra, invece.

+0

Penso che l'istruzione SELECT che hai scritto avrebbe funzionato in entrambi gli esempi. – LatinSuD

+1

Err, l'istruzione select restituirebbe valori, sì, ma le righe che restituiva sarebbero essenzialmente casuali, al contrario di 5 sopra e 5 sotto, è il problema. – Kzqai

risposta

19

C'è un sacco di modi per farlo se si esegue due query con un linguaggio di programmazione, ma qui è un modo per farlo in una query 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 

Questo sarebbe restituire i 5 righe sopra, la riga di destinazione e 5 righe sotto.

+0

Semplice ed efficace, e funziona per così tante situazioni, questo è quello che ho usato. – Kzqai

0

È possibile farlo utilizzando row_number() (disponibile a partire da 8.4). Questo non può essere la sintassi corretta (non hanno familiarità con PostgreSQL), ma si spera che l'idea sarà illustrata:

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

Questo genererà una prima colonna con numeri sequenziali. Puoi usare questo per identificare la singola riga e le righe sopra e sotto di essa.

0

Se si desidera eseguirlo in modo "relazionalmente puro", è possibile scrivere una query che ha ordinato e numerato le righe. Ad esempio:

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

Quindi utilizzarlo come un'espressione di tabella comune. Scrivi una selezione che la filtra nelle righe a cui sei interessato, quindi aggiungila nuovamente a se stessa usando un criterio che l'indice della copia della tabella di destra non è più di k più grande o più piccolo dell'indice del fila a sinistra. Proietta solo le righe a destra. Come:

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 

Cosa potrebbe essere più semplice!

Immagino che le soluzioni basate su numero di riga siano più veloci.

6

Ecco un altro modo per farlo con le funzioni analitiche lead e lag. Sarebbe bello se potessimo usare le funzioni analitiche nella clausola WHERE. Quindi, invece, è necessario utilizzare sottoquery o CTE. Ecco un esempio che funzionerà con il database di esempio 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 

Il problema con la soluzione di sgriffinusa è che non si sa che ROW_NUMBER tua linea centro finirà per essere. Suppose che fosse la riga 30.

1

Per query simili uso funzioni analitiche senza CTE. Qualcosa di simile:

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

tale query funziona più veloce per me che CTE con join (risposta da Scott Bailey). Ma ovviamente meno elegante

+0

... eccetto che non è possibile utilizzare i valori analitici nella clausola 'WHERE', quindi ciò che hai scritto non funzionerà (almeno non in MS SQL). – feetwet

Problemi correlati