Se requieren dos columnas anulables, una para tener valor

10

Pregunta sin explicación:

¿Hay alguna forma de tener una restricción de 2 valores nulos que siempre requiere que 1 tenga valor? Por ejemplo, dos columnas de fecha son nulas pero tienen al menos 1 que requiere tener un valor

Descripción del problema:

Digamos que tengo una tabla llamada Gastos

y tiene 2 fechas:

prevision_expense_expiration_date FECHA NULABLE gasto_pago_fecha FECHA NULABLE

La lógica de esas 2 columnas es la siguiente:

Hice una compra de algo y sé que tengo que pagarlo, alguna fecha, como una factura telefónica. Ingresaré esto como gasto con una fecha_pago_pago. Esta fecha es la supuesta fecha que debo pagar pero no la fecha real del pago, como la fecha de vencimiento de la factura.

En otra situación, vendo una tarjeta de regalo de algún proveedor por su servicio. Es posible que tenga que pagar la compra a mi proveedor del servicio transferido a mi cliente solo si el cliente canjea la tarjeta. Por lo tanto, la tarjeta de regalo tiene una fecha de vencimiento, quiero hacer una previsión para ese "gasto" sin insertar como gasto por el tiempo que la tarjeta de regalo es válida, si la tarjeta de regalo caduca, ese "gasto" no debe ingresar en la cuenta sistema.

Sé que puedo tener 2 tablas iguales llamadas prevision_expense y confirmado_expense pero no suena bien, así que tengo en la misma tabla, 2 fechas, anulables, pero quiero restringir o algo para que siempre se requiera una.

Hay otra estrategia posible:

payment_date DATE NOT NULL is_prevision_date BOOL NOT NULL

Entonces, en este caso, si la fecha es la previsión, el valor de bool sería 1, de lo contrario será 0. Sin valores nulos, todo está bien. excepto que quiero la opción de almacenar AMBOS valores cuando primero tengo una fecha de previsión y LUEGO (digamos dos días después) tengo una fecha confirmada para ese gasto, en cuyo caso con la estrategia 2 no tendré esa opción.

¿Estoy haciendo todo mal en el diseño de la base de datos? :RE

Bart Calixto
fuente

Respuestas:

10

Una versión de la respuesta de JD Schmidt, pero sin la incomodidad de una columna adicional:

CREATE TABLE foo (
  FieldA INT,
  FieldB INT
);

DELIMITER //
CREATE TRIGGER InsertFieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
  IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
  END IF;
END//
CREATE TRIGGER UpdateFieldABNotNull BEFORE UPDATE ON foo
FOR EACH ROW BEGIN
  IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
    SIGNAL SQLSTATE '45000'
    SET MESSAGE_TEXT = '\'FieldA\' and \'FieldB\' cannot both be null';
  END IF;
END//
DELIMITER ;

INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
UPDATE foo SET FieldA = NULL; -- gives error
Michael Platings
fuente
2

Si está utilizando SQL Server, puede evitar el uso de un desencadenador mediante el uso de una columna calculada persistente en su tabla:

CREATE TABLE Test_Constraint
(
    A DateTime Null,
    B DateTime Null,
    A_and_B AS (CASE WHEN A IS Null AND B IS Null THEN Null ELSE Convert(Binary(1), 1) END) PERSISTED Not Null 
);

La declaración de caso en la columna calculada A_y_B devolverá un valor nulo si ambas columnas A y B son nulas, pero la restricción No nulo en la columna calculada generaría un error que impide la inserción. De lo contrario, devuelve un 1.

Como la columna calculada persiste, se almacenará físicamente en la tabla. La conversión a binario minimiza el impacto de esto, haciendo que la columna sea un tipo de datos binarios de longitud 1.

Shane Estelle
fuente
1
En SQL-Server, también puede hacer esto con una CHECKrestricción. No hay necesidad de columna persistente.
ypercubeᵀᴹ
1
Impresionante, eso parece un poco más limpio. CREATE TABLE Test_Constraint2 ( A DateTime Null, B DateTime Null, CONSTRAINT A_or_B_Not_Null CHECK (CASE WHEN A IS Null AND B IS Null THEN 0 ELSE 1 END = 1) )
Shane Estelle
¡gran respuesta! Este es más apropiado que el otro, pero como estoy usando MySQL, CHECK CLAUSE se analiza pero se ignora en MySQL, así que marco la otra respuesta como la aceptada. +1
Bart Calixto
1

Encontré un artículo que se parece a lo mismo aquí

CREATE TABLE foo (
  FieldA INT,
  FieldB INT,
  FieldA_or_FieldB TINYINT NOT NULL;
);

DELIMITER //
CREATE TRIGGER FieldABNotNull BEFORE INSERT ON foo
FOR EACH ROW BEGIN
  IF (NEW.FieldA IS NULL AND NEW.FieldB IS NULL) THEN
    SET NEW.FieldA_or_FieldB = NULL;
  ELSE
    SET NEW.FieldA_or_FieldB = 1;
  END IF;
END//
DELIMITER ;

INSERT INTO foo (FieldA, FieldB) VALUES (NULL, 10); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (10, NULL); -- OK
INSERT INTO foo (FieldA, FieldB) VALUES (NULL, NULL); -- gives error
JD Schmidt
fuente
gracias, estoy usando MySQL y esto funciona perfectamente bien con él. especialmente porque arroja un valor nulo en un error de columna no nulo.
Bart Calixto