【问题标题】:Running a .sql file after migrations in django在 django 中迁移后运行 .sql 文件
【发布时间】:2017-05-22 14:03:51
【问题描述】:

我已经在 django 中设置了我的迁移(非常小的项目);但我有一个 .sql 文件,其中包含我在数据库中需要的大量数据。

在运行迁移之后/期间执行此 .sql 文件的最佳方式是什么(或者甚至可能)?

sql 文件只包含插入的数据,如下所示:

INSERT INTO `mileages_mileages` (`id`, `miles`, `start_location`, `end_location`) VALUES
(NULL,3, 'Location 1', 'Location 2'),

我只需要在运行模型的初始迁移后执行那个 .sql 文件。

这可能吗?

【问题讨论】:

  • 您可以使用RunSQL 在迁移中运行sql。

标签: python django django-models


【解决方案1】:
migrations.RunSQL()

不接受文件作为输入。只有原始 SQL。要解决这个问题,您需要使用:

migrations.RunSQL(
    """
    INSERT INTO 'mileages_mileages' ('id', 'miles', 'start_location', 'end_location') 
    VALUES
       (NULL,3, 'Location 1', 'Location 2');
    """)

def load_data_from_sql(apps, schema_editor):
   file_path = os.path.join(os.path.dirname(__file__), 'file_name.sql')
   sql_statement = open(file_path).read()
   with connection.cursor() as c:
       c.execute(sql_statement)

class Migration(migrations.Migration):
    dependencies = [
        ('..', '...'),
    ]

    operations = [
        migrations.RunPython(load_data_from_sql),
    ]

【讨论】:

  • open(file_path).read() 使文件保持打开状态,最好使用from pathlib import Path; Path(file_path).read_text()
  • 如果文件在包中,请考虑使用sql_statement = pkg_resources.resource_string('my.pkg', 'file_name.sql')
  • 还要注意,sql文件实际上可能包含多条语句,甚至可能创建存储过程;但是不应该使用delimiter // 左右,这仅在使用mysql 命令时才需要并且不起作用,只需使用END; 结束例程即可。
  • 使用这种方法将多个存储例程添加到 MySQL 我遇到了ProgrammingError:Commands out of sync。这是通过使用statements = connection.ops.prepare_sql_script(sql_file_content); with connection.schema_editor() as schema_editor: for statement in statements: schema_editor.execute(statement) 而不是使用游标解决的。注意:我对多条语句都支持,因为这是“迁移后 django 运行 sql 文件”的第一个谷歌命中。
  • 另外,不确定是否使用全局连接对象中的游标将语句与迁移放在同一个事务中(每个迁移都在一个事务中隔离)。 schema_editor 有一个可能更合适的 .execute 方法。
【解决方案2】:

阅读更多内容后 - 运行 SQL 以在迁移中加载数据是错误的做法。使用fixtures 会好很多。

我已经添加了一个夹具,并将使用该文件来引入数据。

首先,要创建夹具,我必须将数据添加到表中(我只是手动运行 SQL 文件以将其添加到相应的表中)。

然后我跑了

manage.py dumpdata mileage.mileage --indent 2 --format=json --output initial_mileages.json

这创建了我的固定装置,然后我加载了它

manage.py loaddata initial_mileages.json

而且它导入得很漂亮。

【讨论】:

  • 值得注意的是,Django 文档不同意。他们说:“如果您想为应用程序自动加载初始数据,请不要使用固定装置。相反,使用 RunPython 或 RunSQL 操作为您的应用程序创建迁移。” docs.djangoproject.com/en/1.11/howto/initial-data - 我不知道我自己完全不同意 - 固定装置看起来不错,但 SQL 不是 错误 方式。
  • 加载夹具是一个额外的步骤,可以忘记。另外,在创建测试数据库时,不会加载夹具,而会加载迁移。
  • loaddata 可能只适用于 INSERT,如果需要任何 UPDATE,请使用其他方式。
【解决方案3】:

使用migrations.RunSQL时,如果出现如下错误:

如果不拆分 SQL,则需要 sqlparse

你只需要安装sqlparse即可解决问题:

pip install sqlparse

【讨论】:

  • 你能解释一下如何使用 sqlparse 在 django 上下文中运行 sql insert 吗?
  • @MichaelScottCuthbert 我认为这个包只是帮助 django 解析、拆分和格式化 SQL 脚本。对我来说,要执行插入操作,我会选择 django 模型来处理它。您可以运行 django shell:python manage.py shell,然后导入您的 django 模型,然后插入您的数据,例如:YourModel.objects.create(field1=value1, field2=value2)
  • 或者传递一个字符串列表,而不仅仅是一个字符串。
【解决方案4】:

这里是完整的脚本(不完美,但原样),用于将带 DELIMITER 的 MySQL 语句转换为 SQL 执行。

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

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

有两种使用脚本的例子:运行 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

希望对您有所帮助!

【讨论】:

  • 只是一个问题:“任何地方”在哪里?你的回答很翔实,但在导入过程中这隐含使得申请像我这样的菜鸟很乏味
  • @DavidZarebski 抱歉隐含的“任何地方”。您应该在 django 应用程序的任何文件夹中使用我的答案中的脚本文件内容创建“migrate_run_sql.py”,并将其导入迁移文件中。例如,我在 django 应用程序中使用文件夹“helpers”。祝你好运!
  • 您可能需要更新您的示例,因为您在// add lines while not met lines with current delimiter 中使用了错误的注释字符此外,对于使用 Python3 看到此内容的任何人,StringIO 不在 io 模块中 E.G from io import StringIO
  • @SteveMapes 确实!谢谢,该死的javascript在另一个窗口:)
【解决方案5】:

您可以执行原始 SQL

operations = [
    migrations.RunSQL('sql statement here')
]

【讨论】:

  • 当我这样做时,我收到一个错误:“如果不拆分 SQL,则需要 sqlparse”。想法?
  • 你需要 sqlparse
  • pip install sqlparse
  • 太棒了!谢谢 - 不胜感激!
  • 请注意,RunSQL 的参数应该是 SQL 语句,而不是文件名。如果 SQL 语句太大而无法合理地放入迁移文件本身,则最好使用 RunPython 操作来加载文件并执行内容。
猜你喜欢
  • 2020-01-02
  • 1970-01-01
  • 2018-05-04
  • 2021-06-27
  • 2018-08-07
  • 2015-05-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-20
相关资源
最近更新 更多