| Autor |
Beitrag |
Narses
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: Sa 10.07.10 13:58
Moin!
Ich habe eine Tabelle, die auf´s Wesentliche reduziert zwei Spalten hat:
Items: ItemName, ItemPos
Durch ItemPos soll bei der Abfrage mit ORDER BY ItemPos die Ordnung der Eintäge hergestellt werden. Sinn ist es, dass der User diese Ordnung herstellen kann.
Neue Einträge mit ItemPos := SELECT MAX(ItemPos)+1 FROM Items anlegen (btw: bisher brauche ich dafür zwei SQL-Queries, eine für das neue ItemPos und eine für das INSERT, geht das nicht auch mit nur einem?  )
Einträge löschen: einfach raushauen.
Problematisch wird´s jetzt beim Ändern der Reihenfolge schon bestehender Einträge. Hier ist ja nun etwas mehr zu tun. Ich habe aktuell eine Lösung, die finde ich aber nicht "elegant" (ihr kennt das ja schon  ).
Also, wie würdet ihr sowas angehen (konzeptionell)?
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
elundril
      
Beiträge: 3747
Erhaltene Danke: 123
Windows Vista, Ubuntu
Delphi 7 PE "Codename: Aurora", Eclipse Ganymede
|
Verfasst: Sa 10.07.10 14:09
Kannst du nicht einfach eine Sequenz anlegen die dann immer genommen wird?
(is jetzt nur ne lösung für die performance beim anlegen)
für den rest würd ich einen Trigger verwenden.
lg elundril
_________________ This Signature-Space is intentionally left blank.
Bei Beschwerden, bitte den Beschwerdebutton (gekennzeichnet mit PN) verwenden.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: Sa 10.07.10 16:07
Für das Hinzufügen:
SQL-Anweisung 1:
| INSERT INTO foo (ItemName, ItemPos) SELECT 'DeinItemName', Max(ItemPos)+1 FROM foo |
Für's Verschieben:
SQL-Anweisung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| BEGIN TRANSACTION -- Abfragen einer freien ItemID SET @tmpPos := SELECT MIN(ItemPos) - 1 FROM foo; -- Eintrag dorthin verschieben UPDATE foo SET ItemPos = @tmpPos WHERE ItemPos = :ItemPos LIMIT 1; -- Einträge zwischen altem und (einschließlich) neuem Eintrag verschieben UPDATE foo SET ItemPos = ItemPos - 1 WHERE ... -- Eintrag wieder an richtige Stelle UPDATE foo SET ItemPos = NewPos WHERE ItemPos = @tmpPos; COMMIT |
Da die ersten beiden Schritte nicht atomar sind, brauchst Du hier zwingend eine Transaktion. Das dürfte aber sowieso ja kein Thema sein.
Wenn Du Indexfehler komplett ausschließen möchtest, solltest Du nicht nur einen Eintrag, sondern alle mit der MIN-Bedingung verschieben, die geändert werden müssen.
Unter 3 UPDATE-Statements geht's AFAIK nicht.
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Narses 
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: So 11.07.10 00:42
Moin!
elundril hat folgendes geschrieben : | Kannst du nicht einfach eine Sequenz anlegen die dann immer genommen wird?
(is jetzt nur ne lösung für die performance beim anlegen) |
Hm, hat MySQL AFAIK nicht?  Es werden aber auch nur wenige Datensätze hinzugefügt, häufiger ist das Sortieren.
elundril hat folgendes geschrieben : | | für den rest würd ich einen Trigger verwenden. |
Tja, kann man alles machen, bringt mich aber der Lösung nicht näher.
Trotzdem Danke für deine Vorschläge.
BenBE hat folgendes geschrieben : | Für das Hinzufügen:
SQL-Anweisung 1:
| INSERT INTO foo (ItemName, ItemPos) SELECT 'DeinItemName', Max(ItemPos)+1 FROM foo | |
Jau, das hab ich gesucht!  Danke!
BenBE hat folgendes geschrieben : | Für's Verschieben:
SQL-Anweisung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| BEGIN TRANSACTION -- Abfragen einer freien ItemID SET @tmpPos := SELECT MIN(ItemPos) - 1 FROM foo; -- Eintrag dorthin verschieben UPDATE foo SET ItemPos = @tmpPos WHERE ItemPos = :ItemPos LIMIT 1; -- Einträge zwischen altem und (einschließlich) neuem Eintrag verschieben UPDATE foo SET ItemPos = ItemPos - 1 WHERE ... -- Eintrag wieder an richtige Stelle UPDATE foo SET ItemPos = NewPos WHERE ItemPos = @tmpPos; COMMIT |
Da die ersten beiden Schritte nicht atomar sind, brauchst Du hier zwingend eine Transaktion. Das dürfte aber sowieso ja kein Thema sein. |
Ich schau mir das mal an, sieht spannend aus.
BenBE hat folgendes geschrieben : | | Wenn Du Indexfehler komplett ausschließen möchtest, solltest Du nicht nur einen Eintrag, sondern alle mit der MIN-Bedingung verschieben, die geändert werden müssen. |
Das hab ich noch nicht so ganz verstanden...
BenBE hat folgendes geschrieben : | | Unter 3 UPDATE-Statements geht's AFAIK nicht. |
Aktuell sind´s bei mir mehr, das sieht doch gut aus.
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
BenBE
      
