2010-09-08 29 views
16

Quiero seleccionar algunas filas según ciertos criterios, y luego tomar una entrada de ese conjunto y las 5 filas anteriores y posteriores.¿Cómo puedo seleccionar filas adyacentes a una fila arbitraria (en sql o postgresql)?

Ahora, puedo hacer esto numéricamente si hay una clave principal en la tabla (por ejemplo, claves primarias que son numéricamente 5 menos que la clave de la fila de destino y 5 más que la clave de la fila de destino).

así que seleccione la fila con la clave primaria de 7 y las filas en las inmediaciones:

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 

Pero si selecciono solamente ciertas filas, para empezar, pierdo ese método numérico de utilizar claves primarias (y que fue asumiendo que las claves no tenían vacíos en su orden de todos modos), y necesitan otra forma de obtener las filas más cercanas antes y después de una determinada fila específica.

La salida de clave principal de tal selecto podría parecerse más al azar y por lo tanto menos succeptable de localización matemática (ya que algunos resultados se filtran, hacia fuera, por ejemplo, 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 cómo, debido a los huecos en las claves primarias causados ​​por el ejemplo donde la condición (por ejemplo porque hay muchos elementos inactivos), ya no obtengo los 5 más cercanos y 5 más abajo, en cambio obtengo el más cercano 1 a continuación y el el 9 más cercano arriba, en cambio.

+0

Creo que la instrucción SELECT que escribió funcionaría en ambos ejemplos. – LatinSuD

+1

Err, la instrucción select devolvería valores, sí, pero las filas que devolvió serían esencialmente aleatorias, en lugar de 5 arriba y 5 abajo, es el problema. – Kzqai

Respuesta

19

Hay muchas maneras de hacerlo si ejecuta dos consultas con un lenguaje de programación, pero aquí es una manera de hacerlo en una consulta 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 

Esto devolvería el 5 filas más arriba, la fila de destino, y 5 filas abajo.

+0

Simple y eficaz, y funciona para muchas situaciones, esto es lo que he usado. – Kzqai

0

Puede hacer esto utilizando row_number() (disponible a partir de 8.4). Esto puede no ser la sintaxis correcta (que no están familiarizados con PostgreSQL), pero es de esperar se ilustrará la idea:

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

Esto generará una primera columna con un número secuencial. Puede usar esto para identificar la única fila y las filas arriba y abajo.

0

Si quisiera hacerlo de una manera "relacionalmente pura", podría escribir una consulta que ordenara y numerara las filas. Me gusta:

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

A continuación, utilice eso como una expresión de tabla común. Escriba una selección que lo filtre a las filas que le interesen, luego vuelva a unir sobre sí mismo utilizando un criterio de que el índice de la copia de la derecha de la tabla no es más que k mayor o menor que el índice de la tabla fila a la izquierda. Proyecte solo las filas de la derecha. Me gusta:

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 

¡Qué podría ser más simple!

Me imagino que las soluciones basadas en el número de fila serán más rápidas.

6

Aquí hay otra manera de hacerlo con las funciones analíticas de adelanto y retraso. Sería bueno si pudiéramos usar funciones analíticas en la cláusula WHERE. Entonces, en su lugar, debe usar subconsultas o CTE. Aquí hay un ejemplo que funcionará con la base de datos de muestra de 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 

El problema con la solución de sgriffinusa es que usted no sabe qué ROW_NUMBER su fila central va a terminar siendo. Supuso que sería la fila 30.

1

Para consultas similares, uso funciones analíticas sin CTE. Algo así como:

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

tales consulta funciona más rápido para mí que CTE con unirse a (respuesta de Scott Bailey). Pero por supuesto menos elegante

+0

... excepto que no puede usar los valores analíticos en la cláusula 'WHERE', por lo que lo que escribió no funcionará (al menos no en MS SQL). – feetwet

Cuestiones relacionadas