Может ли Django queryset генерировать оператор SQL с самостоятельным соединением? - PullRequest
0 голосов
/ 17 марта 2020

У меня есть таблица, которая связывает родителей с детьми, она имеет следующие данные:

+-----+-----+-----+--------+
| 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      |
+-----+-----+-----+--------+

Я могу найти братьев 229, присоединив таблицу npr к себе с помощью SQL:

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 набор запросов, но все не смогли получить правильные результаты. лучшее, что я смог получить, это:

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 Есть предложения по созданию необходимого набора запросов?

1 Ответ

0 голосов
/ 19 марта 2020

к счастью, я нашел решение своей проблемы. Я создал новую модель, указывающую на ту же таблицу базы данных

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
...