2008-09-23 26 views
12

Estoy tratando de ejecutar la siguiente instrucción SQL en Oracle, y que lleva mucho tiempo, para ejecutar:optimización de una consulta SELECT que corre lento en Oracle, que rápidamente se ejecuta en SQL Server

SELECT orderID FROM tasks WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL) 

Si me quedo sólo el sub-parte que está en la cláusula IN, que se ejecuta muy rápidamente en Oracle, es decir

SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL 

¿por qué toda la declaración de llevar mucho tiempo en Oracle? En SQL Server toda la instrucción se ejecuta rápidamente.

¿Existe una declaración SQL más simple/diferente/mejor que debo utilizar?

algunos detalles más sobre el problema:

  • Cada pedido está hecho de muchas tareas
  • se asignará Cada pedido (uno o más de sus funciones tendrán engineer1 y ENGINEER2 establecido) o el orden puede estar sin asignar (toda su tarea tiene valores nulos para los campos de ingeniero)
  • Estoy tratando de encontrar todos los ID de pedido que no están asignados.

Por si acaso hace alguna diferencia, hay ~ 120k filas en la tabla, y 3 tareas por orden, por lo que ~ 40k pedidos diferentes.

Las respuestas a las respuestas:

  • yo preferiría una instrucción SQL que funciona tanto en SQL Server y Oracle.
  • Las tareas solo tienen un índice en orderID y taskID.
  • Probé la versión NO EXISTE de la declaración, pero se ejecutó durante más de 3 minutos antes de que la cancelara. Tal vez necesita una versión JOIN de la declaración?
  • También hay una tabla de "pedidos" con la columna orderID. Pero estaba tratando de simplificar la pregunta al no incluirla en la declaración SQL original.

Supongo que en la declaración SQL original, la subconsulta se ejecuta cada vez para cada fila en la primera parte de la declaración SQL, aunque es estática y solo debe ejecutarse una vez.

Ejecución

ANALYZE TABLE tasks COMPUTE STATISTICS; 

hizo que mi instrucción SQL original de ejecutar mucho más rápido.

Aunque todavía tengo curiosidad por saber por qué tengo que hacer esto, y si/cuándo tendría que volver a ejecutarlo?

Las estadísticas dan optimzer información basada en los costes de Oracle que que necesita para determinar la eficiencia de los diferentes planes de ejecución: para ejemplo, el número de rowsin una mesa, la anchura media de filas, más alto y valores más bajos por columna, número de valores distintos por columna, agrupamiento factor de índices, etc.

En una pequeña base de datos puede simplemente configurar un trabajo para recopilar estadísticas todas las noches y dejarlo en paz. De hecho, esto es por defecto debajo de 10g. Para las implementaciones más grandes de , generalmente tiene que sopesar la estabilidad de los planes de ejecución en comparación con la forma en que cambian los datos , lo cual es un equilibrio complicado.

Oracle también tiene una característica llamada "muestreo dinámico" que se utiliza para tablas de ejemplo para determinar estadísticas relevantes en tiempo de ejecución. Es mucho más a menudo utilizado con los datos almacenes donde la sobrecarga del muestreo superado por el aumento de rendimiento potencial para una consulta de larga duración .

+1

Nunca entenderé por qué los programadores a menudo ponen DISTINCT en sus cláusulas IN. ¿7 en (1, 1, 1, 1, 2, 2, 2, 7)? ¿Es 5? La respuesta no cambia si mi lista es (1, 2, 7). Cuando ejecuto esto en Oracle, simplemente ignora lo distinto ... la CBO se da cuenta de que no hay ningún valor. –

Respuesta

9

A menudo, este tipo de problema desaparece si se analizan las tablas involucradas (por lo que Oracle tiene una mejor idea de la distribución de los datos)

ANALYZE TABLE tasks COMPUTE STATISTICS; 
+0

Increíble, después de ejecutar esto me llevó poco menos de 1 segundo ejecutar mi declaración SQL original. – RickL

+5

Esta es una sintaxis obsoleta para recopilar estadísticas. DBMS_STATS es una forma más robusta. http://download.oracle.com/docs/cd/B19306_01/server.102/b14211/stats.htm#PFGRF30102 –

+3

Estoy de acuerdo, esto es obsoleto. Por favor, trate de evitar usarlo. Use DBMS_STATS y asegúrese de obtener también sus índices, puede establecer cascade => true cuando reúna estadísticas para la tabla. –

3

La cláusula "IN" es conocida en Oracle por ser bastante lenta. De hecho, el optimizador de consultas internas en Oracle no puede manejar las declaraciones con "IN" bastante bien. trate de usar "existe":

