【问题标题】:Django .raw query fails (error 1064) -- but works in SQLDjango .raw 查询失败(错误 1064)——但在 SQL 中有效
【发布时间】:2023-03-31 12:55:02
【问题描述】:

(使用 django 1.11.2、python 2.7.10、mysql 5.7.18)

以下 SQL 查询:

SELECT
    transaction_transaction.id,
    sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime))))
    AS average_time_of_day
FROM transaction_transaction
INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id)
INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id)
WHERE (
    transaction_transaction.transaction_datetime BETWEEN '2017-08-31 00:00:00' AND '2017-08-31 23:59:59'
    AND store_store.company_id=2
    AND payment_method_card.profile_id=8
);

运行并返回以下结果(如预期):

+== id ==+== average_time_of_day ==+
|= 9422 =|===== 20:42:22.8695 =====|

(这是从 HeidiSQL 运行的)

通过 Django 做类似的事情(我认为!但显然有问题):

average_time_of_day = Transaction.objects.raw(
    '''
    SELECT 
        transaction_transaction.id, 
        sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime)))) 
        AS average_time_of_day
    FROM transaction_transaction
    INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id) 
    %s
    WHERE (
        transaction_transaction.transaction_datetime BETWEEN %s AND %s 
        AND store_store.company_id=%s
        %s
    );
    ''',
    [
        'INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id) ' if profile_pk else '',
        start_datetime,
        end_datetime,
        company_pk,
        'AND payment_method_card.profile_id=' + str(profile_pk) if profile_pk else '',
    ]
)

在做

print average_time_of_day.query

输出:

SELECT
    transaction_transaction.id,
    sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime))))
    AS average_time_of_day
FROM transaction_transaction
INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id)
INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id)
WHERE (
    transaction_transaction.transaction_datetime BETWEEN 2017-08-31 00:00:00 AND 2017-08-31 00:00:00
    AND store_store.company_id=2
    AND payment_method_card.profile_id=8
);

然后 Django 返回以下错误:

ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_meth' at line 7")

知道我做错了什么吗?

【问题讨论】:

标签: python mysql sql django django-1.11


【解决方案1】:

没错。

这个会教我不要尝试并且:

  1. 太聪明了
  2. 过多使用单行代码 :-(

这不起作用:

average_time_of_day = Transaction.objects.raw(
    '''
    SELECT 
        transaction_transaction.id, 
        sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime)))) 
        AS average_time_of_day
    FROM transaction_transaction
    INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id) 
    %s
    WHERE (
        transaction_transaction.transaction_datetime BETWEEN %s AND %s 
        AND store_store.company_id=%s
        %s
    );
    ''',
    [
        'INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id) ' if profile_pk else '',
        start_datetime,
        end_datetime,
        company_pk,
        'AND payment_method_card.profile_id=' + str(profile_pk) if profile_pk else ''
    ]
)

注意params 中的'INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id) ' if profile_pk else '''AND payment_method_card.profile_id=' + str(profile_pk) if profile_pk else '' 发送到raw() 方法。有点动态?

...但是这个有效:

if profile_pk:
    average_time_of_day = Transaction.objects.raw(
        '''
        SELECT
            transaction_transaction.id,
            sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime))))
            AS average_time_of_day
        FROM transaction_transaction
        INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id)
        INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id)
        WHERE (
            transaction_transaction.transaction_datetime BETWEEN %s AND %s
            AND store_store.company_id=%s
            AND payment_method_card.profile_id=%s
        );
        ''',
        [
            start_datetime,
            end_datetime,
            company_pk,
            profile_pk
        ]
    )
else:
    average_time_of_day = Transaction.objects.raw(
        '''
        SELECT
            transaction_transaction.id,
            sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime))))
            AS average_time_of_day
        FROM transaction_transaction
        INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id)
        WHERE (
            transaction_transaction.transaction_datetime BETWEEN %s AND %s
            AND store_store.company_id=%s
        );
        ''',
        [
            start_datetime,
            end_datetime,
            company_pk
        ]
    )

那么 Django 会不喜欢 params 的动态构建吗?我很高兴知道答案……不过我仍然怀疑我做错了什么……这可能与 SQL 注入保护有关吗?

显然 Django 将 INNER JOIN 表达式包装在单引号中 - 因此导致 MySQL 拒绝查询。实际上看起来是这样的,尽管 print average_time_of_day.query 并没有说什么:

SELECT
    transaction_transaction.id,
    sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime))))
    AS average_time_of_day
FROM transaction_transaction
INNER JOIN store_store ON (transaction_transaction.store_id=store_store.id)
'INNER JOIN payment_method_card ON (transaction_transaction.card_id=payment_method_card.id)'
WHERE (
    transaction_transaction.transaction_datetime BETWEEN 2017-08-31 00:00:00 AND 2017-08-31 23:59:59
    AND store_store.company_id=2
    'AND payment_method_card.profile_id=8'
);

【讨论】:

    【解决方案2】:

    在 SQL 字符串中,某些变量必须是字符串。例如 datetime 必须在 "" 或 '' 我不记得但 queryset.query 显示您格式化的 sql 字符串不是有效的字符串。 所以只需尝试添加''。

    【讨论】:

      【解决方案3】:

      您是否尝试过使用 Django 的 ORM 来构建该查询?它可以使调试变得容易得多。它可能看起来像这样:

      Transaction.objects.filter(
          store__company_id=company_pk,
          payment_method_card__profile_id=profile_pk,
          transaction_datetime__range(start_datetime, end_datetime)
      )
      

      您可以在查询中使用Django's select_related and prefetch_related 优化查询,如果您需要更细粒度的控制,可以使用Q() object

      我不喜欢手动编写 SQL 语句,所以这有点逃避,但我喜欢使用 Django 提供的工具。

      【讨论】:

      • 哦,是的,我试过了。不幸的是,据我所知,django 不支持“sec_to_time(avg(time_to_sec(extract(HOUR_SECOND from transaction_transaction.transaction_datetime))))”位。
      猜你喜欢
      • 2015-01-12
      • 1970-01-01
      • 1970-01-01
      • 2020-01-20
      • 2019-11-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多