【问题标题】:sqlalchemy FetchedValue and primary_keysqlalchemy FetchedValue 和 primary_key
【发布时间】:2012-10-22 12:04:39
【问题描述】:

我正在尝试创建一个使用 UUID_SHORT() 作为主键的表。我有一个触发器,可以在您进行插入时插入一个值。我无法让 sqlalchemy 将列识别为 primary_key 而不抱怨不提供默认值。如果我确实包含了一个默认值,即使在刷新之后它也会使用该默认值,尽管声明了server_default=FetchedValue()。我似乎可以让事情正常工作的唯一方法是列不是主键。

我正在使用 Pyramid、SQLAlchemy ORM 和 MySQL。

这是模型对象:

Base = declarative_base()
class Patient(Base):
    __tablename__ = 'patient'
    patient_id = Column(BigInteger(unsigned=True), server_default=FetchedValue(), primary_key=True, autoincrement=False)
    details = Column(Binary(10000))

在 initializedb.py 我有:

with transaction.manager:
    patient1 = Patient(details = None)
    DBSession.add(patient1)
    DBSession.flush()
    print(patient1.patient_id)

运行 ../bin/initialize_mainserver_db development.ini 给我以下错误:

2012-11-01 20:17:22,168 INFO  [sqlalchemy.engine.base.Engine][MainThread] BEGIN (implicit)
2012-11-01 20:17:22,169 INFO  [sqlalchemy.engine.base.Engine][MainThread] INSERT INTO patient (details) VALUES (%(details)s)
2012-11-01 20:17:22,169 INFO  [sqlalchemy.engine.base.Engine][MainThread] {'details': None}
2012-11-01 20:17:22,170 INFO  [sqlalchemy.engine.base.Engine][MainThread] ROLLBACK
Traceback (most recent call last):
  File "/sites/metrics_dev/lib/python3.3/site-packages/sqlalchemy/engine/base.py", line 1691, in _execute_context
    context)
  File "/sites/metrics_dev/lib/python3.3/site-packages/sqlalchemy/engine/default.py", line 333, in do_execute
    cursor.execute(statement, parameters)
  File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 418, in execute
    self._handle_result(self._connection.cmd_query(stmt))
  File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 345, in _handle_result
    self._handle_noresultset(result)
  File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 321, in _handle_noresultset
    self._warnings = self._fetch_warnings()
  File "/sites/metrics_dev/lib/python3.3/site-packages/mysql/connector/cursor.py", line 608, in _fetch_warnings
    raise errors.get_mysql_exception(res[0][1],res[0][2])
mysql.connector.errors.DatabaseError: 1364: Field 'patient_id' doesn't have a default value

使用 mysql 客户端运行手动插入会导致一切正常,因此问题似乎出在 SQLAlchemy 上。

mysql> insert into patient(details) values (null);
Query OK, 1 row affected, 1 warning (0.00 sec)

mysql> select * from patient;
+-------------------+---------+
| patient_id        | details |
+-------------------+---------+
| 94732327996882980 | NULL    |
+-------------------+---------+
1 row in set (0.00 sec)

mysql> show triggers;
+-----------------------+--------+---------+-------------------------------------+--------+---------+----------+----------------+----------------------+----------------------+--------------------+
| Trigger               | Event  | Table   | Statement                           | Timing | Created | sql_mode | Definer        | character_set_client | collation_connection | Database Collation |
+-----------------------+--------+---------+-------------------------------------+--------+---------+----------+----------------+----------------------+----------------------+--------------------+
| before_insert_patient | INSERT | patient | SET new.`patient_id` = UUID_SHORT() | BEFORE | NULL    |          | root@localhost | utf8                 | utf8_general_ci      | latin1_swedish_ci  |
+-----------------------+--------+---------+-------------------------------------+--------+---------+----------+----------------+----------------------+----------------------+--------------------+
1 row in set (0.00 sec)

【问题讨论】:

  • 我已经删除了我的答案,因为它显然是垃圾。对于 MySQL,如您所建议的,基于触发器的解决方案将是唯一可行的解​​决方案。如果在此之前没有人给出答案,我稍后会为您提供一个。您没有机会切换到“适当的”DBMS(即 PostgreSQL)吗? :)
  • 感谢您的努力,不过...... :) 当有人尝试帮助时,我很感激。

