Thomas Schmieder: SQL: Bedingtes Update durchführen?

Hallo,

ich habe eine Tabelle B, in der Unterpositionen zu einer anderen A eingetragen werden.

Die Unterpositionen werden über eine eigene Positionsnummer sortiert.

ID_A  | ergeben zusammen den Sortierschlüssel
POS   |

ID_A       POS
-------- --------
1        1
1        2
1        3
3        2
3        5
3        6

So ungefähr könnte die Tabelle nach einigen Bewegungen aussehen.

Nun will ich möglichst mit einem einzigen SQL-Statement festestellen, ob ich vor der ID_A=1, POS=2 eine neue Position einfügen kann. Hierzu müsste ja vorher Platzgeschaffen werden, also ein Update ( "POS=POS+1" )auf die POS >= 2, die zu ID_A gehören.

Das soll aber nur dann passieren, wenn POS=2 überhaupt vorhanden ist, denn sonst könnte sie ja sowieso belegt werden.

Gibt es nun eine Möglichkeit, das in einem SQL-Statement zu verpacken?

Zweite Frage
------------
Muss ich die Tabelle für das Update locken oder lässt MySQL während meiner Aktion sowieso niemand ran? Da ja hinterher auf jeden Fall noch ein Insert auf die gefundene/geschaffene Lücke kommt, wäre locken wohl ratsamer, oder?

Liebe Grüße aus http://www.braunschweig.de

Tom

