Autor Beitrag
Narses
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: 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? :gruebel:)
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 :zwinker:).

Also, wie würdet ihr sowas angehen (konzeptionell)? :nixweiss:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.
elundril
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 3747
Erhaltene Danke: 123

Windows Vista, Ubuntu
Delphi 7 PE "Codename: Aurora", Eclipse Ganymede
BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: Sa 10.07.10 16:07 
Für das Hinzufügen:

ausblenden SQL-Anweisung
1:
INSERT INTO foo (ItemName, ItemPos) SELECT 'DeinItemName'Max(ItemPos)+1 FROM foo					


Für's Verschieben:
ausblenden 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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: So 11.07.10 00:42 
Moin!

user profile iconelundril hat folgendes geschrieben Zum zitierten Posting springen:
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? :nixweiss: Es werden aber auch nur wenige Datensätze hinzugefügt, häufiger ist das Sortieren.

user profile iconelundril hat folgendes geschrieben Zum zitierten Posting springen:
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. ;)




user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Für das Hinzufügen:
ausblenden SQL-Anweisung
1:
INSERT INTO foo (ItemName, ItemPos) SELECT 'DeinItemName'Max(ItemPos)+1 FROM foo					
Jau, das hab ich gesucht! :autsch: Danke! :zustimm:


user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Für's Verschieben:
ausblenden 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. :)

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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... :oops:

user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 8721
Erhaltene Danke: 191

Win95, Win98SE, Win2K, WinXP
D1S, D3S, D4S, D5E, D6E, D7E, D9PE, D10E, D12P, DXEP, L0.9\FPC2.0
BeitragVerfasst: So 11.07.10 01:11 
user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
Moin!

user profile iconelundril hat folgendes geschrieben Zum zitierten Posting springen:
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? :nixweiss: Es werden aber auch nur wenige Datensätze hinzugefügt, häufiger ist das Sortieren.

user profile iconelundril hat folgendes geschrieben Zum zitierten Posting springen:
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).

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Für das Hinzufügen:
ausblenden SQL-Anweisung
1:
INSERT INTO foo (ItemName, ItemPos) SELECT 'DeinItemName'Max(ItemPos)+1 FROM foo					
Jau, das hab ich gesucht! :autsch: Danke! :zustimm:

NP ;-)

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
Für's Verschieben:
ausblenden 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):

ausblenden 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 + 1WHERE 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


user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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... :oops:

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 ;-)

user profile iconNarses hat folgendes geschrieben Zum zitierten Posting springen:
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: So 11.07.10 01:56 
user profile iconBenBE hat folgendes geschrieben Zum zitierten Posting springen:
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 Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Administrator
Beiträge: 10183
Erhaltene Danke: 1256

W10ent
TP3 .. D7pro .. D10.2CE
BeitragVerfasst: So 11.07.10 20:11 
Moin!

user profile iconKha hat folgendes geschrieben Zum zitierten Posting springen:
Wie wäre es damit, sie als verkettete Liste abzulegen (Recursive Foreign Key)?
Geniale Idee :D hat aber auch ein paar Haken... :? Muss ich mir aber mal ernsthaft durch den Kopf gehen lassen! :think:

cu
Narses

_________________
There are 10 types of people - those who understand binary and those who don´t.