Django queryset 可以生成带有自连接的 SQL 语句吗?
Can Django queryset generate a SQL statement with self join?
我有一个 table 将 parents 关联到 children,它有以下数据:
+-----+-----+-----+--------+
| pid | rel | cid | relcat |
+-----+-----+-----+--------+
| 13 | F | 216 | 1 |
| 13 | F | 229 | 1 |
| 13 | f | 328 | 2 |
| 13 | F | 508 | 1 |
| 13 | F | 599 | 1 |
| 13 | f | 702 | 2 |
| 560 | M | 229 | 1 |
| 560 | m | 702 | 2 |
+-----+-----+-----+--------+
我可以通过 SQL:
加入 npr table 来找到 229 的兄弟
SELECT npr_a.cid,
CASE (SUM(IF(npr_a.relcat=1 AND npr_b.relcat=1,1,0))) WHEN 2 THEN '~FB~' WHEN 1 THEN '~HB~' ELSE '~Foster~' END AS BrotherType,
abs(person_details.isalive) as isalive
FROM person_details,
npr npr_a,
npr npr_b
WHERE ( npr_b.cid = 229) AND
( npr_a.pid = npr_b.pid ) AND
( npr_a.cid <> 229) AND
( npr_b.relcat <> 3 ) AND
( npr_a.relcat <> 3 ) AND
( person_details.id = npr_a.cid )
GROUP BY npr_a.cid;
获得:
+-----+-------------+---------+
| cid | BrotherType | isalive |
+-----+-------------+---------+
| 216 | ~HB~ | 1 |
| 328 | ~Foster~ | 0 |
| 508 | ~HB~ | 0 |
| 599 | ~HB~ | 0 |
| 702 | ~Foster~ | 1 |
+-----+-------------+---------+
我尝试了很多方法来使用 Django queryset 获取它,但都未能获得正确的结果。我能得到的最好的东西是:
idp = Npr.objects.filter(cid=229).values_list('pid', flat=True)
idc = Npr.objects.filter(pid__in=idp).exclude(cid=229)
但该解决方案无法生成 BrotherType 字段。
我的模特:
class PersonDetails(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
name= models.CharField(db_column='Name', max_length=20, blank=True, null=True)
isalive = models.BooleanField(db_column='isAlive')
class Meta:
managed = False
db_table = 'person_details'
class Npr(models.Model):
rid = models.AutoField(db_column='rid', primary_key=True)
pid = models.ForeignKey(PersonDetails, on_delete=models.CASCADE, db_column='PID')
rel = models.CharField(max_length=1)
cid = models.ForeignKey(PersonDetails, on_delete=models.CASCADE, db_column='CID')
relcat = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'npr'
unique_together = (('pid', 'cid'),)
使用 Django 版本:3.0.4 Python 版本:3.7.3 数据库:10.3.22-MariaDB-0+deb10u1-log
对构建所需的查询集有什么建议吗?
幸运的是,我找到了解决问题的办法。
我创建了一个指向同一个数据库的新模型 table
class nNpr(models.Model):
rid = models.AutoField(db_column='rid', primary_key=True)
pid = models.ForeignKey(Npr, on_delete=models.CASCADE, db_column='PID', to_field='pid', related_name='rel_pid')
rel = models.CharField(max_length=1)
cid = models.PositiveSmallIntegerField(db_column='CID')
relcat = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'npr'
unique_together = (('pid', 'cid'),)
并修改了原始模型以抑制当您将外键字段连接到非唯一字段时 Django 生成的警告,修改后的模型为:
class Npr(models.Model):
rid = models.AutoField(db_column='rid', primary_key=True)
pid = models.OneToOneField(PersonDetails, on_delete=models.CASCADE, , unique=True, db_column='PID', related_name='rel_pid')
rel = models.CharField(max_length=1)
cid = models.ForeignKey(PersonDetails, on_delete=models.CASCADE, db_column='CID', related_name='rel_cid')
relcat = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'npr'
unique_together = (('pid', 'cid'),)
最后是查询集:
brothers = nNpr.objects.select_related('pid').filter(cid = 229).exclude(Q(relcat = 3)\
| Q(pid__cid = 229) | Q(pid__relcat = 3 )).values('pid__cid', 'pid__cid__name',\
'pid__cid__isalive').annotate(cprel=Sum('relcat'), pcrel=Sum('pid__relcat'))
结果SQL是:
SELECT T2.`CID`, `person_details`.`Name`, `person_details`.`isAlive`, SUM(`npr`.`relcat`) AS `cprel`\
, SUM(T2.`relcat`) AS `pcrel` FROM `npr` INNER JOIN `npr` T2 ON (`npr`.`PID` = T2.`PID`) INNER\
JOIN `person_details` ON (T2.`CID` = `person_details`.`ID`) WHERE (`npr`.`CID` = 229 AND NOT \
((`npr`.`relcat` = 3 OR T2.`CID` = 198 OR T2.`relcat` = 229))) GROUP BY T2.`CID`,\
`person_details`.`Name`, `person_details`.`isAlive` ORDER BY NULL
我找到了另一个更好的解决方案,它不需要创建指向同一数据库的新模型 table。解决方案是在一个模型上递归地级联所需的关系:
brothers = Npr.objects.filter(pid__pid__cid = 229, relcat__lt = 3,
pid__pid__relcat__lt = 3).values('cid', 'cid__name')
我有一个 table 将 parents 关联到 children,它有以下数据:
+-----+-----+-----+--------+
| pid | rel | cid | relcat |
+-----+-----+-----+--------+
| 13 | F | 216 | 1 |
| 13 | F | 229 | 1 |
| 13 | f | 328 | 2 |
| 13 | F | 508 | 1 |
| 13 | F | 599 | 1 |
| 13 | f | 702 | 2 |
| 560 | M | 229 | 1 |
| 560 | m | 702 | 2 |
+-----+-----+-----+--------+
我可以通过 SQL:
加入 npr table 来找到 229 的兄弟SELECT npr_a.cid,
CASE (SUM(IF(npr_a.relcat=1 AND npr_b.relcat=1,1,0))) WHEN 2 THEN '~FB~' WHEN 1 THEN '~HB~' ELSE '~Foster~' END AS BrotherType,
abs(person_details.isalive) as isalive
FROM person_details,
npr npr_a,
npr npr_b
WHERE ( npr_b.cid = 229) AND
( npr_a.pid = npr_b.pid ) AND
( npr_a.cid <> 229) AND
( npr_b.relcat <> 3 ) AND
( npr_a.relcat <> 3 ) AND
( person_details.id = npr_a.cid )
GROUP BY npr_a.cid;
获得:
+-----+-------------+---------+
| cid | BrotherType | isalive |
+-----+-------------+---------+
| 216 | ~HB~ | 1 |
| 328 | ~Foster~ | 0 |
| 508 | ~HB~ | 0 |
| 599 | ~HB~ | 0 |
| 702 | ~Foster~ | 1 |
+-----+-------------+---------+
我尝试了很多方法来使用 Django queryset 获取它,但都未能获得正确的结果。我能得到的最好的东西是:
idp = Npr.objects.filter(cid=229).values_list('pid', flat=True)
idc = Npr.objects.filter(pid__in=idp).exclude(cid=229)
但该解决方案无法生成 BrotherType 字段。 我的模特:
class PersonDetails(models.Model):
id = models.AutoField(db_column='ID', primary_key=True)
name= models.CharField(db_column='Name', max_length=20, blank=True, null=True)
isalive = models.BooleanField(db_column='isAlive')
class Meta:
managed = False
db_table = 'person_details'
class Npr(models.Model):
rid = models.AutoField(db_column='rid', primary_key=True)
pid = models.ForeignKey(PersonDetails, on_delete=models.CASCADE, db_column='PID')
rel = models.CharField(max_length=1)
cid = models.ForeignKey(PersonDetails, on_delete=models.CASCADE, db_column='CID')
relcat = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'npr'
unique_together = (('pid', 'cid'),)
使用 Django 版本:3.0.4 Python 版本:3.7.3 数据库:10.3.22-MariaDB-0+deb10u1-log 对构建所需的查询集有什么建议吗?
幸运的是,我找到了解决问题的办法。 我创建了一个指向同一个数据库的新模型 table
class nNpr(models.Model):
rid = models.AutoField(db_column='rid', primary_key=True)
pid = models.ForeignKey(Npr, on_delete=models.CASCADE, db_column='PID', to_field='pid', related_name='rel_pid')
rel = models.CharField(max_length=1)
cid = models.PositiveSmallIntegerField(db_column='CID')
relcat = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'npr'
unique_together = (('pid', 'cid'),)
并修改了原始模型以抑制当您将外键字段连接到非唯一字段时 Django 生成的警告,修改后的模型为:
class Npr(models.Model):
rid = models.AutoField(db_column='rid', primary_key=True)
pid = models.OneToOneField(PersonDetails, on_delete=models.CASCADE, , unique=True, db_column='PID', related_name='rel_pid')
rel = models.CharField(max_length=1)
cid = models.ForeignKey(PersonDetails, on_delete=models.CASCADE, db_column='CID', related_name='rel_cid')
relcat = models.PositiveIntegerField()
class Meta:
managed = False
db_table = 'npr'
unique_together = (('pid', 'cid'),)
最后是查询集:
brothers = nNpr.objects.select_related('pid').filter(cid = 229).exclude(Q(relcat = 3)\
| Q(pid__cid = 229) | Q(pid__relcat = 3 )).values('pid__cid', 'pid__cid__name',\
'pid__cid__isalive').annotate(cprel=Sum('relcat'), pcrel=Sum('pid__relcat'))
结果SQL是:
SELECT T2.`CID`, `person_details`.`Name`, `person_details`.`isAlive`, SUM(`npr`.`relcat`) AS `cprel`\
, SUM(T2.`relcat`) AS `pcrel` FROM `npr` INNER JOIN `npr` T2 ON (`npr`.`PID` = T2.`PID`) INNER\
JOIN `person_details` ON (T2.`CID` = `person_details`.`ID`) WHERE (`npr`.`CID` = 229 AND NOT \
((`npr`.`relcat` = 3 OR T2.`CID` = 198 OR T2.`relcat` = 229))) GROUP BY T2.`CID`,\
`person_details`.`Name`, `person_details`.`isAlive` ORDER BY NULL
我找到了另一个更好的解决方案,它不需要创建指向同一数据库的新模型 table。解决方案是在一个模型上递归地级联所需的关系:
brothers = Npr.objects.filter(pid__pid__cid = 229, relcat__lt = 3,
pid__pid__relcat__lt = 3).values('cid', 'cid__name')