Beiträge: 8721
Erhaltene Danke: 191
Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
|
Verfasst: So 11.07.10 01:11
Narses hat folgendes geschrieben : | Moin!
elundril hat folgendes geschrieben : | Kannst du nicht einfach eine Sequenz anlegen die dann immer genommen wird?
(is jetzt nur ne lösung für die performance beim anlegen) | Hm, hat MySQL AFAIK nicht? Es werden aber auch nur wenige Datensätze hinzugefügt, häufiger ist das Sortieren.
elundril hat folgendes geschrieben : | | für den rest würd ich einen Trigger verwenden. | Tja, kann man alles machen, bringt mich aber der Lösung nicht näher.
Trotzdem Danke für deine Vorschläge.  |
MySQL hat auch keine Sequences; zumindest nicht in dem Sinne, wie man sie von Postgres her kennt. Den AutoIncrement kann man aber durchaus dafür missbrauchen, solange man dafür sorgt, dass beim setzen eines UNIQUE-Index bei jeder Operation auf der Tabelle die Sortierung eindeutig bleibt. Man muss dann allerdings damit leben können, dass die Sortierung Lücken aufweisen kann (z.B. nach dem Löschen von Einträgen).
Narses hat folgendes geschrieben : | BenBE hat folgendes geschrieben : | Für das Hinzufügen:
SQL-Anweisung 1:
| INSERT INTO foo (ItemName, ItemPos) SELECT 'DeinItemName', Max(ItemPos)+1 FROM foo | | Jau, das hab ich gesucht! Danke!  |
NP
Narses hat folgendes geschrieben : | BenBE hat folgendes geschrieben : | Für's Verschieben:
SQL-Anweisung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| BEGIN TRANSACTION -- Abfragen einer freien ItemID SET @tmpPos := SELECT MIN(ItemPos) - 1 FROM foo; -- Eintrag dorthin verschieben UPDATE foo SET ItemPos = @tmpPos WHERE ItemPos = :ItemPos LIMIT 1; -- Einträge zwischen altem und (einschließlich) neuem Eintrag verschieben UPDATE foo SET ItemPos = ItemPos - 1 WHERE ... -- Eintrag wieder an richtige Stelle UPDATE foo SET ItemPos = NewPos WHERE ItemPos = @tmpPos; COMMIT |
Da die ersten beiden Schritte nicht atomar sind, brauchst Du hier zwingend eine Transaktion. Das dürfte aber sowieso ja kein Thema sein. | Ich schau mir das mal an, sieht spannend aus.  |
Eigentlich recht simpel
1. Das in Transaction sollte klar sein. Encapsulation muss soweit sein, dass nur Stable Reads für die Transaktion zählen. Oder anders gesagt: Die Transaktion stellt hier einen Lock auf die Tabelle dar.
2. Das SET-Statement ermittelt die untere Grenze für verwendete Index-Nummern. Diese sollte theoretisch IMMER konstant bleiben; und zwar 0 (beim verschieben eines Datensatzes) - Bzw. 1 - Anzahl für den allgemeinen Fall. Womit wir bei ner kurzen Korrektur wären: Wir speichern an dieser Stelle mal das aktuelle Minimum.
3. Verschiebe alle Datensätze, die durch das Verschieben betroffen sind UNTER das aktuelle Minimum. Damit wird der Wertebereich zwischen der oberen und unteren Grenze frei.
4. Verschiebe alle Datensätze wieder zurück, die nur um eine Zeile verschoben werden.
5. Verschiebe den Datensatz mit der größten Bewegungsdistanz an die finale Stelle
6. Speichere die Änderungen - ACHTUNG: Dieser Commitkönnte wegen der zahlreichen Index-Operationen etwas brauchen.
Korrigiert also (:new,:old = Params, :old<:new):
SQL-Anweisung 1: 2: 3: 4: 5: 6: 7: 8: 9: 10:
| BEGIN TRANSACTION -- Abfragen einer freien ItemID SET @tmpPos := SELECT MIN(ItemPos) FROM foo; -- Eintrag dorthin verschieben UPDATE foo SET ItemPos = ItemPos - (:new - @tmpPos + 1) WHERE ItemPos >= :old AND ItemPos <= :new LIMIT :new - :old + 1; UPDATE foo SET ItemPos = ItemPos + (:new - @tmpPos) WHERE ItemPos >= @tmpPos - :new + :old AND ItemPos <= @tmpPos LIMIT :new - :old; -- Eintrag wieder an richtige Stelle UPDATE foo SET ItemPos = :new WHERE ItemPos = @tmpPos - :new + :old - 1 LIMIT 1; -- Speichern COMMIT |
Narses hat folgendes geschrieben : | BenBE hat folgendes geschrieben : | | Wenn Du Indexfehler komplett ausschließen möchtest, solltest Du nicht nur einen Eintrag, sondern alle mit der MIN-Bedingung verschieben, die geändert werden müssen. | Das hab ich noch nicht so ganz verstanden...  |
MySQL hat sich manchmal beim aktualisieren des Index, wenn man auf einem Unique Key UPDATE foo SET bar = bar - 1 für UNIQUE KEY baz(bar) ausführt. Daher muss man da erstmal Platz schaffen
Narses hat folgendes geschrieben : | BenBE hat folgendes geschrieben : | | Unter 3 UPDATE-Statements geht's AFAIK nicht. | Aktuell sind´s bei mir mehr, das sieht doch gut aus.
cu
Narses |
Du meinst: jeden Datensatz einzeln? Ach ja: Ich hoffe, die Änderungen für :new < :old bzw. :new = :old dürften klar gehen?
_________________ Anyone who is capable of being elected president should on no account be allowed to do the job.
Ich code EdgeMonkey - In dubio pro Setting.
|
|
Kha
      
