【问题标题】:Creating triggers with custom SQL in Django在 Django 中使用自定义 SQL 创建触发器
【发布时间】:2013-07-01 20:45:22
【问题描述】:

我正在尝试在我的 Django 应用程序中创建一个 MySQL 触发器。我尝试按照 http://bryanmarty.com/blog/2012/12/03/mysql-triggers-django/ 的示例进行操作,但是当我的自定义 SQL 运行时,我没有看到在数据库中创建的触发器(使用 show triggers;)。

我的模型名为 Process,我还有另一个模型名为 Machine。这就是我的“自定义”SQL 的样子:

delimiter |
CREATE TRIGGER processes_running_count
AFTER INSERT ON jobs_process
FOR EACH ROW 
BEGIN
    UPDATE jobs_machine SET processes_running=processes_running+1 limit 1; --
END;
|
delimiter ;

运行 python manage.py syncdb 后,我可以看到存在相应的表(jobs_process、jobs_machine 等),但不存在触发器。如果我将自定义 SQL 中的代码复制/粘贴到 MySQL 提示符中,则会创建触发器(因此我认为问题不是无效的 MySQL 语法)。

有什么建议吗?

【问题讨论】:

    标签: mysql django django-models


    【解决方案1】:

    在 Django 中没有规范的方法可以做到这一点,因为它有点打破了 separation of concerns 关于 Model-View-Whatever 范式(将业务逻辑与持久性混合)。在 Django 中,对于大多数需要考虑触发器的情况,您可以覆盖 Model.save() 和/或 Model.delete() 方法(另请查看 signals)。

    在这种情况下,触发器是多余的——为什么要存储一个可以计算的值?只需执行Process.objects.filter(active=True).count() 之类的操作即可。

    “我们应该忘记小的效率,比如大约 97% 的时间:过早的优化是万恶之源”- Knuth,Donald(1974 年 12 月)。 “使用 go to 语句进行结构化编程”

    也就是说,您可能担心关系数据库是状态轮询的昂贵选择,因为ACID 合约所带来的开销 - 数百个客户端敲击jobs_machine 表对于大多数服务器来说可能会有压力。 redismemcached 等轻量级键/值存储是此类任务的流行选择(将它们视为巨大的共享字典)。

    【讨论】:

      【解决方案2】:

      这里是关于 SQL 语句中的多重分隔符https://stackoverflow.com/a/52292690/9521312的信息

      在您的情况下,您可以使用我的脚本(按原样不完美),它将带有自定义 DELIMITER 的 MySQL 语句转换为原始 SQL 语句。

      在迁移文件中添加脚本执行

      有两种使用脚本的例子:运行 sql 文件或运行原始 MySQL 语句

      from anywhere import migrate_run_sql
      
      operations = [
                    migrations.RunPython(migrate_run_sql.run_sql_file('contract_triggers.sql')),
                    migrations.RunPython(migrate_run_sql.run_sql(
                                                                 """
                                                                 DELIMITER $$
                                                                 CREATE TRIGGER trigger_name BEFORE INSERT ON table
                                                                 FOR EACH ROW
                                                                 BEGIN
                                                                   IF NEW.number <> 'anynumber' AND NEW.number <> 'anynumber'
                                                                     THEN
                                                                       SET NEW.number = 'anynumber';
                                                                   END IF;
                                                                 END$$
                                                                 """
                                                                 ))
                   ]
      

      脚本文件

      # -*- coding: utf-8 -*-
      from django.db import connection
      import re
      from StringIO import StringIO
      from django.conf import settings
      import os
      
      # this function get raw MySQL statement
      def run_sql(sql): 
          def load_data_from_sql(app, schema_editor):
              f = StringIO(sql)
              return _runsql(f)
      
          return load_data_from_sql
      
      # this function get sql file
      def run_sql_file(filename):
          def load_data_from_sql(app, schema_editor):
              filepath = os.path.join(settings.PROJECT_PATH, '../deploy/mysql/', filename)
              with open(filepath, 'rb') as f:
                  return _runsql(f)
      
          return load_data_from_sql
      
      # in this function content splits and checks line by line
      def _runsql(f):
          with connection.cursor() as c:
              file_data = f.readlines()
              statement = ''
              delimiter = ';\n'
              for line in file_data:
                  if re.findall('DELIMITER', line): # found delimiter
                      if re.findall('^\s*DELIMITER\s+(\S+)\s*$', line):
                          delimiter = re.findall('^\s*DELIMITER\s+(\S+)\s*$', line)[0] + '\n'
                          continue
                      else:
                          raise SyntaxError('Your usage of DELIMITER is not correct, go and fix it!')
                  statement += line // add lines while not met lines with current delimiter
                  if line.endswith(delimiter):
                      if delimiter != ';\n':
                          statement = statement.replace(';', '; --').replace(delimiter, ';') # found delimiter, add dash symbols (or any symbols you want) for converting MySQL statements with multiply delimiters in SQL statement
                      c.execute(statement) # execute current statement
                      statement = '' # begin collect next statement
      

      希望对您有所帮助!

      【讨论】:

        猜你喜欢
        • 2017-12-20
        • 1970-01-01
        • 1970-01-01
        • 2013-11-05
        • 1970-01-01
        • 2014-02-02
        • 2011-03-22
        • 2015-06-27
        • 1970-01-01
        相关资源
        最近更新 更多