2012-06-19 8 views
15

Szenario:Speicher Artikel Positionen (zur Bestellung) in einer Datenbank effizient

Es gibt eine Datenbank von Filmen ein Benutzer besitzt, werden Filme auf einer Seite angezeigt als „my-Filme“ können die Filme in denen angezeigt werden bestellen, dass der Benutzer wünscht. Zum Beispiel "Fight Club" in Position # 1, "Drive" in Position # 3 und so weiter und so fort.

Die offensichtliche Lösung ist mit jedem Element eine Position zu speichern, zum Beispiel:

MovieID, Benutzer-ID, Position
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3

Dann bei der Ausgabe der Daten nach der Position sortiert. Diese Methode funktioniert gut für die Ausgabe, hat jedoch ein Problem beim Aktualisieren: Die Position eines Elements Alle anderen Positionen müssen aktualisiert werden, da die Positionen relativ sind. Wenn sich Film # 3 nun an Position 2 befindet, muss Film # 3 nun auf Position 2 aktualisiert werden. Wenn die Datenbank 10.000 Filme enthält und ein Film von Position 1 nach Position 9999 verschoben wird, sind fast 10.000 Zeilen zu aktualisieren!

Meine einzige Lösung ist die Positionierung getrennt zu speichern, anstatt ein einzelnes Feld für jede Position zu haben, es ist nur eine große Datensicherung von Positionen, die zur Laufzeit genommen und mit jedem Element (json, xml, was auch immer) assoziiert sind das fühlt sich ... ineffizient an, da die Datenbank nicht zum Sortieren freigegeben werden kann.

Meine zusammengefasste Frage: Was ist die effizienteste Möglichkeit zum Speichern von Positionen Positionen in einer Liste, die zum Abrufen und Aktualisieren freundlich ist?

Antwort

1

Speichern Sie die Reihenfolge Linked-List-Stil. Speichern Sie anstelle der absoluten Position die ID des vorherigen Artikels. Auf diese Weise müssen Sie bei einer Änderung nur zwei Zeilen aktualisieren.

movieid | userid | previousid 
    1 | 1 | 
    2 | 1 | 1 
    3 | 1 | 4 
    4 | 1 | 2 

Um die Filme zu erhalten, um ...

SELECT movieid WHERE userid = 1 ORDER BY previousid 

-> 1, 2, 4, 3 

To (sagen wir) bewegen # 4 bis ein Raum:

DECLARE @previousid int, @currentid int 
SET @previousid = SELECT previousid FROM movies WHERE movieid = @currentid 

-- current movie's previous becomes its preceding's preceding 
UPDATE movies SET previousid = 
    (SELECT previousid FROM movies WHERE movieid = @previousid) 
WHERE movieid = @currentid 

-- the preceding movie's previous becomes the current one's previous 
UPDATE movies SET previousid = @currentid WHERE movieid = @previousid 

Das noch 1 Reads + 2 schreibt, aber es schlägt 10.000 Schreibvorgänge.

+0

und was wäre die SQL-Abfrage, um Filme aufzulisten? – bjan

+0

@bjan Auswählen sollte ziemlich einfach sein ... Aktualisierung ist ein bisschen Tricker, aber ich denke, das funktioniert. – McGarnagle

+0

Gemäß meinem Test, es ergibt sich doppelte Vorgeschichte !! – bjan

6

Ich habe gekämpft mit dem, was am besten mit dieser Situation zu tun und haben zu der Erkenntnis, dass BY FAR die beste Lösung ist eine Liste/Array der Filme in der Reihenfolge Sie wollen sie z.

userId, moviesOrder

1: [4,3,9,1 ...]

offensichtlich werden Sie Ihre Array serialise.

"Das fühlt sich ... ineffizient an"?

Betrachten Sie den Benutzer hatte eine Liste von 100 Filmen. Nach Position suchen wird eine Datenbankabfrage sein, eine String-zu-Array-Konvertierung und dann moviesOrder [index]. Möglicherweise langsamer als ein einfacher DB-Lookup, aber immer noch sehr, sehr schnell.

OTOH, prüfen Sie, wenn Sie die Reihenfolge ändern;

mit einer in der Datenbank gespeicherten Position benötigen Sie bis zu 100 Zeilenänderungen, verglichen mit einem Array-Spleiß. Die Idee der verknüpften Liste ist interessant, funktioniert aber nicht wie dargestellt, würde alles kaputt machen, wenn ein einzelnes Element versagt, und sieht auch viel langsamer aus. Andere Ideen wie Lücken zu verlassen, Float zu verwenden sind praktikabel, obwohl ein Durcheinander, und anfällig für Fehler an einem Punkt, es sei denn, Sie GC.