SELECT orderID FROM tasks WHERE orderID NOT EXISTS 
    (SELECT DISTINCT orderID FROM tasks WHERE 
     engineer1 IS NOT NULL AND engineer2 IS NOT NULL)`print("code sample");` 

Precaución: Por favor, compruebe si la consulta se acumula los mismos resultados de los datos.

Edith dice: ooops, la consulta no está bien formada, pero la idea general es correcta. Oracle tiene que realizar un escaneo de tabla completo para la segunda consulta (interna), generar los resultados y luego compararlos con la primera consulta (externa), por eso se está desacelerando. Trate

SELECT orderID AS oid FROM tasks WHERE NOT EXISTS 
    (SELECT DISTINCT orderID AS oid2 FROM tasks WHERE 
     engineer1 IS NOT NULL AND engineer2 IS NOT NULL and oid=oid2) 

o algo similar ;-)

+0

Llegué a la misma consulta (vea mi respuesta a continuación), excepto por: * La subconsulta no tiene ninguna razón para seleccionar IDENTIFICACIONES DISTINCT. * suelte el "orderID" entre WHERE y NOT EXISTS (error de sintaxis). * soltar la 'impresión (' muestra de código '), obviamente ;-) – Mac

+0

Cuando intento la segunda consulta, ¿se produce un error? ORA-00904: "OID2": identificador no válido – RickL

+0

Utilice el "AS oid2" que inserté, no estaba en la consulta anterior. – Georgi

0

no es su consulta lo mismo que

SELECT orderID FROM tasks 
WHERE engineer1 IS NOT NULL OR engineer2 IS NOT NULL 

?

+0

No, no lo es. Cometí el mismo error :-) Cada orden tiene múltiples tareas, y si una de esas tareas tiene un ingeniero asignado, la orden cuenta como "asignada" –

0

¿Qué tal:

SELECT DISTINCT orderID FROM tasks t1 WHERE NOT EXISTS (SELECT * FROM tasks t2 WHERE t2.orderID=t1.orderID AND (engineer1 IS NOT NULL OR engineer2 IS NOT NULL)); 

No soy un gurú de la optimización, pero tal vez también se pasa por alto algunos índices en la base de datos de Oracle.

+0

Intenté esto pero aún se estaba ejecutando más de un minuto después cuando cancelé eso. – RickL

0

Otra opción es utilizar menos (excepto los MSSQL)

SELECT orderID FROM tasks 
MINUS 
SELECT DISTINCT orderID FROM tasks WHERE engineer1 IS NOT NULL 
AND engineer2 IS NOT NULL 
+0

Pensé en eso también, pero creo que esta consulta tampoco es la más rápida. – Georgi

+0

Sí, tenga cuidado, la instrucción menos usa mucha memoria –

+0

Normalmente, lo distinto implícito sería una sobrecarga innecesaria, pero en este caso probablemente sería apropiado. Sin embargo, dos escaneos de la tabla pueden ser ineficientes. –

-2

Sub-consultas son "malos" con Oracle. En general, es mejor usar combinaciones.

He aquí un artículo sobre cómo reescribir sus subconsultas con Ingreso: http://www.dba-oracle.com/sql/t_rewrite_subqueries_performance.htm

+0

Nada es necesario "malo". La diferencia entre los beneficios de diferentes técnicas casi siempre se reduce a las distribuciones de valores de un conjunto de datos en particular y la presencia o ausencia de índices y restricciones. –

-1

Aquí se presenta una aproximación alternativa que creo que da lo que quiere:

SELECT orderID 
FROM tasks 
GROUP BY orderID 
HAVING COUNT(engineer1) = 0 OR COUNT(engineer2) = 0 

No estoy seguro de si quieres "Y" o "O" en la cláusula HAVING. Parece que de acuerdo con la lógica comercial, estos dos campos deberían estar poblados o ambos ser NULL; si esto está garantizado, entonces podría reducir la condición a solo verificar engineer1.

Tu consulta original, creo, daría varias filas por orderID, mientras que la mía solo dará una.Supongo que esto está bien, ya que solo está obteniendo el ID de pedido.

2

Algunas preguntas:

  • ¿Cuántas filas hay en las tareas?
  • ¿Qué índices están definidos en él?
  • ¿Se ha analizado la tabla recientemente?

Otra manera de escribir la misma consulta sería:

select orderid from tasks 
minus 
select orderid from tasks 
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL 

Sin embargo, preferiría esperar que la consulta implica una "órdenes" tabla:

select orderid from ORDERS 
minus 
select orderid from tasks 
where engineer1 IS NOT NULL AND engineer2 IS NOT NULL 

o

