【问题标题】:Create model for MySQL view and join on it为 MySQL 视图创建模型并加入它
【发布时间】:2016-04-15 10:26:03
【问题描述】:

我正在做一些原型设计,并有一个像这样的简单模型

class SampleModel(models.Model):
    user_id = models.IntegerField(default=0, db_index=True)
    staff_id = models.IntegerField(default=0, db_index=True)
    timestamp = models.DateTimeField(default=timezone.now, db_index=True)

    objects = AsOfManager()

现在我们需要执行需要自连接的查询,用原始 SQL 编写的查询是这样的:

    SELECT X.* FROM no_chain_samplemodel as X
    JOIN (SELECT user_id, MAX(timestamp) AS timestamp
         FROM no_chain_samplemodel
         GROUP BY user_id) AS Y
    ON (X.user_id = Y.user_id and X.timestamp = Y.timestamp);

此查询应为每个 user_id 返回按时间戳排序的最后一行。这个“链”中的每一个(与 user_id 相关的行)都可能有数千行。

现在我可以使用原始 SQL,但我失去了可组合性,我想返回另一个查询集。 同时也可以让编写原始 SQL 更容易,所以我想我可以使用数据库视图。

视图可能是这样的

CREATE VIEW no_chain_sample_model_with_max_date AS SELECT user_id AS id, MAX(timestamp) AS timestamp
                        FROM no_chain_samplemodel
                        GROUP BY user_id;

所以引用视图的模型可以简单地像这样:

class SampleModelWithMaxDate(models.Model):
    class Meta:
        managed = False
        db_table = 'no_chain_sample_model_with_max_date'

    id = models.IntegerField(default=0, primary_key=True)
    timestamp = models.DateTimeField(default=timezone.now, db_index=True)

但是有几个问题:

  1. 即使 managed = False './manage.py makemigrations' 仍然会为此表创建迁移。 我什至试图将迁移留在那里,但用原始 SQL 替换模型以创建视图 但没有运气。

  2. 我现在需要做 select_related 来连接两个表和查询,但是我应该怎么做呢?

    我在 SampleModel 上尝试了这样的外键:

    by_date = models.ForeignKey(SampleModelWithMaxDate, null=True)

    但这也行不通:

    OperationalError: (1054, "Unknown column 'no_chain_sample_model_with_max_date.by_date_id' in 'field list'")

所以总的来说,我什至不确定这是否可能,我可以看到其他人正在使用带有视图的模型,并且只是为了查询对我也有用的独立模型,但有没有可能做比这更聪明的事情?

谢谢

【问题讨论】:

  • 从您的问题中仍然不清楚您需要什么?给定顶部的 django 模型,您能否提供一个示例?
  • 我只需要使用 Django ORM 翻译那个 SQL 查询。因此,如果没有指向自身的外键,在同一张表上加入似乎基本上是不可能的,我认为一个视图和另一个模型可以解决这个问题。但是我还没有真正看到有人使用基于数据库视图的数据库模型的任何示例,所以我想知道这是否可能。
  • 没关系,但您期望的最终结果是什么。您想获取每个user_id 和最新timestamp 的所有SampleModel 实例吗?
  • 是的,你是对的,没有很清楚的解释谢谢
  • 请检查我的回答。

标签: python mysql django orm


【解决方案1】:

我找不到任何 ORM 方法来在一个查询中得到你想要的,但我们可以用两个查询来做到这一点:

首先,我们得到所有用户的最大timestamp

latest_timestamps = SampleModel.objects.values('user_id')
                        .annotate(max_ts=Max('timestamp')).values('max_ts')

这里values(user_id) 相当于group by 操作。

现在,我们得到了 SampleModel 的所有实例以及确切的时间戳

qs = SampleModel.objects.filter(timestamp__in=latest_timestamps)    

PostgreSQL 特定答案:

您可以混合使用 order_bydistinct 来实现您想要的:

SampleModel.objects.order_by('user_id', '-timestamp').distinct('user_id')

分解:

# order by user_id, and in decreasing order of timestamp
qs = SampleModel.objects.order_by('user_id', '-timestamp')

# get distinct rows using user_id, this will make sure that the first entry for 
# each user is retained and since we further ordered in decreasing order of
# timestamp for each user the first entry will have last row added 
# for the user in the database.
qs = qs.distinct('user_id')  

【讨论】:

  • 我想我试过了,但是 MySQL 不支持“DISTINCT ON”,所以我并没有真正设法做到这一点.. NotImplementedError: DISTINCT ON fields is not supported by this database backend In [11] :在[11]中:models.SampleModel.objects.order_by('user_id', '-timestamp').distinct('user_id').count()
  • 哦,是的,distinct 仅适用于 postgres。让我看看能不能找到mysql的东西。
  • @andrea_crotti 我试图在不使用distinct 的情况下回答您的问题。请检查更新的答案。
  • 这个答案确实假设没有两个 timestamp 值对于 user_id 是相同的。
  • 是的,我们实际上可能正在迁移到 Postgres,这让一切变得更好,谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-31
  • 2016-10-28
  • 1970-01-01
  • 2013-09-28
  • 1970-01-01
  • 2020-06-10
  • 1970-01-01
相关资源
最近更新 更多