2010-01-26 8 views
7

Tengo una tabla con muchas columnas. ¿Hay alguna manera de hacer una consulta que responda a la pregunta: "Para un _id particular (la clave principal), ¿qué campo (s) en esta fila tienen un valor de 10"?SQL: Busque una lista de columnas con un valor determinado (dentro de una fila)

EDIT:

Aclaración: La mesa está configurado correctamente. La consulta que estoy haciendo es una consulta manual ya que rastrear algunos datos incorrectos. La tabla se ha optimizado para ser la más rápida para las consultas automáticas que representan las vastas mayorías de las consultas ejecutadas. (Y con más de 95 millones de filas, cada optimización es importante)

Me doy cuenta de que mi pregunta es hacer algo que SQL no tenía la intención de hacer. Solo espero que haya algún truco para obtener lo que quiero.

EDITAR para la posteridad:

En nuestro sistema, que tiene muchas cuentas de usuario diferentes. Una cuenta es la que utilizamos para todas las consultas de solo lectura (esta es la que uso la mayor parte del tiempo). No propietario de las tablas en cuestión, así que cuando me estaba adaptando la respuesta a mi situación, he tenido que hacer el siguiente cambio:

USER_TAB_COLUMNS tenido que convertirse en ALL_TAB_COLUMNS y tuve que añadir OWNER = '[OWNER]' a la consulta.

Respuesta

3

Esta no es una pieza normal de la funcionalidad de la base de datos. Sin embargo, no eres la primera persona que ha pedido esto o algo así.

La solución requiere dos cosas. El primero es el diccionario de datos; la base de datos Oracle no es compatible con Reflection, pero viene con un conjunto de vistas que nos dan metadatos sobre nuestros objetos de base de datos. En este caso, necesitamos user_tab_columns, que nos dará las columnas para una tabla dada. La segunda cosa es SQL dinámico; esta es la capacidad de ensamblar una consulta SQL en tiempo de ejecución y luego ejecutarla. Hay un par de maneras de hacerlo, pero generalmente los cursores de referencia son suficientes.

El siguiente código es una prueba de concepto. Se necesitan cuatro parámetros:

  1. el nombre de la tabla que desea buscar
  2. el nombre de la columna de clave principal de esa mesa
  3. el valor de clave principal que desea limitar a
  4. el valor que quiero buscar

Ya no está listo, por lo que puede necesitar editarlo para ordenar la salida o para hacer que el programa sea más flexible.

create or replace procedure search_cols 
    (tname in user_tables.table_name%type 
    , pk_col in user_tab_columns.column_name%type 
    , pk in number 
    , val in number) 
is 
    firstcol boolean := true; 
    stmt varchar2(32767); 
    result varchar2(32767); 
    rc sys_refcursor; 
begin 
    stmt := 'select '; 
    <<projection>> 
    for lrec in (select column_name from user_tab_columns 
        where table_name = tname 
        and column_name != pk_col 
        and data_type = 'NUMBER' 
        order by column_id) 
    loop 
     if not firstcol then 
      stmt := stmt || chr(10) || '||'',''||'; 
     else 
      firstcol := false; 
     end if; 
     stmt := stmt || ' case when '|| lrec.column_name||' = '|| val || 
          ' then '''|| lrec.column_name || ''' else null end'; 
    end loop projection; 
    stmt := stmt || chr(10)|| ' from '||tname||' where '|| pk_col || ' = '|| pk; 
    -- dbms_output.put_line(stmt); 
    open rc for stmt; 
    fetch rc into result; 
    close rc; 
    dbms_output.put_line(tname || '::' || val || ' found in '||result); 
end search_cols; 
/

Como puede ver, SQL dinámico es difícil de leer. Es más difícil depurar :) Por lo tanto, es una buena idea tener un medio para mostrar la declaración final.

De todos modos, aquí están los resultados:

SQL> set serveroutput on size unlimited 
SQL> exec search_cols('T23', 'ID', 111, 10) 
T23::10 found in ,COL_B,COL_C, 

PL/SQL procedure successfully completed. 

SQL> exec search_cols('T23', 'ID', 222, 10) 
T23::10 found in COL_A,,, 

PL/SQL procedure successfully completed. 

SQL> 
+0

Sí, este era el tipo de cosa que sabía que tenía que estar allí. ¡Gracias! –

1

Parece que su base de datos no está debidamente normalizada. Dicho esto, probablemente puedas utilizar el comando UNPIVOT dentro de una subconsulta para hacer lo que estás tratando de hacer.

+0

¿Podría dar más detalles sobre unpivot? Específicamente, ¿funciona unpivot en Oracle? –

+0

@David Oneill: 'UNPIVOT' (y' PIVOT' para el caso) solo son compatibles con Oracle 11g +. Cualquier cosa anterior requiere el uso de sentencias 'CASE' o' DECODE' - verifique las etiquetas sql, pivot y/o ranking en SO. –

1

Mi solución sería utilizar tablas del diccionario (USER_TAB_COLUMNS) a buscar dinámicamente el nombre de todas las columnas número de la tabla, y el SQL dinámico, porque aquí no lo hago mira cómo uno podría evitarlo.

DECLARE 
    CURSOR cur_columns IS 
      select COLUMN_NAME from USER_TAB_COLUMNS 
      where TABLE_NAME='<MY_TABLE>' and DATA_TYPE='NUMBER'; 
    query_text VARCHAR2(1000); 
    result_value NUMBER; 
BEGIN 
    -- Iterate through each NUMBER column of the table 
    FOR rec_col IN cur_columns LOOP 

     -- In my line of primary key <MY_ID>, check if the current column has 
     -- the wanted value. 
     query_text := 
     'SELECT count(1) FROM <MY_TABLE> WHERE <TABLE_ID> = <MY_ID> AND ' 
     || rec_col.COLUMN_NAME || ' = <MY_VALUE>'; -- < the "magic" is here 

     EXECUTE IMMEDIATE query_text INTO result_value; 

     IF result_value > 0 THEN 
      DBMS_OUTPUT.PUT_LINE('Got a match for column ' || 
           rec_col.COLUMN_NAME || '.'); 
     END IF; 

    END LOOP; 
END; 

Obviamente, tendrá que reemplazar todas las variables <> con su valor elegido.

Para consultas manuales, funciona bien. Sin embargo, el rendimiento de esto es probablemente malo, así que no lo ejecute contra grandes conjuntos de datos como está.

+0

Sí, este era el tipo de cosa que sabía que tenía que estar allí. ¡Gracias! –

Cuestiones relacionadas