2009-02-10 10 views
11

Tengo una situación en la que necesito hacer una actualización en un gran conjunto de filas que solo puedo identificar por su ID (ya que los registros de destino son seleccionados por el usuario y no tienen nada en común que no sea el conjunto de registros que el usuario desea modificar). La misma propiedad se está actualizando en todos estos registros, así que me gustaría hacer una sola llamada ACTUALIZACIÓN.Usando "IN" en una cláusula WHERE donde el número de elementos en el conjunto es muy grande

¿Es una mala práctica o hay una forma mejor de hacer esta actualización que usar "WHERE IN (1,2,3,4, ..... 10000)" en la instrucción UPDATE?

¿Tendría más sentido utilizar estados de actualización individuales para cada registro y pegarlos en una sola transacción? En este momento estoy trabajando con SQL Server y Access pero, de ser posible, me gustaría escuchar soluciones más amplias de mejores prácticas en cualquier tipo de base de datos relacional.

+0

¿Qué tipo de "conjunto muy grande" estamos hablando aquí? ¿Son miles, millones o billones de valores? – Tooony

+0

Sí, miles. Mi muestra allí arriba tiene "10000" como último índice. Creo que ese será el número máximo, al menos a corto plazo. – Karim

Respuesta

6

yo siempre utilizo

WHERE id IN (1,2,3,4,.....10000) 

a menos que su cláusula era estúpidamente grande, que en realidad no debería pasar de la entrada del usuario.

editar: Por ejemplo, rieles hace mucho detrás de las escenas

Definitivamente no sería mejor hacer las instrucciones de actualización separados en una sola transacción.

13

Otra alternativa es almacenar esos números en una tabla temporal y usarlos en una combinación para realizar la actualización. Si puede ejecutar una sola declaración de actualización es definitivamente mejor que ejecutar una declaración por registro.

+0

Me gusta mucho esta idea, pero no tengo claro si será compatible con Access. Lamentablemente, estamos encadenados al acceso de apoyo. – Karim

+0

El acceso lo hará. A menudo utilizo una tabla temporal, incluso una definida permanentemente, vinculada a un control UI. Entonces, cuando el usuario selecciona los elementos que se procesarán, esos elementos se marcan o se insertan en la tabla temporal. Cuando el usuario hace clic en Aceptar, se une una consulta de ACTUALIZACIÓN a la tabla temporal. –

+1

Sólo asegúrese de que si la tabla está actualizando es una tabla vinculada en un SQL Server que la tabla temporal también está ahí. De lo contrario, es posible que Access intente extraer toda la tabla en la memoria para hacer la actualización y luego enviarla a SQL Server. También puede utilizar una tabla permanente con un identificador de transacción –

2

En Oracle hay un límite de valores que puede poner en una cláusula IN. Así que es mejor usar un OR, x = 1 o x = 2 ... esos no son limitados, hasta donde yo sé.

+0

1000 es el límite en Oracle para un conjunto pero se puede hacer en Oracle: en donde id (1,2, ..., 1000) o id en (1001, ..., 2000) o id en (... .) ... Sin embargo, es mejor usar la solución de jimmyorr. – tuinstoel

2

Yo usaría una tabla-variable/temp-table; inserta los valores en esto, y únete a él. Entonces puede usar el mismo conjunto varias veces. Esto funciona especialmente bien si está (por ejemplo) transmitiendo un CSV de ID como varchar. Como ejemplo de SQL Server:

DECLARE @ids TABLE (id int NOT NULL) 

INSERT @ids 
SELECT value 
FROM dbo.SplitCsv(@arg) // need to define separately 

UPDATE t 
SET t. // etc 
FROM [TABLE] t 
INNER JOIN @ids #i ON #i.id = t.id 
+0

No es una UDF que se podría utilizar para SplitCsv aquí -http: //stackoverflow.com/questions/519769/sql-server-2008-pass-arraylist-or-string-to-sp-for-in/519793#519793 –

+0

@Russ Cam - aplausos, ideal. –

4

¿Cómo se genera la cláusula IN?

Si no hay otra instrucción SELECT que genera esos valores, se podría simplemente enchufe que en el ACTUALIZACIÓN así:

UPDATE TARGET_TABLE T 
SET 
    SOME_VALUE = 'Whatever' 
WHERE T.ID_NUMBER IN(
        SELECT ID_NUMBER --this SELECT generates your ID #s. 
        FROM SOURCE_TABLE 
        WHERE SOME_CONDITIONS 
        ) 

En algunos RDBMSes, obtendrá un mejor rendimiento mediante el uso de la sintaxis EXISTE, que se vería así:

UPDATE TARGET_TABLE T 
SET 
    SOME_VALUE = 'Whatever' 
WHERE EXISTS (
      SELECT ID_NUMBER --this SELECT generates your ID #s. 
      FROM SOURCE_TABLE S 
      WHERE SOME_CONDITIONS 
       AND S.ID_NUMBER = T.ID_NUMBER 
      ) 
+0

definitivamente voy a tener esto en cuenta para futuras consultas, pero no, los artículos vienen directamente desde la interfaz de usuario que el usuario selecciona varios elementos de una lista. – Karim

+0

Ah, es una pena. Luego elegiría el enfoque de tabla temporal sugerido por ocdecio. Simplemente agregue cosas a la tabla temporal mientras el usuario hace sus selecciones. – JosephStyons

2

Sin saber lo que podría ser un número "grande" de identificaciones, me atrevería a adivinar. ;-)

