Duas colunas anuláveis, uma necessária para ter valor

10

Pergunta sem explicação:

Existe uma maneira de ter uma restrição de 2 valores nulos que sempre exige que 1 tenha valor? Por exemplo, duas colunas de data nulas, mas com pelo menos 1 que requer um valor

Descrição do Problema:

Digamos que tenho uma tabela chamada Despesa

e tem 2 datas:

prevision_expense_expiration_date DATE NULLABLE expens_payment_date DATE NULLABLE

a lógica dessas 2 colunas é a seguinte:

Fiz uma compra de algo e sei que tenho que pagar por isso em algum momento, como uma conta de telefone. Vou inserir isso como uma despesa com um gasto_pagamento_data. Esta data é a data suposta que devo pagar, mas não a data real do pagamento, como a data de vencimento da fatura.

Em outra situação, vendo um cartão-presente de algum provedor por seu serviço. Eu posso ter a despesa de comprar ao meu provedor o serviço transferido para o meu cliente apenas se o cliente resgatar o cartão. Portanto, o cartão-presente tem uma data de validade, quero fazer uma previsão para essa 'despesa' sem inserir como despesa pelo tempo que o cartão-presente é válido, se o cartão-presente expirar, essa 'despesa' não deve entrar na conta sistema.

Eu sei que posso ter 2 tabelas iguais chamadas prevision_expense e confirm_expense, mas não parece certo, por isso tenho na mesma tabela 2 datas anuláveis, mas quero restringir ou algo assim para que seja sempre necessário.

Há outra estratégia possível:

payment_date DATA NÃO NULL is_prevision_date BOOL NOT NULL

Portanto, nesse caso, se a data for o valor boológico de previsão, será 1, caso contrário, será 0. Nenhum valor nulo, tudo está bom. exceto que desejo a opção de armazenar AMBOS os valores quando tiver uma data de previsão e THEN (digamos, dois dias depois) tiver uma data confirmada para essa despesa; nesse caso, com a estratégia 2, não terei essa opção.

Estou fazendo tudo errado no design do banco de dados? : D

Bart Calixto
fonte

Respostas:

10

Uma versão da resposta de JD Schmidt, mas sem o constrangimento de uma coluna extra:

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
fonte
2

Se você estiver usando o SQL Server, poderá evitar o uso de um gatilho usando uma coluna computada persistente na sua tabela:

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 
);

A instrução de caso na coluna computada A_and_B retornará um valor nulo se as colunas A e B forem nulas, mas a restrição Não Nulo na coluna computada geraria um erro impedindo a inserção. Caso contrário, ele retornará 1.

Como a coluna computada é mantida, ela será fisicamente armazenada na tabela. A conversão em binário minimiza o impacto disso, tornando a coluna um tipo de dados binário de comprimento 1.

Shane Estelle
fonte
11
No SQL-Server, você também pode fazer isso com uma CHECKrestrição. Não há necessidade de coluna persistente.
ypercubeᵀᴹ
11
Impressionante, isso parece um pouco mais limpo. 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
Ótima resposta! Este é mais apropriado que o outro, mas como eu estou usando o MySQL, CHECK CLAUSE é analisado, mas ignorado no MySQL, então marque a outra resposta como a aceita. +1
Bart Calixto
1

Encontrei um artigo que se parece com a mesma coisa aqui

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
fonte
obrigado, estou usando o MySQL e isso funciona perfeitamente bem com ele. especialmente por lançar um valor nulo em um erro de coluna não nulo.
Bart Calixto