--
Intelligenz ist die Fähigkeit, aus Fehlern Anderer zu lernen und Mut die, eigene zu machen.
  1. Halihallo Thomas

    Nun will ich möglichst mit einem einzigen SQL-Statement festestellen, ob ich vor der ID_A=1, POS=2 eine neue Position einfügen kann. Hierzu müsste ja vorher Platzgeschaffen werden, also ein Update ( "POS=POS+1" )auf die POS >= 2, die zu ID_A gehören.

    Ich habe ein bissle darüber nachgedacht. Bin zum Schluss gekommen, dass dies sicher
    nicht über einen Query zu machen ist. Du brauchst _mindestens_ zwei. Ich hätte einige
    unausgereifte Ideen im Hinterkopf, wie sich dies (zugegebenermassen unelegant, aber
    wirklich kurz) machen lässt. Diese Tricks sind jedoch vom DBMS abhängig (also welches?)
    und erfordern noch folgende Fragen zu beantworten:

    Muss POS eine Zahl sein?
    Muss jede Unterposition einer "Oberposition" mit eins anfangen (was jedoch durch dein
    Beispiel mit nein zu beantworten ist, wäre es repräsentativ).
    Muss der "Abstand" zwischen zwei Unterpositionen einer Position immer eins sein?

    Gibt es nun eine Möglichkeit, das in einem SQL-Statement zu verpacken?

    Falls dein DBMS stored procedures unterstützt?

    Muss ich die Tabelle für das Update locken oder lässt MySQL während meiner Aktion sowieso niemand ran? Da ja hinterher auf jeden Fall noch ein Insert auf die gefundene/geschaffene Lücke kommt, wäre locken wohl ratsamer, oder?

    Nur bei einem UPDATE bestimmt nicht; aber wenn du mindestens zwei Queries brauchst und
    davon gehe ich aus, ja. Obwohl dies bei zeitunkritischen Anwendungen wohl kaum
    problematisch sein dürfte.

    Viele Grüsse

    Philipp

    1. Hallo und Dank an Beide,

      Millionen von Sätzen werde ich da nicht haben, die das Kriterium erfüllen. Pro Hauptposition wird es vielleicht 20 bis 30 Unterpositionen geben. Man könnte die nun auch nicht geschlossen vorcodieren, also erstmal in 10er-Schritten. Abr irgendwann wird man immer vor dem Problem stehen, dass die Postition schon belegt ist.

      Ich werde also wohl nicht drum herum kommen, eine Abfrage, ein Update für die Lücke und ein Insert für den neuen Satz zu bauen. Also drei Statements und die müssen ggf. auch noch durch Lock gebunden werden. Allerdings ist es unwahrscheinlich, dass gerade zwei User am selben Nummernkreis fummeln, aber nicht ausgeschlossen.

      Datenbank ist erstmal MySQL, also leider keine Stored Procedures und einfach nen Trigger drauf und gut wärs...

      Liebe Grüße aus http://www.braunschweig.de

      Tom

      --
      Intelligenz ist die Fähigkeit, aus Fehlern Anderer zu lernen und Mut die, eigene zu machen.
      1. Halihallo Thomas

        Millionen von Sätzen werde ich da nicht haben, die das Kriterium erfüllen. Pro Hauptposition wird es vielleicht 20 bis 30 Unterpositionen geben. Man könnte die nun auch nicht geschlossen vorcodieren, also erstmal in 10er-Schritten. Abr irgendwann wird man immer vor dem Problem stehen, dass die Postition schon belegt ist.

        Das Problem hast du nur bei Integern. Nimmst du eine Timestamp und sortierst du diese
        aufsteigend, hast du die richtige Reihenfolge und kannst die Positions implizit durch
        einen Counter in der Programmiersprache setzen; damit umgehst du das Zeitaufwendige
        sortieren und neubelegen der Positionen. Eine von einigen Ideen, die ich gestern hatte.
        Ziel der ganzen Sache: Du musst erreichen, dass die Unterposition _redundant_ wird.
        Du musst versuchen, diese irgendwie implizit "berechnen" zu können; eg. durch Timestamp
        Sortierung. Du musst dich von der Unterposition in der Ursprungsform trennen, ansonsten
        wirst du immer die gesamte Tabelle überarbeiten müssen (naja, Worst Case).

        Ich werde also wohl nicht drum herum kommen, eine Abfrage, ein Update für die Lücke und ein Insert für den neuen Satz zu bauen. Also drei Statements und die müssen ggf. auch noch durch Lock gebunden werden. Allerdings ist es unwahrscheinlich, dass gerade zwei User am selben Nummernkreis fummeln, aber nicht ausgeschlossen.

        Nun, wie Michael sagt, ist dieses Vorgehen etwas suboptimal. Es ist kann nicht Zweck der
        Sache sein, im Worst-Case alle Records neu zu belegen (POS), nur um einen neuen Record
        einzufügen. Die Lösung über einen Baum oder der Timestampmethode von oben wäre vielleicht
        etwas besser; hängt jedoch stark von dem ab, was _du_ für eine Aufgabenstellung hast.

        Datenbank ist erstmal MySQL, also leider keine Stored Procedures und einfach nen Trigger drauf und gut wärs...

        Tja... Warten wir mal die nächsten Versionen ab. VIEW's, Stored Procedures und
        Subselects sind ja noch ausstehend...

        Viele Grüsse

        Philipp

        1. Halihallo Thomas

          Das Problem hast du nur bei Integern. Nimmst du eine Timestamp und sortierst du diese
          aufsteigend, hast du die richtige Reihenfolge und kannst die Positions implizit durch
          einen Counter in der Programmiersprache setzen; damit umgehst du das Zeitaufwendige
          sortieren und neubelegen der Positionen. Eine von einigen Ideen, die ich gestern hatte.
          Ziel der ganzen Sache: Du musst erreichen, dass die Unterposition _redundant_ wird.
          Du musst versuchen, diese irgendwie implizit "berechnen" zu können; eg. durch Timestamp
          Sortierung. Du musst dich von der Unterposition in der Ursprungsform trennen, ansonsten
          wirst du immer die gesamte Tabelle überarbeiten müssen (naja, Worst Case).

          Vielleicht noch die anderen Überlegungen. Leider hatte ich keine Zeit diese auszuarbeiten
          und/oder zu überprüfen:

          MySQL unterstützt Variablen, diese können, wenn ich mich erinnere über SELECT @var:=5;
          gesetzt werden. Eventuell kannst du mit einem UPDATE die Positionen automatisch
          hochzählen lassen (wäre möchlich, dass UPDATE POS = (@var:=@var+1) oder so funktionieren
          könnte, obwohl ich das als unwahrscheinlich einstufe).
          Man könnte einen Umweg über einen VIEW machen (in MySQL eine temporäre Tabelle, oder eine
          Hilfstabelle); weiss zwar nicht, was das bringt, aber wäre u. U. von Vorteil...
          Hm. Was hatte ich da noch? - Eben, die Timestamp-Variante. Ach ja, POS als autoincrement,
          dann zählt das ja immer automatisch hoch; naja, das Einfügen ist dann zwar unmöglich,
          wenn man nicht noch eine Extrainformation (eg. Timestamp) hat.

          Das sind so meine Vorschläge um dies evtl. vollkommen ohne Scripte hinzukriegen. Aber der
          Umweg über ein Script ist vielleicht doch ratsamer, also derartige kurriosen Konstrukte
          zu verwenden...

          Viele Grüsse

          Philipp

        2. Hallo,

          Ich halte das gar nicht für so schlimm mit den POS-Feldern. Da es in der Regel nur ca. 20 bis 30, vielleicht mal 50 Unterpositionen zu einer Seite gibt, und die dann ohnehin alle geholt werden müssen, kann MySQL die ohne weiteres im Speicher sortieren. Das POS-Feld selber muss auch nicht Bestandteil des Index werden.

          Ich denke, das Select besorgt die Menge über die ID der Seite und die einzelnen Elemente werden dann durch das Orderstatement erst sortiert. Wenn die DB schlau ist, merkt die, dass auf ID ein Index liegt und auf POS nicht und optimiert das Query entsprechend.

          Wäre mir nur lieber gewesen, ich müsste nicht locken. Bei drei einzelnen Folgestatements wird mir da aber nix anderes übrig bleiben.

          Subselects sind ja noch ausstehend...

          Die haben mir schon sehr gefehlt zur Inkonsistenzsuche. Zum Glück habe ich hier Rat bekommen. Mit "left outer join" hats dann auch geklappt.

          Liebe Grüße aus http://www.braunschweig.de

          Tom

          --
          Intelligenz ist die Fähigkeit, aus Fehlern Anderer zu lernen und Mut die, eigene zu machen.
          1. Halihallo Thomas

            Ich halte das gar nicht für so schlimm mit den POS-Feldern. Da es in der Regel nur ca. 20 bis 30, vielleicht mal 50 Unterpositionen zu einer Seite gibt, und die dann ohnehin alle geholt werden müssen, kann MySQL die ohne weiteres im Speicher sortieren. Das POS-Feld selber muss auch nicht Bestandteil des Index werden.

            Ohne weiteres sortieren? - Aber 'türlich, das stellt mysql nicht vor grössere Probleme.

            Ich denke, das Select besorgt die Menge über die ID der Seite und die einzelnen Elemente werden dann durch das Orderstatement erst sortiert. Wenn die DB schlau ist, merkt die, dass auf ID ein Index liegt und auf POS nicht und optimiert das Query entsprechend.

            EXPLAIN SELECT foo, bar FROM bar ORDER BY foo

            wird dir das sagen. Ich bin mir sogar sehr sicher, dass Mysql den Query entsprechend
            optimiert und sonst ist es schlecht programmiert(tm) :-).

            Wäre mir nur lieber gewesen, ich müsste nicht locken. Bei drei einzelnen Folgestatements wird mir da aber nix anderes übrig bleiben.

            Naja, also wie gross würde den der Schaden sein, wenn das mal schief laufen würde?
            Und wie gross ist die Wahrscheinlichkeit, dass es falsch laufen wird?
            Beides miteinander multipliziert gibt deinen "Problemkoeffizient", der IMHO sehr klein
            ausfallen wird. Vielleicht so klein, dass du ihm keine Relevanz mehr zuordnest, aber
            das ist deine Entscheidung.
            WARNUNG: Diese Überlegung ist in den _meisten_ Fällen tödlich! - Solche Fragen darf
            man sich nicht stellen! - Also besser doch ein LOCK TABLE ;)

            Subselects sind ja noch ausstehend...

            Die haben mir schon sehr gefehlt zur Inkonsistenzsuche. Zum Glück habe ich hier Rat bekommen. Mit "left outer join" hats dann auch geklappt.

            Ja, mit LEFT OUTER JOIN's kann man einige gewisse Subselects "umgehen"; steht auch als
            Beispiel in der Doku.
            Inkonsistenzsuche: Nun, eines der sehr wichtigen Prinzipien einer Datenbank ist, dies
            auf Datenbankebene gar nicht zuzulassen. Bei MySQL ist man leider noch auf eine
            Suche angewiesen, bzw. muss das programmiertechnisch ausschliessen. Aber Datenkonsistenz
            sollte schon das Problem der Datenbank sein und nicht des Anwenderprogrammes. Aber
            vielleicht fabrizieren sie auch schon an derartigen Features.

            Hat sich deine Frage eigentlich erledigt? - Ich hörte aus deinem Posting keine
            (Rück-)Frage heraus.

            Viele Grüsse

            Philipp

  2. Hi Thomas,

    Nun will ich möglichst mit einem einzigen SQL-Statement festestellen, ob ich vor der ID_A=1, POS=2 eine neue Position einfügen kann. Hierzu müsste ja vorher Platzgeschaffen werden, also ein Update ( "POS=POS+1" )auf die POS >= 2, die zu ID_A gehören.

    das halte ich für keine tolle Idee.
    Stell Dir vor, Du hast eine Million Datensätze und mußt ganz vorne einen einfügen!

    Ich würde Dir empfehlen, als Sortierkriterium etwas Besseres als Integer zu verwenden.

    Erinnere Dich daran, wie man mit Bäumen sortiert. Jedes Element innerhalb eines Baums ist durch seinen "Weg" eindeutig adressierbar; diese "Links-Rechts"-Kombination ist als String von Nullen und Einsen darstellbar.

    Du kannst Deinen Datenvorrat als sortierte lineare Liste darstellen, indem Du ihn als balancierten Baum darstellst und dann jedem Knoten seine Baum-Adresse als Sortier-Schlüssel gibst.
    Willst Du einen neuen Datensatz einfügen, dann denkst Du Dir einfach die Knotenteilung des Blattes an der Einfügeposition im Baum und änderst dabei nur genau zwei Datensätze: Den einzufügenden und denjenigen, der nun sein Bruder wird. Beim Löschen gilt das Inverse analog.

    Datentyp für die Baumadresse: Ein hinreichend langer String. Dieser ist für das normale ORDER BY geeignet; er ist sogar indexierbar.

    Nach hinreichend vielen Einfüge- und Lösch-Operationen tendiert Dein Baum über längere Zeit hinweg dazu, zu degenerieren; es macht also Sinn, ihn alle paar Wochen oder Monate mal wieder neu zu balancieren. Aber nicht bei jeder einzelnen Operation ...

    Muss ich die Tabelle für das Update locken oder lässt MySQL während meiner Aktion sowieso niemand ran?

    Das kommt m. E. auf den Tabellentreiber an - der sollte transaktionsfähig sein. (myISAM reicht beispielsweise nicht.)

    Viele Grüße
          Michael

    --
    T'Pol: I apologize if I acted inappropriately.
    V'Lar: Not at all. In fact, your bluntness made me reconsider some of my positions. Much as it has now.