【问题标题】:Performing a Django raw SQL (using "WHERE col IN" syntax) or translating raw SQL to .raw() or .extra()执行 Django 原始 SQL(使用“WHERE col IN”语法)或将原始 SQL 转换为 .raw() 或 .extra()
【发布时间】:2010-11-23 15:42:58
【问题描述】:

Django 1.3-dev 提供了几种使用原始 SQL 查询数据库的方法。它们被覆盖herehere。推荐的方法是使用.raw().extra() 方法。这样做的好处是,如果检索到的数据符合 Model,您仍然可以直接使用它的某些功能。

我要显示的页面有些复杂,因为它使用了大量信息,这些信息分布在具有不同关系(one2one、one2many)的多个表中。使用当前方法,服务器每页必须执行大约 4K 查询。由于数据库到网络服务器的通信,这显然很慢。

一种可能的解决方案是使用原始 SQL 来检索相关数据,但由于查询的复杂性,我无法将其转换为 Django 中的等价物。

查询是:

SELECT clin.iso as iso,
   (SELECT COUNT(*)
       FROM clin AS a
       LEFT JOIN clin AS b
           ON a.pat_id = b.pat_id
       WHERE a.iso = clin.iso
   ) AS multiple_iso,
   (SELECT COUNT(*)
       FROM samptopat
       WHERE samptopat.iso_id = clin.iso
   ) AS multiple_samp,
   (SELECT GROUP_CONCAT(value ORDER BY snp_id ASC)
       FROM samptopat
       RIGHT JOIN samptosnp
           USING(samp_id)
       WHERE iso_id = clin.iso
       GROUP BY samp_id
       LIMIT 1 -- Return 1st samp only
   ) AS snp
FROM clin
WHERE iso IN (...)

或者WHERE iso = ...

示例输出如下所示:

+-------+--------------+---------------+-------------+
| iso   | multiple_iso | multiple_samp | snp         |
+-------+--------------+---------------+-------------+
|     7 |        19883 |             0 | NULL        |
|     8 |        19883 |             0 | NULL        |
| 21092 |            1 |             2 | G,T,C,G,T,G |
| 31548 |            1 |             0 | NULL        |
+-------+--------------+---------------+-------------+
4 rows in set (0.00 sec)

documentation 解释了如何使用WHERE col = %s 而不是IN 语法进行查询。 这个问题的一部分是如何使用 Django 和 IN 语句执行原始 SQL 查询?

另一部分是,考虑以下模型:

class Clin(models.Model):
    iso = models.IntegerField(primary_key=True)
    pat = models.IntegerField(db_column='pat_id')
    class Meta:
        db_table = u'clin'

class SampToPat(models.Model):
    samptopat_id = models.IntegerField(primary_key=True)
    samp = models.OneToOneField(Samp, db_column='samp_id')
    pat = models.IntegerField(db_column='pat_id')
    iso = models.ForeignKey(Clin, db_column='iso_id')
    class Meta:
        db_table = u'samptopat'

class Samp(models.Model):
    samp_id = models.IntegerField(primary_key=True)
    samp = models.CharField(max_length=8)
    class Meta:
        db_table = u'samp'

class SampToSnp(models.Model):
    samptosnp_id = models.IntegerField(primary_key=True)
    samp = models.ForeignKey(Samp, db_column='samp_id')
    snp = models.IntegerField(db_column='snp_id')
    value = models.CharField(max_length=2)
    class Meta:
        db_table = u'samptosnp'

是否可以将上述查询重写为更面向 ORM 的内容?

【问题讨论】:

  • 首先,我会删除显式主键,django 无论如何都使用整数。
  • 看起来这是指向一个现有的数据库;因此他需要指定主键,因为它们的名称已经不是id。我想知道的是,ClinSampToPat 都引用了pat_id,但我没有看到与Pat 对应的模型。我也没有看到Snp 的模型为了完整性,你不应该包括那些模型吗?我不禁想到,如果我们有可用的 Pat 模型,我们也许能够想出处理其中一些聚合函数的方法。
  • @Jordan,是的,模型存在,但由于我没有在当前查询中直接使用它们,为了简单起见,我删除了这些关系。您能否详细说明为什么相关模型的存在会使聚合函数更易于使用? ID 还不够吗?
  • @Evgeny,正如 Jordan 猜测的那样,这是一个遗留设置,并非所有字段都遵循 Django 约定。
  • 我明白,只是想向自己解释问题:)