Es scheint, als sollte es einen besseren Weg, es in SQL zu tun, aber es gibt wirklich nicht.

8

Wenn Sie eine Kombination der Position und eines Zeitstempels verwenden, bei der der Benutzer einen Film an einer bestimmten Position ablegt und nicht versucht, die aktuelle Position beizubehalten, können Sie die Daten auf einfache Weise auswählen und aktualisieren. Beispielsweise; ein Basissatz von Daten:

create table usermovies (userid int, movieid int, position int, positionsetdatetime datetime) 

insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (123, 99, 1, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (123, 98, 2, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (123, 97, 3, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (123, 96, 4, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (123, 95, 5, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (123, 94, 6, getutcdate()) 

insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (987, 99, 1, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (987, 98, 2, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (987, 97, 3, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (987, 96, 4, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (987, 95, 5, getutcdate()) 
insert into usermovies (userid, movieid, position, positionsetdatetime) 
values (987, 94, 6, getutcdate()) 

Wenn Sie die Benutzer-Filme mit einer Abfrage wie folgt abfragen:

;with usermovieswithrank as (
    select userid 
    , movieid 
    , dense_rank() over (partition by userid order by position asc, positionsetdatetime desc) as movierank 
    from usermovies 
) 
select * from usermovieswithrank where userid=123 order by userid, movierank asc 

Dann werden Sie das erwartete Ergebnis erhalten:

USERID MOVIEID  MOVIERANK 
123  99   1 
123  98   2 
123  97   3 
123  96   4 
123  95   5 
123  94   6 

bewegen eine der rankings der filme müssen wir die position und die positionssatzzeitspalten aktualisieren. Zum Beispiel, wenn Benutzer-ID 123 bewegt Film 95 von Rang 5 auf Rang 2 dann tun wir dies:

update usermovies set position=2, positionsetdatetime=getutcdate() 
where userid=123 and movieid=95 

, die in diesen Ergebnisse (oben im Anschluss an die Aktualisierung der SELECT-Abfrage):

USERID MOVIEID  MOVIERANK 
123  99   1 
123  95   2 
123  98   3 
123  97   4 
123  96   5 
123  94   6 

Dann wenn Benutzer-ID 123 bewegt Film 96-1 Rang:

update usermovies set position=1, positionsetdatetime=getutcdate() 
where userid=123 and movieid=96 

Wir erhalten:

USERID MOVIEID  MOVIERANK 
123  96   1 
123  99   2 
123  95   3 
123  98   4 
123  97   5 
123  94   6 

Natürlich werden Sie mit doppelten Positionsspaltenwerten in der usermovies-Tabelle enden, aber mit dieser Methode werden Sie diese Spalte nie anzeigen, Sie verwenden sie einfach zusammen mit positionsetdatetime, um einen sortierten Rang für jeden Benutzer und Rang zu bestimmen Sie bestimmen, ist die reale Position.

Wenn Sie möchten, dass die Positionsspalte die Filmrangfolgen ohne Bezug auf die positionsetdatetime korrekt wiedergibt, können Sie den movieRank aus der Auswahlabfrage oben verwenden, um den Spaltenwert der Spalte usermovies zu aktualisieren bestimmte Filmrankings.

+2

Gerade ist diese Frage ein Jahr alt - oops! Egal, vielleicht hilft mein Vorschlag jemandem :-) – Elliveny

+0

Dies funktioniert nicht, wenn der Benutzer einen Film filmt _down_ die Liste.Wenn zum Beispiel der Film 98 von Position 4 nach Position 6 bewegt wird, gibt es zwei Filme mit der Position 6, aber der Film 98 wird wegen seiner kürzeren Positionsetdatumszeit zuerst (in Position 5) angezeigt. – bergie3000

+0

@ bergie3000 Sie haben Recht - es tut mir leid, das habe ich verpasst! Ich vermute, dass es leicht behoben werden kann, indem 1 bei einer Abwärtsbewegung zu der gewünschten Position hinzugefügt wird; Also in Ihrem Beispiel Einstellung Film 98 von Position 4 zu Position 7 (d. h. die gewünschte Position 6, plus 1) würde es tun, denke ich? – Elliveny

Verwandte Themen