2011-07-28 19 views
21

En primer lugar, aquí está el resumen sucinto de la pregunta:MySQL Insertar si (a medida, si las declaraciones)

¿Es posible ejecutar una sentencia condicional INSERT? algo parecido a esto:

IF(expression) INSERT... 

Ahora, sé que puedo hacer esto con un procedimiento almacenado. Mi pregunta es: ¿puedo hacer esto en mi consulta?


Ahora, ¿por qué querría hacer eso?

Supongamos que tenemos las siguientes 2 tablas:.

products: id, qty_on_hand 
orders: id, product_id, qty 

Ahora, supongamos que un pedido de 20 muñecas del vudú (producto ID 2) se presenta en
En primer lugar, comprobar si hay suficiente cantidad disponible:

SELECT IF(
    (SELECT SUM(qty) FROM orders WHERE product_id = 2 ) + 20 
    <= 
    (SELECT qty_on_hand FROM products WHERE id = 2) 
, 'true', 'false'); 

Entonces, si se evalúa como verdadera, se ejecuta una consulta INSERT.
Hasta ahora todo bien.


Sin embargo, hay un problema con la concurrencia.
Si 2 pedidos llegan al exactamente el mismo horario, es posible que ambos lean la cantidad disponible antes de que cualquiera de ellos haya ingresado el pedido. Entonces ambos harán el pedido, excediendo así el qty_on_hand.


Así que, volviendo a la raíz de la cuestión:
¿Es posible ejecutar una sentencia condicional INSERT, por lo que podemos combinar estas dos consultas en una sola?

He buscado mucho, y el único tipo de declaración condicional INSERT que pude encontrar fue ON DUPLICATE KEY, que obviamente no se aplica aquí.

Respuesta

38
INSERT INTO TABLE 
SELECT value_for_column1, value_for_column2, ... 
FROM wherever 
WHERE your_special_condition 

Si no se devuelven las filas de la selección (porque su condición especial es falsa), no se insertan.

Utilizando el esquema de la pregunta (suponiendo que su columna es idauto_increment):

insert into orders (product_id, qty) 
select 2, 20 
where (SELECT qty_on_hand FROM products WHERE id = 2) > 20; 

Esto insertará ninguna fila si no hay suficiente stock disponible, de lo contrario se creará la orden de fila.

¡Buena idea por cierto!

+0

@Bohemian: No es necesario tener dos instrucciones 'SELECT'. Uno será suficiente. Eche un vistazo a mi respuesta. :) – Shef

+0

Es cierto, pero estaba tratando de hacer coincidir mi patrón * general *. Aunque me gusta tu respuesta ... +1 – Bohemian

+0

@Joseph Silber: ¿Para hacer qué? Donde esta la resta? ¿Qué tiene que ver la resta con la normalización? : D ¿Entiende que la consulta anterior se compone de dos declaraciones 'SELECT' en comparación con la mía, que se ejecuta con una? (Nada en contra de su respuesta, o usted, bohemio). – Shef

2

Probablemente resuelva el problema de la manera incorrecta.

Si tiene miedo de que se produzcan dos operaciones de lectura al mismo tiempo y, por lo tanto, una funcionará con datos obsoletos, la solución es use bloqueos o transacciones.

Tener la consulta hacer esto:

  • tabla de bloqueo para leer
  • tabla de lectura
  • tabla de actualización
  • bloqueo de seguridad de
+0

No estoy seguro de que el bloqueo sea la mejor solución. Podría causar problemas graves de rendimiento. –

2

No estoy seguro sobre la concurrencia, que necesita para leer sobre el bloqueo en mysql, pero esto le permitirá estar seguro de que solo toma 20 elementos si hay 20 artículos disponibles:

update products 
set qty_on_hand = qty_on_hand - 20 
where qty_on_hand >= 20 
and id=2 

Puede verificar cuántas filas se vieron afectadas. Si ninguno se vio afectado, no tenía suficiente stock. Si se afectó 1 fila, efectivamente ha consumido el stock.

16

Probar:

INSERT INTO orders(product_id, qty) 
SELECT 2, 20 FROM products WHERE id = 2 AND qty_on_hand >= 20 

Si un producto con id igual a 2 existe y la qty_on_hand es mayor o igual a 20 de este producto, a continuación, una pieza de inserción se producirá con los valores product_id = 2, y qty = 20. De lo contrario, no se insertará.

Nota: Si sus productos son identificadores de nota única, es posible que desee agregar una cláusula LIMIT al final de la declaración SELECT.

+0

¿Cómo negarías esto? – user2340939

+0

Una forma es agregar al final lo siguiente: TENER COUNT (*) = 0. – user2340939

Cuestiones relacionadas