Dado que está utilizando Access como base de datos, el número de ID no puede ser que alto. Suponiendo que estamos hablando de menos de, digamos, 10,000 números y debemos conocer las limitaciones de los contenedores para contener los ID (¿qué idioma se usa para el front-end?), Me quedaría con una declaración UPDATE; si es más legible y más fácil de realizar el mantenimiento más tarde. De lo contrario, los dividiría en múltiples declaraciones utilizando una lógica inteligente. Algo así como dividir la declaración en varias declaraciones con una, diez, cien, mil ... ID por declaración.

Luego, dejo que el optimizador de base de datos ejecute la declaración lo más eficientemente posible. Probablemente haría una 'explicación' en la consulta/consultas para asegurarme de que no pasa nada tonto.

Pero en mi experiencia, con frecuencia es correcto dejar este tipo de optimización al administrador de la base de datos. Lo que más tiempo lleva es generalmente la conexión real a la base de datos, por lo que si puede ejecutar todas las consultas dentro de la misma conexión, normalmente no hay problemas. Asegúrese de enviar todas las declaraciones UPDATE antes de comenzar a buscar y esperar a que vuelvan los conjuntos de resultados. :-)

+0

El campo Autonumérico de una base de datos de Jet es un caso especial de un campo entero largo de Jet y tiene un rango de -2,147,483,648 a 2,147,483,647, eso es un montón de números. Eso me parece un número MUY GRANDE. –

+0

Sí, ese es un número bastante grande (o un número entero largo ...). Pero, ¿ha intentado usar algo parecido a esa cantidad de filas en una base de datos de Access? ;-) Eso era más de lo que estaba pensando. – Tooony

1

En general, hay varias cosas que considerar.

  1. Caché de análisis de sentencias en la base de datos. Cada afirmación, con un número diferente de elementos en la cláusula IN, debe analizarse por separado. ESTÁS usando variables encuadernadas en lugar de literales, ¿verdad?
  2. Algunas bases de datos tienen un límite en el número de elementos en la cláusula IN. Para Oracle es 1000.
  3. Al actualizar, bloquea los registros. Si tiene varias instrucciones de actualización separadas, puede tener bloqueos sin salida. Esto significa que debe tener cuidado con el orden en que publica sus actualizaciones.
  4. La latencia de ida y vuelta a la base de datos puede ser alta, incluso para una declaración muy rápida. Esto significa que a menudo es mejor manipular muchos registros a la vez para ahorrar tiempo de viaje.

Recientemente cambiamos nuestro sistema para limitar el tamaño de las cláusulas internas y siempre utilizamos variables enlazadas porque esto reducía el número de sentencias SQL diferentes y, por lo tanto, mejoraba el rendimiento. Básicamente generamos nuestras sentencias SQL y ejecutamos múltiples sentencias si la cláusula in excede un cierto tamaño. No hacemos esto para las actualizaciones, así que no hemos tenido que preocuparnos por el bloqueo. Vas a.

El uso de una tabla temporal no puede mejorar el rendimiento porque debe completar la tabla temporal con los ID. Las pruebas de experimentación y rendimiento pueden indicarle la respuesta aquí.

Una sola cláusula IN es muy fácil de entender y mantener. Esto es probablemente de lo que deberías preocuparte primero. Si observa que el rendimiento de las consultas es deficiente, puede intentar una estrategia diferente y ver si ayuda, pero no optimizar de forma prematura. La cláusula IN es semánticamente correcta, así que déjala en paz si no está rota.

1

Si estuviera en Oracle, le recomendaría usar funciones de tabla, de forma similar a la publicación de Marc Gravell.

-- first create a user-defined collection type, a table of numbers 
create or replace type tbl_foo as table of number; 

declare 
    temp_foo tbl_foo; 
begin 
    -- this could be passed in as a parameter, for simplicity I am hardcoding it 
    temp_foo := tbl_foo(7369, 7788); 

    -- here I use a table function to treat my temp_foo variable as a table, 
    -- and I join it to the emp table as an alternative to a massive "IN" clause 
    select e.* 
    from emp e, 
     table(temp_foo) foo 
    where e.empno = foo.column_value; 
end; 
+0

Él no está en Oracle. D'oh. –

+1

Hm. Pidió "soluciones amplias de mejores prácticas en cualquier tipo de base de datos relacional". No estoy seguro de si Oracle es el único DB con funciones de tabla, pero este consejo se aplicaría a cualquier DB que admita ese tipo de característica. Di un ejemplo de Oracle porque en eso trabajo principalmente. : \ – jimmyorr

+1

+1, el OP de hecho también pidió una solución para otros proveedores. – tuinstoel

0

No sé el tipo de valores en su lista de EN. Si ellos son la mayor parte de los valores de 1 a 10.000, que podría ser capaz de procesarlos para obtener algo como:

WHERE MyID BETWEEN 1 AND 10000 AND MyID NOT IN (3,7,4656,987) 

O, si el Fuera de la lista seguiría siendo larga, el procesamiento de la lista y generar un montón de instrucciones BETWEEN:

WHERE MyID BETWEEN 1 AND 343 AND MyID BETWEEN 344 AND 400 ... 

Y así sucesivamente.

Por último, no tiene que preocuparse por cómo Jet procesará una cláusula IN si utiliza una consulta de paso a través. No puede hacer eso en el código, pero podría tener un QueryDef guardado que se define como un paso a paso y alterar la cláusula WHERE en el código en tiempo de ejecución para usar su lista IN.Luego todo se pasa a SQL Server, y SQL Server decidirá mejor cómo procesarlo.

Cuestiones relacionadas