Django ORM: Lösche alle Einträge aber behalte die neusten X

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Ich möchte alle Einträge bis auf die neusten 10 löschen.

Anscheinend ist das ein wenig kompliziert. Meine momentane Lösung erscheint mir zu umständlich:

Code: Alles auswählen

number = 10

queryset = LogEntry.objects.all().order_by('-createtime')

ids = tuple(queryset[number:].values_list('id', flat=True))
queryset.filter(id__in=ids).delete()
tuple() in Zeile 6 ist bei MySQL erforderlich, damit wirklich zwei Queries ausgeführt werden. Ansonsten gibt es den Fehler:
This version of MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery'
Einfacher wäre ja:

Code: Alles auswählen

number = 10

queryset = LogEntry.objects_site.all().order_by('-createtime')
queryset = queryset[-number:]
queryset.delete()
Geht aber nicht, es kommt der django Fehler:
Cannot use 'limit' or 'offset' with delete.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Niemand eine bessere Lösung dafür?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Ich kenne mich jetzt nicht mit Djangos ORM aus, aber in SQL würde man doch einfach die Bedingung

Code: Alles auswählen

delete from dings where datecol < kleinstes_datum_zu_behaltendes_datum
formulieren. Das sollte doch aus in Django gehen, oder?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Klar mit einem datum zu arbeiten ist weniger das Problem. Das kann so aussehen:

Code: Alles auswählen

datetime_filter = datetime.now() - timedelta(hours=2)
queryset = LogEntry.objects.all().order_by('-createtime')
queryset.exclude(createtime__gte=datetime_filter).delete()
Ich möchte es aber auch über die Anzahl machen: Behalte die neusten 10 Stück.

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
frabron
User
Beiträge: 306
Registriert: Dienstag 31. März 2009, 14:36

Und als Subselect?

Code: Alles auswählen

delete from foo where date < (select min(date) from foo order by date desc limit 10)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Gerade nochmal mit "Cannot use 'limit' or 'offset' with delete." in den selben Fehler gelaufen ;)

Also in SQL sollte das gehen:

Code: Alles auswählen

DELETE FROM foo ORDER BY bar LIMIT 1000;
Warum das mit dem Django ORM nicht geht, weiß ich nicht. Meine Momentane Lösung ist noch immer die von oben...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@jens: Bei so etwas muss man aber immer aufpassen worauf sich das LIMIT bezieht. Wenn man Pech hat nicht auf die sortierten Daten. Dann bekommt man 1000 „zufällige“ Datensätze die sinnloserweise vor dem Löschen halt noch sortiert werden. :-)
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Das wäre natürlich fatal.

Im IRC wurde ich darauf hingewiesen, das anscheinend nicht alle DBs sowas können. MySQL kann es aber offensichtlich. Zumindest nach diesen Bug Einträgen zu Urteilen:
http://bugs.mysql.com/bug.php?id=1024
http://bugs.mysql.com/bug.php?id=12915

Wäre vielleicht mal eine Gelegenheit ein RAW-SQL zu erstellen. Aber ich vermute das sich der Aufwand in meinem Fall nicht lohnen wird. Denn in diesem Fall müßten immer nur wenige Einträge gelöscht werden. Wenn es aber wirklich viele sind, macht es wohl Sinn, denke ich...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

jens hat geschrieben:

Code: Alles auswählen

number = 10

queryset = LogEntry.objects.all().order_by('-createtime')

ids = tuple(queryset[number:].values_list('id', flat=True))
queryset.filter(id__in=ids).delete()
Als kleine Optimierungsmöglichkeit - je nach Menge der Daten - könntest du auch aus dem Queryset die IDs holen die bleiben sollen und dann mit NOT IN löschen. "NOT IN" muss mit dem ORM von Django als .exclude(id__in=...) formuliert werden.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

@/me: Stimmt. Allerdings ist es beim mir umgekehrt. Es bleiben mehr als gehen müssen ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten