Проверить ограничение для взаимоисключающих столбцов в SQLAlchemy

Если у меня есть декларативная модель SQLAlchemy, как показано ниже:

class Test(Model):
    __tablename__ = 'tests'

    id = Column(Integer, Sequence('test_id_seq'), primary_key=True)
    ...
    Atest_id = Column(Integer, ForeignKey('Atests.id'), nullable=True)
    Btest_id = Column(Integer, ForeignKey('Btests.id'), nullable=True)
    Ctest_id = Column(Integer, ForeignKey('Ctests.id'), nullable=True)
    Dtest_id = Column(Integer, ForeignKey('Dtests.id'), nullable=True)
    Etest_id = Column(Integer, ForeignKey('Etests.id'), nullable=True)
    ...
    date = Column(DateTime)
    status = Column(String(20))  # pass, fail, needs_review

И я хотел бы убедиться, что в данной строке присутствует только один из внешних ключей *test_id, как я могу выполнить это в SQLAlchemy?

Я вижу, что есть объект SQLAlchemy CheckConstraint (см. docs), но MySQL не поддерживает контрольные ограничения.

Модель данных имеет взаимодействие вне SQLAlchemy, поэтому предпочтительнее будет проверка уровня базы данных (MySQL)

Ответ 1

Хорошо, учитывая ваши реквизиты "Модель данных имеет взаимодействие вне SQLAlchemy, поэтому желательно, чтобы это была проверка уровня базы данных (MySQL)" и "убедитесь, что только один [..] не является нулевым". Я думаю, что лучший подход - написать триггер следующим образом:

DELIMITER $$
CREATE TRIGGER check_null_insert BEFORE INSERT 
ON my_table
FOR EACH ROW BEGIN
IF CHAR_LENGTH(CONCAT_WS('', NEW.a-NEW.a, NEW.b-NEW.b, NEW.c-NEW.c)) = 1 THEN 
    UPDATE `Error: Only one value of *test_id must be not null` SET z=0;
END IF;
END$$
DELIMITER ;

Некоторые трюки и соображения:

  • IF STATEMENT. Чтобы избежать утомительной записи проверки, каждый столбец не является нулевым, а другие - нулевым, я сделал этот трюк: уменьшите каждый столбец до одного символа и проверьте, сколько символы существуют. Обратите внимание, что NEW.a-NEW.a всегда возвращает 1 символ, если NEW.a является Integer, NULL возвращает 0 символов, а операция NULL-NULL возвращает NULL в MySQL.

  • ERROR TRIGGERING. Предположим, вы хотите поднять ошибку, и как это сделать в MySQL? Вы не упомянули версию MySQL. Только в MySQL 5.5 вы можете использовать синтаксис SIGNAL для исключения исключений. Поэтому более переносимый способ выдает недопустимый оператор вроде: UPDATE xx SET z=0. Если вы используете MySQL 5.5, вы можете использовать: signal sqlstate '45000' set message_text = 'Error: Only one value of *test_id must be not null'; вместо UPDATE `Error: Only one value of *test_id must be not null` SET z=0;

Кроме того, я думаю, вы тоже хотите проверить это на обновлениях, поэтому используйте:

DELIMITER $$
CREATE TRIGGER check_null_update BEFORE UPDATE 
ON my_table
FOR EACH ROW BEGIN
IF CHAR_LENGTH(CONCAT_WS('', NEW.a-NEW.a, NEW.b-NEW.b, NEW.c-NEW.c)) = 1 THEN 
    UPDATE `Error: Only one value of *test_id must be not null` SET z=0;
END IF;
END$$
DELIMITER ;

Или создайте хранимую процедуру и вызовите ее.

Update

Для баз данных, поддерживающих ограничения проверки, код более прост, см. этот пример для SQL Server:

CREATE TABLE MyTable (col1 INT NULL, col2 INT NULL, col3 INT NULL);
GO

ALTER TABLE MyTable
ADD CONSTRAINT CheckOnlyOneColumnIsNull
CHECK (
  LEN(CONCAT(col1-col1, col2-col2, col3-col3)) = 1
)
GO