Wybieranie unikalnych danych z dwóch tabel.

20 views
Skip to first unread message

Piotr Husiatyński

unread,
Jan 11, 2009, 4:36:36 AM1/11/09
to django-pl - grupa polskiej społeczności Django
Zatrzymałem się na problemie który rozwiązałem pętlami w Pythonie, ale
złożoność jest kwadratowa, więc to bardzo złe rozwiązanie. Nie za
bardzo jednak wiem znaleźć odpowiednie obiekty już na poziomie bazy.
Mam dwie tabele:

class Thread(models.Model):
# ...
author = models.ForeignKey(User)
latest_post_date = models.DateTimeField(_("Latest Post time"),
default=datetime.datetime.now())
latest_post_author = models.ForeignKey(User, related_name="User")

class VisitedThread(models.Model):
user = models.ForeignKey(User)
thread = models.ForeignKey(Thread)
date = models.DateTimeField(auto_now_add=True)

i w dużym skrócie chcę dostać listę obiektów Thread, do których nie ma
odwołań w tabeli VisitedThread. Da się coś takiego w Djangowym ORM
osiągnąć?

Darek

unread,
Jan 11, 2009, 1:36:06 PM1/11/09
to django-pl - grupa polskiej społeczności Django
On Jan 11, 10:36 am, Piotr Husiatyński <PHusiatyn...@gmail.com> wrote:
> i w dużym skrócie chcę dostać listę obiektów Thread, do których nie ma
> odwołań w tabeli VisitedThread. Da się coś takiego w Djangowym ORM
> osiągnąć?
Możesz użyć np. metody extra() i zadać warunek "WHERE id NOT IN
(SELECT thread_id FROM `VisitedThread`)", pewnie można też zrobić
wykluczenie (metoda exclude()).. bez złączeń tabel (tudzież dwóch
zapytań) się oczywiście nie obejdzie.. lepiej jednak zrzucić dwa
zapytania silnikowi bazy niż robić oba przez ORM'a.

Filip Wasilewski

unread,
Jan 14, 2009, 1:20:30 PM1/14/09
to django-pl - grupa polskiej społeczności Django
On 11 Sty, 10:36, Piotr Husiatyński <PHusiatyn...@gmail.com> wrote:
[...]
> i w dużym skrócie chcę dostać listę obiektów Thread, do których nie ma
> odwołań w tabeli VisitedThread. Da się coś takiego w Djangowym ORM
> osiągnąć?

Na co najmniej dwa sposoby, przy czym ten pierwszy jest lepszy i
prostszy, natomiast drugi przytoczę jako ciekawostkę:

In [2]: print Thread.objects.filter(visitedthread__isnull=True).query
SELECT `thread`.`id`, `thread`.`author_id`,
`thread`.`latest_post_date`, `thread`.`latest_post_author_id` FROM
`thread` LEFT OUTER JOIN `visitedthread` ON (`thread`.`id` =
`visitedthread`.`thread_id`) WHERE `visitedthread`.`id` IS NULL

W tym przypadku `isnull` wymusza połączenie LEFT OUTER JOIN, a warunek
sprawdza, dla których obiektów Thread nie istnieją odpowiadające im
obiekty VisitedThread. Dokładnie to, o co Ci chodziło.


In [3]: print Thread.objects.exclude
(id__in=VisitedThread.objects.values_list(
....: 'thread', flat=True).query).query
SELECT `thread`.`id`, `thread`.`author_id`,
`thread`.`latest_post_date`, `thread`.`latest_post_author_id` FROM
`thread` WHERE NOT (`thread`.`id` IN (SELECT
`visitedthread`.`thread_id` FROM `visitedthread`))

Do filtra `in` można też przekazać podzapytanie [1] (przydatne przy
niektórych łamańcach z Django ORM).

[1] http://docs.djangoproject.com/en/dev/ref/models/querysets/#in

P.S. Odkąd przyjrzałem się bliżej SQLAlchemy, Django ORM wydaje mi się
"gotowe w 60%". Tzn. jest fajne do pisania blogów, dpaste itp., gdzie
wystarczy prosty zrzut danych z bazy na stronę, ale już wykonanie
OUTER JOIN'a i innych konstrukcji graniczy z cudem...

fw
--
http://www.linkedin.com/in/filipwasilewski

Piotr Husiatyński

unread,
Jan 15, 2009, 8:59:24 AM1/15/09
to django-pl - grupa polskiej społeczności Django


