执行Django原始SQL(使用“WHERE col IN”语法)或将原始SQL转换为.raw()或.extra()

时间:2010-11-23 15:42:58

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

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的内容?

2 个答案:

答案 0 :(得分:1)

对于像这样的问题,我将查询分成少数更简单的问题,我认为这很有可能。此外,我发现MySQL实际上可以通过这种方法更快地返回结果。

编辑 ...实际上在稍微思考后我发现你需要“在子查询上注释”,这在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

答案 1 :(得分:0)

你能解释一下你正在尝试用snp子查询提取的内容吗?我看到你正在加入这两个表,但看起来你真正想要的是Snp对象,它们具有一个具有给定id的关联Clin。如果是这样,那么几乎就像另一个单独的查询一样简单:

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(如果我不使用),我会发现效果更好ORM。