Beiträge: 3803
Erhaltene Danke: 176
Arch Linux
Python, C, C++ (vim)
|
Verfasst: So 11.07.10 01:56
BenBE hat folgendes geschrieben : | | Unter 3 UPDATE-Statements geht's AFAIK nicht. |
Wenn kein Paging stattfinden soll, die Daten also immer komplett abgefragt werden, und im Gegenzug zur Entlastung des Servers der Client ein bisschen Aufwand beim Rekonstruieren der Daten haben darf: Wie wäre es damit, sie als verkettete Liste abzulegen (Recursive Foreign Key)? Dann wären wir immer noch bei 3 UPDATEs, aber es müssen auch nur genau 3 Reihen überhaupt angefasst werden  .
_________________ >λ=
|
|
Narses 
      

Beiträge: 10183
Erhaltene Danke: 1256
W10ent
TP3 .. D7pro .. D10.2CE
|
Verfasst: So 11.07.10 20:11
Moin!
Kha hat folgendes geschrieben : | | Wie wäre es damit, sie als verkettete Liste abzulegen (Recursive Foreign Key)? |
Geniale Idee  hat aber auch ein paar Haken...  Muss ich mir aber mal ernsthaft durch den Kopf gehen lassen!
cu
Narses
_________________ There are 10 types of people - those who understand binary and those who don´t.
|
|
|