select orderid from ORDERS 
where orderid not in 
(select orderid from tasks 
    where engineer1 IS NOT NULL AND engineer2 IS NOT NULL 
) 

o

select orderid from ORDERS 
where not exists 
(select null from tasks 
    where tasks.orderid = orders.orderid 
    and engineer1 IS NOT NULL OR engineer2 IS NOT NULL 
) 
+0

Su segunda propuesta NO es lo mismo, ya que puede haber varias tareas por orderID, algunas asignadas y otras no. Solo quiere orderIDs para los que NINGUNA de las tareas está asignada. –

+0

Su última propuesta debería tener una subconsulta correlacionada (por ejemplo, agregue "AND tasks.orderID = orders.orderID"). No puede simplemente cambiar NOT IN a NOT EXISTS sin modificar la subconsulta. –

+0

Ambos puntos se corrigieron ahora –

2

Estoy de acuerdo con TZQTZIO, no recibo su consulta.

Si suponemos que la consulta tenía sentido, entonces puede intentar usar EXISTS ya que algunos sugieren y evitan IN. IN no siempre es malo y hay casos probables que uno podría demostrar que en realidad funciona mejor que EXISTS.

El título de la pregunta no es muy útil. Podría establecer esta consulta en una base de datos Oracle y hacerla funcionar lentamente y hacerla funcionar rápidamente en otra. Hay muchos factores que determinan cómo la base de datos resuelve la consulta, las estadísticas de objetos, las estadísticas de esquema SYS y los parámetros, así como el rendimiento del servidor. Sqlserver vs. Oracle no es el problema aquí.

Para aquellos interesados ​​en el ajuste de las consultas y el rendimiento, y desean obtener más información, algunos de los términos de búsqueda de Google son "roble tabla oráculo" y "oráculo jonathan lewis".

3

me gustaría probar usando une en lugar

SELECT 
    t.orderID 
FROM 
    tasks t 
    LEFT JOIN tasks t1 
     ON t.orderID = t1.orderID 
     AND t1.engineer1 IS NOT NULL 
     AND t1.engineer2 IS NOT NULL 
WHERE 
    t1.orderID IS NULL 

también su búsqueda original, probablemente sería más fácil de entender si se ha especificado como:

SELECT orderID FROM orders WHERE orderID NOT IN 
(SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL) 

(suponiendo que tiene tabla de pedidos con todas las órdenes en la lista)

que se puede volver a escribir con combinaciones como:

SELECT 
    o.orderID 
FROM 
    orders o 
    LEFT JOIN tasks t 
     ON o.orderID = t.orderID 
     AND t.engineer1 IS NOT NULL 
     AND t.engineer2 IS NOT NULL 
WHERE 
    t.orderID IS NULL 
+0

Esto no funciona porque solo debe devolver pedidos donde * todas * las tareas de ingeniería de campos no son nulas, pero esto devolverá pedidos donde algunas tareas son noll de ingeniería, y algunas tareas no son nulas. ¿Quisiste decir que la cláusula WHERE es diferente? – RickL

+0

Se piensa que devuelve el mismo resultado que la consulta que ha especificado: SELECT orderID DE tareas DONDE orderId NO EN (SELECT orderID distinto de tareas en las que engineer1 NO ES NULO Y ENGINEER2 NO ES NULO) que creo que las devoluciones: Todos pedidos que no tienen ninguna tarea con ambos ingenieros asignados – kristof

+0

Pero quizás me falta algo, si un ingeniero asignado es suficiente para no enumerar el pedido, cambie la combinación izquierda para leer como: tareas IZQUIERDA UNIÓN t1 EN t.ordenID = t1. orderID AND (t1.engineer1 IS NOT NULL O t1.engineer2 IS NOT NULL) Pero eso sería diferente de su consulta original. – kristof

-1

Si no tiene índice sobre las columnas Engineer1 e Engineer2, siempre generará una exploración de tabla en SQL Server y su equivalente en Oracle.

Si solo necesita los pedidos que tienen tareas sin asignar, lo siguiente debería funcionar bien en ambas plataformas, pero también debería considerar agregar los índices a la tabla Tareas para mejorar el rendimiento de la consulta.

SELECT DISTINCT orderID 
FROM tasks 
WHERE (engineer1 IS NULL OR engineer2 IS NULL) 
+0

Normalmente, los campos de ingeniero serán claves foráneas, por lo que los índices deberían estar allí. – tzot

+0

@ ΤΖΩΤΖΙΟΥ Gracias por el downvote, pero ¿has leído la pregunta? "Las tareas solo tienen un índice en orderID y taskID" –

0