标签: sql mysql django django-models query-optimization


【解决方案1】:

对于这样的问题,我会将查询拆分为少量更简单的查询,我认为这很有可能。另外,我发现 MySQL 实际上可以使用这种方法更快地返回结果。

edit ...实际上,经过一番思考,我发现您需要“对子查询进行注释”,这在 Django ORM 中是不可能的(至少在 1.2 中是不可能的)。也许您必须在这里执行普通 sql 或使用其他工具来构建查询。

尝试以更默认的 django 模式重写您的模型,也许这将有助于更好地理解问题。但是模型 Pat 和 Snp 不见了……

class Clin(models.Model):
    pat = models.ForeignKey(Pat)
    class Meta:
        db_table = u'clin'

class SampToPat(models.Model):
    samp = models.ForeignKey(Samp)
    pat = models.ForeignKey(Pat)
    iso = models.ForeignKey(Clin)
    class Meta:
        db_table = u'samptopat'
        unique_together = ['samp', 'pat']

class Samp(models.Model):
    samp = models.CharField(max_length=8)
    snp_set = models.ManyToManyField(Snp, through='SampToSnp')
    pat_set = models.ManyToManyField(Pat, through='SaptToPat')
    class Meta:
        db_table = u'samp'

class SampToSnp(models.Model):
    samp = models.ForeignKey(Samp)
    snp = models.ForeignKey(Snp)
    value = models.CharField(max_length=2)
    class Meta:
        db_table = u'samptosnp'

以下似乎意味着 - 获取每个诊所的独特患者数量...

(SELECT COUNT(*)
   FROM clin AS a
   LEFT JOIN clin AS b
       ON a.pat_id = b.pat_id
   WHERE a.iso = clin.iso
) AS multiple_iso,

每个诊所的样本数:

(SELECT COUNT(*)
   FROM samptopat
   WHERE samptopat.iso_id = clin.iso
) AS multiple_samp,

这部分比较难理解,但是在 Django 中没有办法在纯 ORM 中做 GROUP_CONCAT。

(SELECT GROUP_CONCAT(value ORDER BY snp_id ASC)
   FROM samptopat
   RIGHT JOIN samptosnp
       USING(samp_id)
   WHERE iso_id = clin.iso
   GROUP BY samp_id
   LIMIT 1 -- Return 1st samp only
) AS snp

【讨论】:

  • 在尝试了您的想法一段时间后,我能够找到比最初更好的解决方案。事实证明,大量查询的问题是由 Django 的 ORM 的惰性方面引起的。只需将一些 QuerySet 转换为列表,我就能够将查询数量从 ~4k 减少到 ~120,并从 10 秒减少到 0.4 秒,这已经足够了。最终解决方案是单独检索每个块,并通过在某些位置转换为列表来强制进行批量检索。
【解决方案2】:

您能否准确解释一下您要使用 snp 子查询提取什么?我看到您正在加入这两个表,但看起来您真正想要的是Snp 对象,它具有关联的Clin,它具有给定的ID。如果是这样,这几乎变得像其他 2 个单独的查询一样简单:

Snp.objects.filter(samp__pat__clin__pk=given_clin)

或者一些这样的事情应该可以解决问题。不幸的是,由于您违反约定的所有方式,您可能需要稍微重写一下。

其他是这样的:

Pat.objects.filter(clin__pk=given_clin).count()

Samp.objects.filter(clin__pk=given_clin).count()

如果@Evgeny 的阅读正确(我也是这样阅读的)。

通常,使用 Django 的 ORM,如果我尝试直接考虑我想要的 ORM,而不是尝试转换为我可能使用的 SQL 或从我不使用的 SQL 转换,我会发现我会得到更好的结果ORM。

【讨论】:

  • samptosnp 既有关系也有我要检索的信息。我对子查询所做的是检索SampToSnp 中的所有value 以获得匹配的Samp,但仅限于第一次命中,因为SampSampToSnp 之间的关系是一对一许多。奇怪的查询是 SampToSnp 表设计不佳的一种解决方法,该表包含 Snp 值,也是一种通过 SQL 直接在一行中获取所有信息的方法。
猜你喜欢
  • 1970-01-01
  • 2019-04-03
  • 1970-01-01
  • 1970-01-01
  • 2014-12-18
  • 2021-12-05
  • 2022-08-18
  • 2019-06-11
  • 1970-01-01
相关资源
最近更新 更多