2012-07-09 21 views
28

Tengo una consulta bastante complicada en mi base de datos PostgreSQL que abarca 4 tablas a través de una serie de subconsultas anidadas. Sin embargo, a pesar de la apariencia y configuración ligeramente engañosa, en última instancia devolverá dos columnas (de la misma tabla, si eso ayuda a la situación) basándose en esa coincidencia de dos parámetros externos (dos cadenas deben coincidir con campos en tablas diferentes). Soy bastante nuevo en el diseño de bases de datos en PostgreSQL, así que sé que existe algo aparentemente mágico llamado Views, y parece que podría ayudarme aquí, pero quizás no.¿Pasa los parámetros "WHERE" a la vista de PostgreSQL?

¿Hay alguna manera de mover mi consulta compleja dentro de una vista y de alguna manera simplemente pasarle los dos valores que necesito para que coincida? Eso simplificaría en gran medida mi código en el front-end (al cambiar las complejidades a la estructura de la base de datos). Puedo crear una vista que envuelva mi consulta de ejemplo estático, y eso funciona bien, sin embargo, eso solo funciona para un par de valores de cadena. Necesito poder usarlo con una variedad de valores diferentes.

Por lo tanto, mi pregunta es: ¿es posible pasar parámetros a una Vista estática y hacer que se vuelva "dinámico"? O tal vez una Vista no es la forma correcta de abordarlo. Si hay algo más que funcione mejor, ¡soy todo oídos!

* Editar: * a lo solicitado en los comentarios, aquí está mi pregunta tal como está ahora:

SELECT param_label, param_graphics_label 
    FROM parameters 
WHERE param_id IN 
     (SELECT param_id 
      FROM parameter_links 
      WHERE region_id = 
       (SELECT region_id 
        FROM regions 
        WHERE region_label = '%PARAMETER 1%' AND model_id = 
         (SELECT model_id FROM models WHERE model_label = '%PARAMETER 2%') 
       ) 
     ) AND active = 'TRUE' 
ORDER BY param_graphics_label; 

Los parámetros son fijados apagado por ciento símbolos anteriores.

+2

Nos puede ayudar a comprender mejor si fue capaz de publicar algunos fragmentos de código –

Respuesta

39

Se puede usar un conjunto de funciones de regresar:

create or replace function label_params(parm1 text, parm2 text) 
    returns table (param_label text, param_graphics_label text) 
as 
$body$ 
    select ... 
    WHERE region_label = $1 
    AND model_id = (SELECT model_id FROM models WHERE model_label = $2) 
    .... 
$body$ 
language sql; 

entonces usted puede hacer:

select * 
from label_params('foo', 'bar') 

Por cierto: ¿está seguro de que quiere:

AND model_id = (SELECT model_id FROM models WHERE model_label = $2) 

si model_label no es único (o la clave principal), entonces esto lanzará un error eventualmente. Es probable que desee:

AND model_id IN (SELECT model_id FROM models WHERE model_label = $2) 
+1

Está en lo correcto al adivinar que 'model_label' no es la clave principal. "Debería" ser único, pero eso no necesariamente se aplica por software. ('model_id' es la clave principal de' models'). Por lo que recuerdo, cambiar a IN no debería dañar el hecho de que solo quiero hacer coincidir una entrada, ¿correcto? – Devin

+2

@Devin: correcto, si usa IN funcionará incluso si se devuelve más de una fila. Si no, recibirá un error en el tiempo de ejecución. –

+1

Impresionante, eso funcionará exactamente como lo necesito. Y aprendí algo genial sobre las funciones de PostgreSQL. ¡Gracias por tu ayuda! – Devin

2

No creo que sea posible una vista "dinámica" como usted indicó.

¿Por qué no escribir un procedimiento almacenado que toma 2 argumentos en su lugar?

22

Además de lo que ya se @a_horse aclarado, se podría simplificar su SQL utilizando JOIN syntax en lugar de subconsultas anidadas. El rendimiento será similar, pero la sintaxis es mucho más corta y más fácil de administrar.

CREATE OR REPLACE FUNCTION param_labels(_region_label text, _model_label text) 
    RETURNS TABLE (param_label text, param_graphics_label text) AS 
$func$ 
    SELECT p.param_label, p.param_graphics_label 
    FROM parameters  p 
    JOIN parameter_links l USING (param_id) 
    JOIN regions   r USING (region_id) 
    JOIN models   m USING (model_id) 
    WHERE p.active 
    AND r.region_label = $1 
    AND m.model_label = $2 
    ORDER BY p.param_graphics_label; 
$func$ LANGUAGE sql; 
  • Si model_label no es única o alguna otra cosa en la consulta produce filas duplicadas, es posible que desee hacer que SELECT DISTINCT p.param_graphics_label, p.param_label - con un ORDER BY cláusula de equivalencia para un mejor rendimiento. O use una cláusula GROUP BY.

  • Desde Postgres 9.2 puede utilizar los nombres de parámetros declarados en lugar de $1 y $2 en funciones SQL. (Ha sido posible para funciones PL/pgSQL durante mucho tiempo).

  • Se debe tener cuidado para evitar conflictos de nombres. Es por eso que me convierto en un prefijo para los nombres de los parámetros en la declaración (esos son visibles en todas partes dentro de la función) y los nombres de las columnas de clasificación de tablas en el cuerpo.

  • Simplifiqué WHERE p.active = 'TRUE'-WHERE p.active, porque la columna active debe muy probablemente ser de tipo boolean, no text.

  • USING solo funciona si los nombres de las columnas no son ambiguos en todas las tablas a la izquierda de JOIN. Cosa que usted tiene que utilizar la sintaxis más explícito:
    ON l.param_id = p.param_id

+1

¡Oh, guau, eso es mucho mejor! Sé que las uniones están por ahí y habrían sido útiles para este problema, pero supongo que no resolví el problema para tratar de descubrir cómo usarlo aquí. Como resultado, ¡no es tan malo! – Devin

13

En la mayoría de los casos la función de puesta a devolver es el camino a seguir, pero en el caso de que usted quiere leer y escribir en el conjunto, una vista puede ser más apropiada. Y es posible para una vista para leer un parámetro de sesión:

CREATE VIEW widget_sb AS SELECT * FROM widget WHERE column = cast(current_setting('mydomain.myparam') as int) 

SET mydomain.myparam = 0 
select * from widget_sb 
[results] 

SET mydomain.myparam = 1 
select * from widget_sb 
[distinct results] 
0

Me gustaría reformular la consulta como la siguiente:

SELECT p.param_label, p.param_graphics_label 
    FROM parameters p 
where exists (
    select 1 
    from parameter_links pl 
    where pl.parameter_id = p.id 
    and exists (select 1 from regions r where r.region_id = pl.region_id 
) and p.active = 'TRUE' 
order by p.param_graphics_label; 

Asumiendo que usted tiene índices en las diversas columnas id, este la consulta debe ser significativamente más rápida que usar el operador IN; los parámetros existentes aquí solo usarán los valores del índice sin siquiera tocar la tabla de datos excepto para obtener los datos finales de la tabla de parámetros.

Cuestiones relacionadas