2009-06-02 11 views
5

quiero emitir una consulta como la siguiente¿Es posible usar GROUP BY con variables de vinculación?

select max(col1), f(:1, col2) from t group by f(:1, col2) 

donde :1 es una variable de vinculación. Usando PreparedStatement, si digo

connection.prepareStatement 
    ("select max(col1), f(?, col2) from t group by f(?, col2)") 

consigo un error de DBMS quejándose de que f(?, col2) no es una expresión GROUP BY.

¿Cómo se suele resolver esto en JDBC?

+0

qué es exactamente lo que desea enlazar? Muéstranos tu código, no solo un fragmento. Puede enlazar un valor como 'YYYY' pero no un nombre de columna. – tuinstoel

+1

ACTUALIZACIÓN: Ahora veo lo que está mal. El DBMS no reconoce que la expresión en el GROUP BY es la misma expresión en la lista SELECT. Es verlos como dos expresiones separadas. El uso de una variable de vinculación con nombre evita ese problema, ya que solo hay un valor. – spencer7593

Respuesta

8

Sugiero volver a escribir la declaración para que solo haya un argumento de vinculación. Este enfoque es un poco feo, pero devuelve el conjunto de resultados:

select max(col1) 
    , f_col2 
    from (
     select col1 
       , f(? ,col2) as f_col2 
      from t 
     ) 
group 
    by f_col2 

Esta declaración re-escrito tiene una referencia a un solo argumento se unen, por lo que ahora el DBMS ve las expresiones en la cláusula GROUP BY y la La lista SELECT es idéntica.

HTH

[EDIT]

(Me gustaría que hubiera una manera más bonita, es por eso que prefiero el enfoque argumento de vinculación llamada que Oracle utiliza. Con el controlador Perl DBI, argumentos posicionales se convierten en argumentos con nombre en la declaración enviada a Oracle.)

No vi el problema al principio, no entendí la pregunta original. (Aparentemente, varias otras personas también lo echaron de menos.) Pero después de ejecutar algunos casos de prueba, me di cuenta cuál era el problema, cuál era la pregunta que estaba funcionando.

Déjeme ver si puedo establecer el problema: cómo obtener dos argumentos de enlace separados (posicionales) para ser tratados (por el DBMS) como si fueran dos referencias al mismo argumento de enlace (nombrado).

El DBMS espera que la expresión en el GROUP BY coincida con la expresión en la lista SELECT. Pero las dos expresiones se consideran DIFERENTES incluso cuando las expresiones son idénticas, cuando la única diferencia es que cada expresión hace referencia a una variable de vinculación diferente. (Podemos demostrar algunos casos de prueba que al menos algunos DBMS permitirán, pero hay casos más generales que generarán una excepción.)

En este punto, la respuesta corta es que eso me dejó perplejo. La sugerencia que tengo (que puede no ser una respuesta real a la pregunta original) es reestructurar la consulta.

[/ EDIT]

que puede proporcionar más detalles si este enfoque no funciona, o si tiene algún otro problema para comprender. O si hay un problema con el rendimiento (puedo ver que el optimizador elige un plan diferente para la consulta reescrita, aunque devuelve el conjunto de resultados especificado. Para realizar más pruebas, realmente necesitamos saber qué DBMS, qué controlador, estadísticas, etc.) Años

EDITAR (ocho y media más tarde)

Otro intento de una reescritura de la consulta. Una vez más, la única solución que se me ocurre es una consulta con un marcador de posición de enlace. Esta vez, lo pegamos en una vista en línea que arroja una sola fila y lo sumamos a t. Puedo ver lo que está haciendo; No estoy seguro de cómo el optimizador de Oracle verá esto. Es posible que deseemos (o necesitemos) hacer una conversión explícita, p. TO_NUMBER(?) AS param, TO_DATE(?,'...') AS param, TO_CHAR(?) AS param, dependiendo del tipo de datos del parámetro de enlace, y el tipo de datos que queremos ser devueltos a partir de la vista.)

Esta es la forma en que lo haría en MySQL. La consulta original en mi respuesta hace la operación de unión dentro de la vista en línea (MySQL tabla derivada). Y queremos evitar materializar una tabla derivada de hughjass si podemos evitarla. Por otra parte, MySQL probablemente permita que la consulta original se desplace siempre que sql_mode no incluya ONLY_FULL_GROUP_BY. MySQL también nos dejaba soltar el FROM DUAL)

SELECT MAX(t.col1) 
     , f(v.param ,t.col2) 
    FROM t 
    CROSS 
    JOIN (SELECT ? AS param FROM DUAL) v 
    GROUP 
     BY f(v.param ,t.col2) 

De acuerdo con la respuesta de MadusankaD, dentro de los últimos ocho años, Oracle ha añadido para la reutilización de los mismos parámetros de vinculación llamada en el controlador JDBC, y la retención de equivalencia. (No he probado, pero si que funciona ahora, entonces genial.)

0

¿Intentó usar ? en lugar de las variables de vinculación con nombre? Además, ¿qué driver estás usando? Probé este ejemplo trivial utilizando el controlador fino, y parecía funcionar bien:

PreparedStatement ps = con.prepareStatement("SELECT COUNT(*), TO_CHAR(SYSDATE, ?) FROM DUAL GROUP BY TO_CHAR(SYSDATE, ?)"); 
ps.setString(1, "YYYY"); 
ps.setString(2, "YYYY"); 
ps.executeQuery(); 
0

En el segundo caso, en realidad hay dos variables - que tendrá que enviar a ambos con el mismo valor.

+0

@Cade: establecer ambas variables en el mismo valor no es suficiente, el DBMS ve dos argumentos de enlace separados. El DBMS ve expresiones diferentes en SELECT y GROUP BY. – spencer7593

+0

Me preocupaba que ese fuera el caso. Yo siempre uso procs almacenados. –

1

Aunque se haya emitido una consulta a través del controlador JDBC (usando PreparedStatement) así:

select max(col1), f(:1, col2) from t group by f(:1, col2) 

Por último controlador JDBC reemplaza estos como la consulta siguiente antes de analizar en la base de datos, aunque haya utilizado el mismo nombre de variable de enlace en ambos lugares.

select max(col1), f(*:1*, col2) from t group by f(*:2*, col2) 

Pero en el oráculo esto no se reconocerá como una cláusula válida por grupo. Y también el controlador JDBC normal no admite variables de vinculación con nombre.

Para eso puede usar la clase OraclePreparedStatement para su conexión. Eso significa que es Oracle JDBC. Entonces puede usar variables de vinculación con nombre. Solucionará tu problema.

A partir de los controladores JDBC de Oracle Database 10g, se puede vincular por nombre utilizando los métodos setXXXAtName.

http://docs.oracle.com/cd/E24693_01/java.11203/e16548/apxref.htm#autoId20

+0

Agradable. Esa es una mejora (el método 'setXXXAtName' para establecer un * solo * parámetro de vinculación con nombre, y tener ese mismo marcador de posición referenciado * múltiples * lugares en la misma instrucción, y Oracle optimizer reconociendo que una expresión con el marcador de posición es idéntica, por lo que la misma expresión se puede usar en la cláusula GROUP BY y en la lista SELECT. Esa es una mejora masiva en funcionalidad sobre JDBC vainilla. – spencer7593