【问题标题】:Django: Using an F expression for a text field in an update callDjango:在更新调用中对文本字段使用 F 表达式
【发布时间】:2011-12-29 21:52:34
【问题描述】:

在 django 视图中,我需要将字符串数据附加到数据库中现有文本列的末尾。例如,假设我有一个名为“ATable”的表,它有一个名为“aField”的字段。我希望能够以无竞争条件的方式将字符串附加到“aField”的末尾。最初,我有这个:

tableEntry = ATable.objects.get(id=100)
tableEntry.aField += aStringVar
tableEntry.save()

问题是,如果这是同时执行的,两者都可以获得相同的“tableEntry”,然后它们各自独立更新,最后一个“保存”的获胜,丢失另一个附加的数据。

我稍微研究了一下,发现了这个,我希望它可以工作,使用 F 表达式:

ATable.objects.filter(id=100).update(aField=F('aField') + aStringVar)

这里的问题是我得到一个 SQL 错误,说:

operator does not exist: text + unknown
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.

尝试更改为“str(aStringVar)”,即使它已经是一个字符串 - 不走运。我发现一些 django 错误报告抱怨类似问题,但我没有看到修复或解决方法。有什么方法可以强制转换 aStringVar 以便将其附加到 F 表达式的文本中?顺便说一句 - 还尝试了“str(F('aField')) + aStringVar”,但将 F 表达式的结果转换为字符串“(DEFAULT: )”。

【问题讨论】:

  • 你能看到 Django 尝试执行的 sqlquery 吗?
  • 我认为丹尼尔对另一个问题的解释非常明确。作为替代方案,您可以写 custom sql 来执行更新。
  • 我搜索了 SO 和一般的网络,但从未遇到过这个答案 - 感谢您指出它。我目前的解决方法是在 django 中使用带有光标的原始 SQL,但它非常丑陋和将我们与我宁愿避免的 postgres 联系起来。
  • 所以我做了原始的事情,这让我到现在为止,但我正在使用多处理并且有两个线程显然在争夺数据库连接??我收到一条错误消息,提示“必须在任何查询之前设置 SET TRANSACTION ISOLATION LEVEL”。我在这里达到了我的数据库知识限制。 SO上的一个问题似乎表明我不能这样做,但这似乎很难相信,因为我希望通过网络连接,它完全有可能有几个线程同时接触数据库。

标签: django


【解决方案1】:

您可以使用Concat db 函数。

from django.db.models import Value
from django.db.models.functions import Concat

ATable.objects.filter(id=100).update(some_field=Concat('some_field', Value('more string')))

在我的例子中,我正在为 facebook 头像 URI 添加一个后缀,如下所示:

FACEBOOK_URI = 'graph.facebook.com'
FACEBOOK_LARGE = '?type=large'
# ...
users = User.objects.filter(Q(avatar_uri__icontains=FACEBOOK_URI) & ~Q(avatar_uri__icontains=FACEBOOK_LARGE))
users.update(avatar_uri=Concat('avatar_uri', Value(FACEBOOK_LARGE)))

我得到这样的 SQL (Django 1.9):

UPDATE `user_user` SET `avatar_uri` = CONCAT(COALESCE(`user_user`.`avatar_uri`, ''), COALESCE('?type=large', ''))
WHERE (`user_user`.`avatar_uri` LIKE '%graph.facebook.com%' AND NOT (`user_user`.`avatar_uri` LIKE '%?type=large%' AND `user_user`.`avatar_uri` IS NOT NULL))

结果是所有图像 URI 都从 http://graph.facebook.com/<fb user id>/picture 更改为 http://graph.facebook.com/<fb user id>/picture?type=large

【讨论】:

  • 不理想的可移植性:“但是,SQLite 不支持 CONCAT() 函数”。有时 sqlite 很方便,例如用于测试。
【解决方案2】:

您可以通过一个简单的更改覆盖 Django 中的 F 对象:

class CF(F):
    ADD = '||'

然后只需使用CF 代替F。它将放置“||”生成 SQL 时,而不是“+”。例如查询:

User.objects.filter(pk=100).update(email=CF('username') + '@gmail.com')

将生成 SQL:

UPDATE "auth_user" SET "email" = "auth_user"."username" || '@gmail.com'
WHERE "auth_user"."id" = 100 

【讨论】:

  • 只是为了完成这个答案(为我工作!谢谢!)你还应该设置sql_mode='PIPES_AS_CONCAT';如果你不这样做,我的 MySQL 会考虑“||”作为逻辑或
  • 好点!我在 PostgreSQL 上进行测试。事实上,|| 并不是标准的 SQL 运算符。标准方法是使用CONCAT() 函数,但它不适合此解决方案...
  • 查看下面的答案,了解使用 Concat 的便携式解决方案!
【解决方案3】:

如果你让它运行,它就不是线程安全的。当您的更新正在运行时,其他一些进程可以在不知道数据库中的数据已更新的情况下更新模型。

你也获得了一个锁,但不要忘记这个场景:

  1. Django:m = Model.objects.all()[10]
  2. Django:m.field = 字段
  3. Django:需要一段时间的进度 (time.sleep(100))
  4. 数据库:锁定表
  5. 数据库:更新字段
  6. DD:解锁表
  7. Django:慢进程结束
  8. Django:m.save()

现在字段更新已被 Django 中的模型实例撤消(Ghost write)

【讨论】:

  • 非常有趣的一点(但与问题无关)。您是否有一些资源讨论如何使用 Django 处理这种情况?
【解决方案4】:

您可以使用 Django 的 select_for_update() 运算符实现此功能:https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update

类似这样的:

obj = ATable.objects.select_for_update().get(id=100)
obj.aField = obj.aField + aStringVar
obj.save()

调用.select_for_update().get()会锁定表行,调用.save()会释放锁,让你原子地执行操作。

【讨论】:

    【解决方案5】:

    看来你不能这样做。但是,您可以使用 transactions

    解决您想要做的事情

    (看起来您正在使用 postgres,因此如果您想在一个查询中执行此操作并按照建议使用原始 sql,|| 是您想要的连接运算符)

    【讨论】:

      猜你喜欢
      • 2014-02-21
      • 2012-05-23
      • 1970-01-01
      • 2017-07-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多