标签: mysql python-3.x sqlalchemy pyramid


【解决方案1】:

这是我作为解决方法所做的...

DBSession.execute(
"""CREATE TRIGGER before_insert_patient BEFORE INSERT ON `patient`
    FOR EACH ROW BEGIN
        IF (NEW.patient_id IS NULL OR NEW.patient_id = 0) THEN
            SET NEW.patient_id = UUID_SHORT();
        END IF;
    END""")

在患者类中:

patient_id = Column(BigInteger(unsigned=True), default=text("uuid_short()"), primary_key=True, autoincrement=False, server_default="0")

因此,只有当有人直接访问数据库而不是通过 python 代码访问数据库时,触发器才会执行某些操作。并且希望没有人这样做 patient1 = Patient(patient_id=0, details = None) 因为 SQLAlchemy 将使用“0”值而不是触发器产生的值

【讨论】:

    【解决方案2】:

    为了完整起见,根据您的回答,这里有另外两种可能的解决方案(也可以使用here)。它们比您的解决方案稍微简单一些(省略传递具有正确默认值的参数)并使用 SQLAlchemy 构造来定义触发器。

    #!/usr/bin/env python3
    
    from sqlalchemy import BigInteger, Column, create_engine, DDL, event
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import sessionmaker
    from sqlalchemy.schema import FetchedValue
    from sqlalchemy.sql.expression import func
    
    Base = declarative_base()
    
    class PatientOutputMixin(object):
        '''
        Mixin to output human readable representations of models.
        '''
        def __str__(self):
            return '{}'.format(self.patient_id)
    
        def __repr__(self):
            return str(self)
    
    
    class Patient1(Base, PatientOutputMixin):
        '''
        First version of ``Patient`` model.
        '''
        __tablename__ = 'patient_1'
    
        patient_id = Column(BigInteger, primary_key=True,
                            default=func.uuid_short())
    
    # the following trigger is only required if columns are inserted in the table
    # not using the above model/table definition, otherwise it is redundant
    create_before_insert_trigger = DDL('''
    CREATE TRIGGER before_insert_%(table)s BEFORE INSERT ON %(table)s
    FOR EACH ROW BEGIN
        IF NEW.patient_id IS NULL THEN
            SET NEW.patient_id = UUID_SHORT();
        END IF;
    END
    ''')
    
    event.listen(Patient1.__table__, 'after_create',
                 create_before_insert_trigger.execute_if(dialect='mysql'))
    # end of optional trigger definition
    
    
    class Patient2(Base, PatientOutputMixin):
        '''
        Second version of ``Patient`` model.
        '''
        __tablename__ = 'patient_2'
    
        patient_id = Column(BigInteger, primary_key=True,
                            default=0, server_default=FetchedValue())
    
    create_before_insert_trigger = DDL('''
    CREATE TRIGGER before_insert_%(table)s BEFORE INSERT ON %(table)s
    FOR EACH ROW BEGIN
        SET NEW.patient_id = UUID_SHORT();
    END
    ''')
    
    event.listen(Patient2.__table__, 'after_create',
                 create_before_insert_trigger.execute_if(dialect='mysql'))
    
    
    # test models
    
    engine = create_engine('mysql+oursql://test:test@localhost/test?charset=utf8')
    
    Base.metadata.bind = engine
    Base.metadata.drop_all()
    Base.metadata.create_all()
    
    Session = sessionmaker(bind=engine)
    
    session = Session()
    
    for patient_model in [Patient1, Patient2]:
        session.add(patient_model())
        session.add(patient_model())
        session.commit()
    
        print('{} instances: {}'.format(patient_model.__name__,
                                        session.query(patient_model).all()))
    

    运行上述脚本会产生以下(示例)输出:

    Patient1 instances: [22681783426351145, 22681783426351146]
    Patient2 instances: [22681783426351147, 22681783426351148]
    

    【讨论】:

    • 根据您的完全修改答案。可能会给你一两个改进的想法。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-02-15
    • 2012-07-16
    • 1970-01-01
    • 2020-11-10
    • 1970-01-01
    • 1970-01-01
    • 2015-03-25
    相关资源
    最近更新 更多