On 14 Sty, 19:20, Filip Wasilewski <filipwasilew...@gmail.com> wrote:
> On 11 Sty, 10:36, Piotr Husiatyński <PHusiatyn...@gmail.com> wrote:
> [...]
>
> > i w dużym skrócie chcę dostać listę obiektów Thread, do których nie ma
> > odwołań w tabeli VisitedThread. Da się coś takiego w Djangowym ORM
> > osiągnąć?
>
> Na co najmniej dwa sposoby, przy czym ten pierwszy jest lepszy i
> prostszy, natomiast drugi przytoczę jako ciekawostkę:
>
> In [2]: print Thread.objects.filter(visitedthread__isnull=True).query
> SELECT `thread`.`id`, `thread`.`author_id`,
> `thread`.`latest_post_date`, `thread`.`latest_post_author_id` FROM
> `thread` LEFT OUTER JOIN `visitedthread` ON (`thread`.`id` =
> `visitedthread`.`thread_id`) WHERE `visitedthread`.`id` IS NULL
>
> W tym przypadku `isnull` wymusza połączenie LEFT OUTER JOIN, a warunek
> sprawdza, dla których obiektów Thread nie istnieją odpowiadające im
> obiekty VisitedThread. Dokładnie to, o co Ci chodziło.
>

Prawie działa tak jak powinno. Prawie, bo dodałem kolejny warunek,
który sporo komplikuje. Napisałem taki oto kod:

dt = jakas_data
# ...
unreaded = Thread.objects.filter(
# bez starych obiektow, oraz
Q(latest_post_date__gt=dt),
# te ktore na pewno sa nowe, bo uzytkownik ich nie
widzial lub
Q(visitedthread__isnull=True) |
# te ktore sa nowe, jesli pojawily sie nowe
wiadomosci od ostatniej wizyty
Q(visitedthread__isnull=False,
# to powinno sprawdzać datę ostatniej wiadomości
i porównywać z wpisem w osobnej tabeli,
# ale nie mam pojecia jak to napisac

visitedthread__date__gt=visitedthread__thread__latest_post_date)
).distinct()[offset:offset + number]

Jakieś pomysły jak zrealizować ostatnie porównanie dat?

Piotr Husiatyński

unread,
Jan 31, 2009, 2:31:23 PM1/31/09
to django-pl - grupa polskiej społeczności Django
Udało się..

Zgodnie z radą jaką otrzymałem na django-users ** poczekałem aż
zamknięty zostanie ticket, pobrałem wersję svn i oto jak wygląda mój
kod:

from django.db.models.expressions import F
# ...
unreaded = Thread.objects.filter(
Q(latest_post_date__gt=dt),
Q(visitedthread__isnull=True) |
Q(visitedthread__isnull=False,
visitedthread__date__lt=F('latest_post_date'))
).distinct()[offset:offset + number]

który generuje takie oto zapytanie:

SELECT DISTINCT "forum_thread"."id", "forum_thread"."author_id",
"forum_thread"."title", "forum_thread"."slug",
"forum_thread"."closed", "forum_thread"."sticky",
"forum_thread"."solved", "forum_thread"."post_count",
"forum_thread"."view_count", "forum_thread"."latest_post_date",
"forum_thread"."latest_post_author_id"
FROM "forum_thread"
LEFT OUTER JOIN "forum_visitedthread" ON ("forum_thread"."id" =
"forum_visitedthread"."thread_id")
WHERE ("forum_thread"."latest_post_date" > 2009-01-21
20:22:25.173780 AND ("forum_visitedthread"."id" IS NULL OR
("forum_visitedthread"."date" <
"forum_thread"."latest_post_date" AND "forum_visitedthread"."id" IS
NOT NULL)))
ORDER BY "forum_thread"."sticky" DESC,
"forum_thread"."latest_post_date"
DESC LIMIT 20



**
http://groups.google.com/group/django-users/browse_thread/thread/e69129daf7717dde/877328e6bbd97b4e?lnk=gst&q=Piotr+Husiaty%C5%84ski#877328e6bbd97b4e

Filip Wasilewski

unread,
Feb 1, 2009, 2:44:40 PM2/1/09
to django-pl - grupa polskiej społeczności Django
On 31 Sty, 20:31, Piotr Husiatyński <PHusiatyn...@gmail.com> wrote:
> Zgodnie z radą jaką otrzymałem na django-users ** poczekałem aż
> zamknięty zostanie ticket, pobrałem wersję svn i oto jak wygląda mój
> kod:
>
>   from django.db.models.expressions import F
[...]

Wreszcie..

fw
--
http://www.linkedin.com/in/filipwasilewski

Piotr Husiatyński

unread,
Feb 1, 2009, 3:39:22 PM2/1/09
to django-pl - grupa polskiej społeczności Django


On 1 Lut, 20:44, Filip Wasilewski <filipwasilew...@gmail.com> wrote:
> On 31 Sty, 20:31, Piotr Husiatyński <PHusiatyn...@gmail.com> wrote:> Zgodnie z radą jaką otrzymałem na django-users ** poczekałem aż
> > zamknięty zostanie ticket, pobrałem wersję svn i oto jak wygląda mój
> > kod:
>
> >   from django.db.models.expressions import F
>
> [...]
>
> Wreszcie..

Niestety tylko svn.
A kod da się jeszcze bardziej uprościć i poza jednym bardzo dziwnym
przypadkiem działa znakomicie, generując bardzo prosty SQL.

unreaded = Thread.objects.exclude(
visitedthread__user=u,
latest_post_date__lt=F('visitedthread__date')
).filter(latest_post_date__gt=dt).distinct()
Reply all
Reply to author
Forward
0 new messages