postgresql SQLAlchemy:是否有任何约束检查两列中的一列是否为空?

velaa5lx  于 2022-11-04  发布在  PostgreSQL
关注(0)|答案(5)|浏览(150)

这可能是完全愚蠢的事情,但我有这样一个要求,在我的模型中,至少有一个categoryparent_categorynot null
我的模型看起来像

class BudgetCategories(db.Model):
    __tablename__ = 'budget_categories'
    uuid = Column('uuid', GUID(), default=uuid.uuid4, primary_key=True,
                  unique=True)
    budget_id = Column(GUID(), ForeignKey('budgets.uuid'), nullable=False)
    budget = relationship('Budget', backref='budgetCategories')
    category = Column('category', sa.types.String, nullable=True)
    parent_category = Column('parent_category', sa.types.String, nullable=True)
    amount = Column('amount', Numeric(10, 2), nullable=False)
    recurring = Column('recurring', sa.types.Boolean,
                       nullable=False)
    created_on = Column('created_on', sa.types.DateTime(timezone=True),
                        nullable=False)

我怎么能说清楚呢。我都不知道该怎么做
感谢您的指点
我使用PostgreSQL作为后端数据库

cwtwac6a

cwtwac6a1#

我不是100%确定PostgreSQL的语法,但是在BudgetCategories模型中添加以下内容应该可以使用CheckConstraint

class BudgetCategories(Base):
    __tablename__ = 'budget_categories'
    # ...

    # @note: new
    __table_args__ = (
            CheckConstraint('NOT(category IS NULL AND parent_category IS NULL)'),
            )
t0ybt7op

t0ybt7op2#

我在SQLAlchemy模型中需要XOR行为。我提出了以下定义(使用后端:PostgreSQL):

from sqlalchemy.schema import (
    CheckConstraint
)

class ScheduledNotebook(Base):
    __table_args__ = (
        (CheckConstraint('(uuid::text IS NULL) <> (notebook_path IS NULL)', name='uuid_xor_notebook_path')),
    )

    id = Column(Integer, primary_key=True)
    notebook_path = Column(String, nullable=True, unique=True)
    uuid = Column(UUID(as_uuid=True), primary_key=True, unique=True, nullable=True)

和随后的Alembic迁移(注意:自动生成不会检测到它-您必须手动添加它):

def upgrade():
    op.create_check_constraint(
        'uuid_xor_notebook_path',
        table_name='scheduled_notebooks',
        schema='metadata',
        condition='(uuid::text IS NULL) <> (notebook_path IS NULL)'
    )

def downgrade():
    op.drop_constraint('uuid_xor_notebook_path')

它的作用就像一个魔咒:

-仅笔记本路径-确定

datalake=#  INSERT INTO scheduled_notebooks (schedule,enabled,owner, notebook_path) VALUES ('{"kind":"hourly"}',true,'akos', '/a/b/c/d/e.ipynb');
INSERT 0 1

-仅uuid -确定

datalake=#  INSERT INTO scheduled_notebooks (schedule,enabled,owner, uuid) VALUES ('{"kind":"hourly"}',true,'akos', '7792bd5f-5819-45bf-8902-8cf43102434d');
INSERT 0 1

-uuid和notebook_path -依需要失败

datalake=#  INSERT INTO scheduled_notebooks (schedule,enabled,owner, uuid, notebook_path) VALUES ('{"kind":"hourly"}',true,'akos', '7792bd5f-5819-45bf-8902-8cf43102434f', '/a/b/c/d');
ERROR:  new row for relation "scheduled_notebooks" violates check constraint "uuid_xor_notebook_path"
DETAIL:  Failing row contains (567, /a/b/c/d, {"kind": "hourly"}, t, akos, null, null, null, 7792bd5f-5819-45bf-8902-8cf43102434f).

-既不是uuid也不是notebook_path -按要求失败

datalake=#  INSERT INTO scheduled_notebooks (schedule,enabled,owner) VALUES ('{"kind":"hourly"}',true,'akos');
ERROR:  new row for relation "scheduled_notebooks" violates check constraint "uuid_xor_notebook_path"
DETAIL:  Failing row contains (568, null, {"kind": "hourly"}, t, akos, null, null, null, null).
sh7euo9m

sh7euo9m3#

我希望还不算太晚,但这应该可以了,它被检查为与PostGreSQL DB一起工作:

class BudgetCategories(Base):
    __tablename__ = 'budget_categories'
    __table_args__ = (
        CheckConstraint('coalesce(category , parent_category ) is not null'),
    )
    # ...
bxpogfeg

bxpogfeg4#

当使用PostgreSQL后端时,您也可以使用num_nonnulls函数来实现此目的:

class BudgetCategories(Base):
    __tablename__ = 'budget_categories'
    __table_args__ = (
        CheckConstraint('num_nonnulls(category, parent_category) = 1'),
    )
jdgnovmf

jdgnovmf5#

几周前我也遇到了这个问题,并在这里提供了解决方案。所以我猜翻译成sqlalchemy(还没有测试!!):

from sqlalchemy.schema import CheckConstraint

class MyClass(Base):
    __table_args__ = (
        (CheckConstraint(
            'num_nulls(nullif(trim(col_a::text), ''), nullif(trim(col_b::text), '')) = 1', 
            name='uuid_xor_notebook_path')),
    )
    id = Column(Integer, primary_key=True)
    col_a = Column(String, nullable=True, unique=True)
    col_b = Column(String, nullable=True, unique=True)

然而出于美观的原因,我更喜欢@andilabs解决方案。为了仍然考虑空字段,我们可以使用另一个solution from here并编写:

from sqlalchemy.schema import CheckConstraint

class MyClass(Base):
    __table_args__ = (
        (CheckConstraint(
            '(col_a::text IS NULL) <> (col_b IS NULL)', 
            name='uuid_xor_notebook_path')),
    )
    id = Column(Integer, primary_key=True)
    col_a = Column(String, nullable=True, unique=True)
    col_b = Column(String, nullable=True, unique=True)

    @validates('col_a', 'col_b')
    def empty_string_to_null(self, key, value):
        if isinstance(value, str) and value == '':
            return None
        else:
            return value

不是所有片段中最短的,我承认...

相关问题