Si decide crear una tabla de Pedidos, me gustaría añadir una bandera que le sea asignada, y crear un índice de mapa de bits.Este enfoque también lo obliga a modificar la lógica comercial para mantener el indicador actualizado, pero las consultas serán muy rápidas. Depende de cuán críticas sean las consultas para la aplicación.

En cuanto a las respuestas, cuanto más simple mejor en este caso. Olvídese de subconsultas, uniones, bys separados y grupales, ¡no son necesarios en absoluto!

1

Creo que muchas personas tienen el SQL correcto, pero les falta una combinación entre las consultas internas y externas.
Prueba esto:

SELECT t1.orderID 
FROM tasks t1 
WHERE NOT EXISTS 
     (SELECT 1 
     FROM tasks t2 
     WHERE t2.orderID = t1.orderID 
     AND t2.engineer1 IS NOT NULL 
     AND t2.engineer2 IS NOT NULL) 
+0

Gracias, probé esto y es la sintaxis correcta, pero aún se ejecutaba durante más de 3 minutos cuando lo cancelé. – RickL

0

¿Qué proporción de la las filas en la tabla cumplen la condición "ingeniero1 NO ES NULO Y el ingeniero2 NO ES NULO"?

Esto le dice (más o menos) si valdría la pena tratar de usar un índice para recuperar los orderid's asociados.

Otra manera de escribir la consulta en Oracle que se ocuparía de los casos no indexados muy bien sería:

select distinct orderid 
from 
(
select orderid, 
     max(case when engineer1 is null and engineer2 is null then 0 else 1) 
      over (partition by orderid) 
      as max_null_finder 
from tasks 
) 
where max_null_finder = 0 
0

El optimizador de Oracle hace un buen trabajo de los estados de procesamiento negativo. Si vuelve a escribir su consulta usando MINUS, es probable que se ejecute con bastante rapidez:

SELECT orderID FROM tasks 
MINUS 
SELECT DISTINCT orderID FROM tasks WHERE 
engineer1 IS NOT NULL AND engineer2 IS NOT NULL 
0

Nueva toma.

Iff: función

  • El COUNT() no cuenta valores NULL

y

  • Usted quiere que el orderID de todas las tareas en las que ninguno de las tareas tienen engineer1 o engineer2 establecido en un valor

continuación esto debería hacer lo que quiera:

SELECT orderID 
FROM tasks 
GROUP BY orderID 
HAVING COUNT(engineer1) = 0 AND COUNT(engineer2) = 0 

favor probarlo.

1

"Aunque todavía tengo curiosidad por saber por qué tengo que hacer esto, y si/cuándo tendría que volver a ejecutarlo?" Las estadísticas dan la información del optimizador basado en costos de Oracle que necesita para determinar la eficiencia de los diferentes planes de ejecución: por ejemplo, el número de filas en una tabla, valores distintos por columna, factor de agrupamiento de índices, etc.

En una base de datos pequeña, puede configurar un trabajo para recopilar estadísticas todas las noches y dejarlas en paz. De hecho, este es el valor predeterminado en 10g.Para implementaciones más grandes, por lo general, debe sopesar la estabilidad de los planes de ejecución frente a la forma en que los datos cambian, lo cual es un equilibrio difícil.

Oracle también tiene una característica llamada "muestreo dinámico" que se utiliza para muestrear tablas para determinar estadísticas relevantes en tiempo de ejecución. Se usa mucho más a menudo con almacenes de datos donde la sobrecarga del muestreo es mayor que el aumento del rendimiento potencial para una consulta de larga ejecución.

+0

Gracias, copió su respuesta a la pregunta. – RickL

+0

Dave's en una muestra dinámica Binge –

+0

Me encanta el muestreo dinámico. No sé, no se habla más a menudo. –

0

Estoy de acuerdo con ΤΖΩΤΖΙΟΥ y wearejimbo que su consulta debe ser ...

SELECT DISTINCT orderID FROM Tasks 
WHERE Engineer1 IS NULL OR Engineer2 IS NULL; 

No sé acerca de SQL Server, pero esta consulta no será capaz de tomar ventaja de los índices porque nula las filas no están en índices. La solución a esto sería volver a escribir la consulta de una manera que permita crear un índice basado en función que solo incluya las filas de valores nulos. Esto podría hacerse con NVL2, pero probablemente no sería portátil para SQL Server.

Creo que la mejor respuesta no es la que cumple con sus criterios y que es escribir una declaración diferente para cada plataforma que sea mejor para esa plataforma.

+1

SQL Server permite solo una fila NULL en un índice excepto que crea un índice filtrado. En este caso, no incluye NULLs. – usr

+0

Gracias por la información. –

Cuestiones relacionadas