Lide: Verwalten des "Datenzugriff mit SQL"

Yo!

Hat man erst mal fuer ein neues Projekt die Anforderungen "zusammengetragen" so macht man sich an's Design des Datenmodells, welches hoffentlich den gewuenschten Teil der Realitaet abbildet.

Dann macht man sich an den Datenzugriff. Man ist ja schlau und verzichtet auf das Zusammenbasteln von SQL-Statements im "Programmcode" und auch auf den Einsatz von MS-empfohlenen Techniken (ADO, OOP, Serverprodukte von MS ausser 'MSSQL Server'), sondern gestaltet den Datenzugriff mithilfe von sog. Stored Procedures.

Dann hat man ein groesseres Rudel von "SET_", "GET_", DEL_", LST_" und "LET_"-Prozeduren, die sich allerdings langfristig (fuer Wartungszwecke z.B.) nicht leicht verwalten lassen. - Eine Trennung von Praesentation, Datenhaltung und Datenzugriff ist aber da.

Nun (endlich) die Frage:
Gibt es neben der o.g. Trennung nach Datenzugriffsform (CRUDL) weitere Vorschlaege zur Gruppierung (Namensgebung / Verwaltung) der Stored Prodedures? (Meine Versuche "Geschaeftslogik" von Datenzugriffslogik zu trennen, misslingen regelmaessig. Die Trennung von Praesentation, Datenzugriff und "Geschaeftslogik" habe ich allerdings "von Microsoft" gelernt.)

Gruss,
Lude

  1. Hi Lide,

    Man ist ja schlau und verzichtet auf das Zusammenbasteln von SQL-Statements im "Programmcode" ... , sondern gestaltet den Datenzugriff mithilfe von sog. Stored Procedures.

    ... sofern man kann. Und wenn ein RDBMS das nicht unterstützt, was dann?
    (Ich habe nichts gegen dynamisch zusammengebastelte SQL-Statements - solange der "Bastler" selbst wiederum ein Modul ist.)

    Dann hat man ein groesseres Rudel von "SET_", "GET_", DEL_", LST_" und "LET_"-Prozeduren, die sich allerdings langfristig (fuer Wartungszwecke z.B.) nicht leicht verwalten lassen.

    Wie bist Du denn auf _diese_ Namen gekommen ... ?

    Vorschlaege zur Gruppierung (Namensgebung / Verwaltung) der Stored Prodedures?

    Was hältst Du davon, Dich von gängigen Adressierungsmethoden objektorientierter Sprachen inspirieren zu lassen?
    ("objektname"."methodenname", bzw. ein an dieser Stelle syntaktisch erlaubter Pseudo-Trenner wie "_")

    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.
    (sh:| fo:} ch:] rl:( br:^ n4:( ie:% mo:) va:| de:/ zu:| fl:( ss:) ls:~ js:|)
     => http://www.peter.in-berlin.de/projekte/selfcode/?code=sh%3A|+fo%3A}+ch%3A]+rl%3A(+br%3A^+n4%3A(+ie%3A%25+mo%3A)+va%3A|+de%3A%2F+zu%3A|+fl%3A(+ss%3A)+ls%3A~+js%3A|
    Auch diese Signatur wird an korrekt konfigurierte Browser gzip-komprimiert übertragen.
    1. Hallo, Michael,

      Man ist ja schlau und verzichtet auf das Zusammenbasteln von SQL-Statements im "Programmcode" ... , sondern gestaltet den Datenzugriff mithilfe von sog. Stored Procedures.

      ... sofern man kann. Und wenn ein RDBMS das nicht unterstützt, was dann?
      (Ich habe nichts gegen dynamisch zusammengebastelte SQL-Statements - solange der "Bastler" selbst wiederum ein Modul ist.)

      das Basteln von SQL-Statements hat u.a. den Nachteil, dass ausfuehrbarer Code vom Nutzer eingegeben werden koennte ('-- drop db...'). Ausserdem ist die Performance suboptimal. - Ansonsten kommt ein objektorientiertes Herangehen (Klassenmodule) in Frage und ist vielleicht zu praeferieren. - Aber ist es nicht wesentlich komplexer als ein Rudel SPs?

      Dann hat man ein groesseres Rudel von "SET_", "GET_", DEL_", LST_" und "LET_"-Prozeduren, die sich allerdings langfristig (fuer Wartungszwecke z.B.) nicht leicht verwalten lassen.

      Wie bist Du denn auf _diese_ Namen gekommen ... ?

      Das waren Praefixe. Ist nach meiner Kenntnis auch ueblich.

      Vorschlaege zur Gruppierung (Namensgebung / Verwaltung) der Stored Prodedures?

      Was hältst Du davon, Dich von gängigen Adressierungsmethoden objektorientierter Sprachen inspirieren zu lassen?
      ("objektname"."methodenname", bzw. ein an dieser Stelle syntaktisch erlaubter Pseudo-Trenner wie "_")

      OK, das scheint die Alternative zu sein. - Aber kann man dann auch mit vertretbarem Aufwand sicherstellen, dass eine Aenderung am Datenmodell den bisher implementierten Datenzugriff nicht zerstoert. - Ich meine, muss man dann nicht in jedem Modul schauen, ob der Code noch laeuft?

      Gruss,
      Lude

      1. Moin!

        das Basteln von SQL-Statements hat u.a. den Nachteil, dass ausfuehrbarer Code vom Nutzer eingegeben werden koennte ('-- drop db...').

        Wenn sowas möglich ist, hast du als Programmierer geschlampt. Solche Dinge passieren dann nicht, wenn alle Usereingaben ordentlich validiert und escaped werden.

        Ausserdem ist die Performance suboptimal.

        Wenn das RDBMS sowas nicht anbietet, wird man die verfügbaren Methoden verwenden und die resultierende Performance akzeptieren müssen. :)

        Aber kann man dann auch mit vertretbarem Aufwand sicherstellen, dass eine Aenderung am Datenmodell den bisher implementierten Datenzugriff nicht zerstoert. - Ich meine, muss man dann nicht in jedem Modul schauen, ob der Code noch laeuft?

        Auf diese Frage wird Michael antworten, dass er (sowas hat er unlängst mal angedeutet) grundsätzlich für ein Modul eine Tabelle mit Datenmodell verwendet, und keinerlei Joins über Tabellen unterschiedlicher Module laufen läßt. Änderungen im Datenmodell einer Tabelle haben also nur Auswirkungen auf das Modul (bzw. erfordern dort Änderungen), aber nirgends sonst.

        - Sven Rautenberg

        --
        ss:) zu:) ls:[ fo:} de:] va:) ch:] sh:) n4:# rl:| br:< js:| ie:( fl:( mo:|
        1. Hi,

          danke fuer Deine Ausfuehrungen. Ich habe fast alles positiv aufgenommen, nur...

          das Basteln von SQL-Statements hat u.a. den Nachteil, dass ausfuehrbarer Code vom Nutzer eingegeben werden koennte ('-- drop db...').

          Wenn sowas möglich ist, hast du als Programmierer geschlampt. Solche Dinge passieren dann nicht, wenn alle Usereingaben ordentlich validiert und escaped werden.

          Hier macht man m.E. einen Denkfehler ueblerer Art, wenn man Deiner Argumentation folgt. (Habe gerade diese oder eine aehnliche zu meinem Aerger immer wieder gehoert.) - Komplexitaet muss vermieden werden! Und wenn man Nutzereingaben nicht oder weniger filtern muss (Filtern ist komplex), dann ist das eindeutig zu praeferieren.

          Gruss,
          Lude

          PS: Zu dem objektorientierten Ansatz beim Datenzugriff haette ich gerne noch etwas mehr info.

        2. Hallo Sven,

          Auf diese Frage wird Michael antworten, dass er (sowas hat er unlängst mal angedeutet) grundsätzlich für ein Modul eine Tabelle mit Datenmodell verwendet, und keinerlei Joins über Tabellen unterschiedlicher Module laufen läßt.

          das geht ein bißchen an dem vorbei, was ich damals geschrieben hatte.
          Es ist eher umgekehrt: Mein Datenmodell ist stark objektorientiert ausgelegt, die einzelnen externen Objekte werden jeweils durch Informationen repräsentiert, die auf mehrere Tabellen verteilt sind, wobei aber Inhalte übergeordneter Tabellen beeinflussen, welche untergeordneten Tabellen dazugenommen werden müßten. Ich muß also tatsächlich zuerst die übergeordnete Tabelle lesen, um herauszufinden, womit ich hätte JOINen müssen. (Ich baue etwas Ähnliches wie die Tabs eines Browsers, aber mit stark typisierten Inhalten, welche wiederum sehr unterschiedlich detailliert parametrisiert werden, nicht so einheitlich wie die URLs in Browser-Tabs - im Sinne eines redundanzfreien Datenmodells habe ich für jeden Inhaltstyp eine separate Untertabelle usw.)
          Mein Datenmodell ist wahrscheinlich ein Ausnahmefall, der sich aber genau deshalb für eine stark objektorientierte Modellierung, bei welcher die Datenbank lediglich der Datencontainer ist (und ich keine Alternative dazu habe - die Datenbank war schon vor meinem Projekt da, und ich muß _in_ihr_ ein Feature dazu bauen, das dann von einer Fremdanwendung so ausgewertet wird, als hätte sie selbst diese Daten erzeugt).

          Und die objektorientierte Modellierung (mit der ich bisher gar nicht so viel Erfahrung habe, die mir aber sehr zusagt) hat eben den Vorteil der "dünnen Kanten" zwischen den Objekten - je stärker das Geheimnisprinzip ausgeprägt ist, desto einfacher ist es, die Konsequenzen nachträglicher Änderungen zu überschauen.

          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.
          (sh:| fo:} ch:] rl:( br:^ n4:( ie:% mo:) va:| de:/ zu:| fl:( ss:) ls:~ js:|)
           => http://www.peter.in-berlin.de/projekte/selfcode/?code=sh%3A|+fo%3A}+ch%3A]+rl%3A(+br%3A^+n4%3A(+ie%3A%25+mo%3A)+va%3A|+de%3A%2F+zu%3A|+fl%3A(+ss%3A)+ls%3A~+js%3A|
          Auch diese Signatur wird an korrekt konfigurierte Browser gzip-komprimiert übertragen.
      2. Hi Lude,

        das Basteln von SQL-Statements hat u.a. den Nachteil, dass ausfuehrbarer Code vom Nutzer eingegeben werden koennte ('-- drop db...').

        Keineswegs. Ich kann ja innerhalb des Moduls eine Abbildung zwischen legalen (!) symbolischen Eingabewerten und tatsächlich in den Code einbindbaren Elementen vornehmen, wenn die Semantik das hergibt (Aufzählungstypen etc.). Sind meine Eingaben Strings bzw. Integers, dann ist ein ordentliches Quoting (Element der DBI-Schnittstelle) bzw. ein Wertemengentest (ein regular expression) nicht zuviel an Investition.

        Ausserdem ist die Performance suboptimal.

        Siehe GMX-Diskussion: Die 2ct zusätzlich bezahle ich gerne.

        Ansonsten kommt ein objektorientiertes Herangehen (Klassenmodule) in Frage

        Genau das wollte ich andeuten - insbesondere auch für die Namenswahl.

        Aber ist es nicht wesentlich komplexer als ein Rudel SPs?

        Ganz im Gegenteil. "Teile und herrsche" lautet das Motto des Informatikers.

        Aber kann man dann auch mit vertretbarem Aufwand sicherstellen, dass eine Aenderung am Datenmodell den bisher implementierten Datenzugriff nicht zerstoert.

        Genau das ist es, was _Dein_ Modell nicht mit vertretbarem Aufwand sicherstellen kann, weil alle Deine "Rudel-Funktionen" gleichberechtigt sind und einander in die Suppe spucken dürfen.

        Kapselst Du aber die Zugriffe auf datentragende Objekte in kleinstmögliche Funktionsmodule, dann mußt Du nur innerhalb des Funktionsmoduls ändern, weil andere Instanzen die geänderten Daten ja noch nicht mal syntaktisch ansprechen können.
        Ich hatte Anfang dieses Jahres so einen Fall, wo ich am vor Tag der Produktionsaufnahme einen Verständnisfehler (der Bedeutung der Daten der entsprechenden Tabelle, die ich nur benutzen darf, nicht aber erfunden habe) in einem meiner Module fand - und die Behebung bestand dann in der Änderung genau zweier Zeilen, nämlich zweiere WHERE-Klauseln innerhalb des Moduls, der diese Tabelle kapselte. Niemand außerhalb dieses Moduls erfuhr jemals von dieser Änderung.

        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.
        (sh:| fo:} ch:] rl:( br:^ n4:( ie:% mo:) va:| de:/ zu:| fl:( ss:) ls:~ js:|)
         => http://www.peter.in-berlin.de/projekte/selfcode/?code=sh%3A|+fo%3A}+ch%3A]+rl%3A(+br%3A^+n4%3A(+ie%3A%25+mo%3A)+va%3A|+de%3A%2F+zu%3A|+fl%3A(+ss%3A)+ls%3A~+js%3A|
        Auch diese Signatur wird an korrekt konfigurierte Browser gzip-komprimiert übertragen.
        1. Hallo, Michael,

          ich versuche mir die Sache mal anhand folgenden Beispiels vorzustellen:

          Gegeben sind die Tabellen 'USERS' (DFs 'User_ID', 'User_Name', 'User_Password', 'User_Rights_ID') und 'RIGHTS' (DFs 'Right_ID', 'Right_Name'), wobei in der Tabelle 'USERS' auf dem DF 'User_Right_ID' ein Fremdschluessel auf 'Right_ID' (Tabelle 'USERS') sitzt.

          Datenzugriff - Anforderung "Gib' mir 'Right_Name' fuer 'User_Name' und 'User_Password':
          Die "umgebende Programmlogik" wendet sich nun beispielsweise an ein Objekt mit dem Namen 'Datenzugriff', welches beim Konstrukten prueft, ob der Datenserver erreichbar ist und im Fehlerfall im Destruktor die Meldung 'Error Datenzugriff.1: Konnte Datenserver nicht erreichen' zurueckgibt.

          Dann setzt das globale Datenzugriffsobjekt das Unterobjekt mit dem Namen 'Datenzugriff.USERS', welches im Konstruktor eine Schemaabfrage Richtung "Datenserver.DB.USERS" sendet und prueft, ob das Schema dem Erwarteten entspricht, ansonsten Destruktormeldung 'Error Datenzugriff.USERS.1: Schema unerwartet' an 'Datenzugriff'. Dann ruft das Objekt 'Datenzugriff' die Methode 'Datenzugriff.USERS.Gib_mir_das_Recht_fuer_User_Name_und_User_Password()' auf. Diese setzt dann das Objekt 'Datenzugriff.RIGHTS', welches im Konstruktor ebenfalls eine Schemaabfrage Richtung "Datenserver.DB.RIGHTS" sendet mit der Frage "Sind die erwarteten DFs alle da?", im Misserfolgsfall wird vom Destruktor die Meldung 'Error Datenzugriff.RIGHTS.1: Schema unerwartet' an 'Datenzugriff' zurueckgegeben.

          Nun sind die drei Objekte also da.   :-)

          Als naechstes ruft die Methode 'Datenzugriff.USERS.Gib_mir_das_Recht_fuer_User_Name_und_User_Password()' die private Methode 'Datenzugriff.USERS.Gib_mir_die_User_ID_fuer_User_Name_und_User_Password()' auf, welche im Erfolgsfall eine ID zurueckgibt. Ansonsten Fehlermeldung: 'Error Datenzugriff.USERS.2: keinen User gefunden'

          Nun wendet sich 'Datenzugriff.USERS.Gib_mir_das_Recht_fuer_User_Name_und_User_Password()' an die Methode 'Datenzugriff.RIGHTS.Gib_mir_Right_Name_fuer_Right_GUID()', welche dank der vom DB(M)S unterstuetzen ref. Integritaet einen Namen zurueckgibt.

          Nun wird diese Information "nach oben" zurueckgegeben und destruktet ('Datenzugriff' wuerde ich mal nicht destrukten und permanent verfuegbar lassen). - Validieren und ggf. Maskieren der Daten mal in dieser Betrachtung unbehandelt gelassen.

          Habe ich Dich tendenziell richtig verstanden? - Welche Strukturhierarchie sollten die Objekte? - Soll sowas wie eine Baumstruktur 'rauskomen? - Ist der Overhead nicht _immens_, wenn man sich Abfragen mit beispielsweise 8 Joins vorstellt? - Kann man diese "Datenzugriffs-ObjektStruktur" nicht auch mit SPs, die sich gegenseitig aufrufen, abbilden?

          Wie immer gespannt auf (D)eine Antwort.

          Gruss,
          Lude

          1. Hi Lude,

            Habe ich Dich tendenziell richtig verstanden?

            Ja.

            Welche Strukturhierarchie sollten die Objekte?
            Soll sowas wie eine Baumstruktur 'rauskomen?

            "Soll" klingt so, als könntest Du es erzwingen - ich denke, das kann man nicht, das kommt auf die Natur der zu modellierenden Daten an. Bei mir ist das schon im theoretischen Sinne ein (vierstufiger) Baum, der ziemlich "mit Gewalt" in _eine_ relationale Tabelle gequetscht wurde ... das, was ich an Tabellen selbst angelegt habe, ist m. E. 'natürlicher', existiert neben dieser einen Tabelle und muß ständig in diese übersetzen, weil deren Inhalt zu signifikanten Teilen außerhalb meiner Kontrolle periodisch neu generiert wird und dabei bestimmte Teile seines Inhalts ändern kann ... ich sagte ja, mein Datenmodell sei ein Extremfall.

            Ist der Overhead nicht _immens_, wenn man sich Abfragen mit beispielsweise 8 Joins vorstellt?

            Ja, ist er.

            Aber ich habe nie ausgeschlossen, daß ein Objekt nicht auch eine _Gruppe_ thematisch verwandter Tabellen, die selbst nur untereinander geJOINed werden, nicht jedoch mit anderen zusammen, kapselt. (In mySQL würde ich für eine solche Gruppe ggf. sogar eine separate "Datenbank" anlegen ...)

            • Kann man diese "Datenzugriffs-ObjektStruktur" nicht auch mit SPs, die sich gegenseitig aufrufen, abbilden?

            Kann man. Aber wie stellst Du sicher, daß jede Stored Procedure nur auf diejenigen Objekte zugreift, auf die sie zugreifen 'darf' (im Sinne einer sauberen Modularisierung)?

            Das Geheimnisprinzip ersetzt guten Willen durch Kontrolle.

            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.
            (sh:| fo:} ch:] rl:( br:^ n4:( ie:% mo:) va:| de:/ zu:| fl:( ss:) ls:~ js:|)
             => http://www.peter.in-berlin.de/projekte/selfcode/?code=sh%3A|+fo%3A}+ch%3A]+rl%3A(+br%3A^+n4%3A(+ie%3A%25+mo%3A)+va%3A|+de%3A%2F+zu%3A|+fl%3A(+ss%3A)+ls%3A~+js%3A|
            Auch diese Signatur wird an korrekt konfigurierte Browser gzip-komprimiert übertragen.
            1. Hallo, Michael,

              vielen Dank fuer Deine Ausfuehrungen (und Deine Geduld)! - Ich werde den objektorientierten Datenzugriff auf ein RDB(M)S im Sommer testweise bei einem Privatprojekt (Aus- und Einnahmenverwaltung mit Perl/MSSQL Server; 14 Tabellen; produktiv in verschiedensten Datenhaltungen/Umgebungen seit 5 Jahren) testen.

              Vielleicht hast Du noch eine Meinung zu bestimmten MS-Produkten (ADO, VB, weitere Namen mir nicht gegenwaertig (BizServer?)), die das o.g. Vorgehen tendenziell zu unterstuetzen versprechen? (Ein Partnerteam in meiner Firma will/muss sich der genannten Herausforderung stellen (ca. 30 MSSQL Server, ca. 50 Tabellen, 50-300GB Daten).)

              Beste Gruesse,
              Lude

            2. Hallo, Michael,

              • Kann man diese "Datenzugriffs-ObjektStruktur" nicht auch mit SPs, die sich gegenseitig aufrufen, abbilden?

              Kann man. Aber wie stellst Du sicher, daß jede Stored Procedure nur auf diejenigen Objekte zugreift, auf die sie zugreifen 'darf' (im Sinne einer sauberen Modularisierung)?

              Gegenfrage: Wie stellst Du das bei objektorientiertem Datenzugriff sicher?

              Gruss,
              Lude

              1. Hi Lude,

                Kann man. Aber wie stellst Du sicher, daß jede Stored Procedure nur auf diejenigen Objekte zugreift, auf die sie zugreifen 'darf' (im Sinne einer sauberen Modularisierung)?
                Gegenfrage: Wie stellst Du das bei objektorientiertem Datenzugriff sicher?

                Gute Frage, in der Tat.

                In beiden Fällen ist eine Trennung nur dann möglich, wenn jeder 'Befugte' etwas weiß, was andere nicht wissen (z. B. eine Authentifizierung gegenüber der Datenbank - theoretisch könnte ich jede Tabellengruppe in eine separate 'Datenbank' legen und diese per Userid/Passwort und GRANT schützen).

                Inwiefern nun alle Stored Procedures 'aus einer Hand' stammen oder nicht, und inwiefern selbiges für alle Module gilt, das ist natürlich eine organisatorische Frage.
                Mir erscheint die Trennung zwischen Modulen logischer - um so mehr, als die hier ursprünglich genannte Namensbildung der Stored Procedures gemäß welcher Konvention auch immer eben gerade _nicht_ in dieser Dimension trennte, was Methoden eines 'Objekt-Moduls' aber schon aufgrund syntaktischer Vorgaben der Programmiersprache automatisch tun müssen (ich dulde keine name space pollution, ich adressiere Funktionen beim Aufruf generell über deren Modulname als Präfix).

                Das CREATE TABLE-Statement steht ja im Quelltext des Moduls (in der Generator-Methode); dieses Modul besitzt folglich Wissen über die ansprechbaren Felder der Tabelle(n).
                Würde ich dieses Wissen innerhalb eines anderen Moduls ebenfalls verwenden, dann würde ich gegen meine eigene Richtlinie verstoßen, auf die ich die Entstehung der Module überhaupt erst zurückgeführt habe: Divide et impera.

                Letzten Endes erzwingt die Verwendung der Module also (fast) nichts; sie macht _mir_ das zugrundeliegende Konzept des Programmierstils nur deutlicher bewußt als die Stored Procedures.
                (Welche durchaus auch ihren Anwendungsbereich haben - nämlich dann, wenn ich mit Programmen verschiedener Programmiersprachen auf dieselben Tabellen zugreifen _muß_, also die Modul-Lösung aus technischen Gründen ausscheidet ... bei Oracle hatte ich diesen Fall schon, weil ich sowohl Programme aus Oracle-proprietären Sprachen wie dessen Formular-Generator brauchte als auch solche in Pro*C.)

                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.
                (sh:| fo:} ch:] rl:( br:^ n4:( ie:% mo:) va:| de:/ zu:| fl:( ss:) ls:~ js:|)
                 => http://www.peter.in-berlin.de/projekte/selfcode/?code=sh%3A|+fo%3A}+ch%3A]+rl%3A(+br%3A^+n4%3A(+ie%3A%25+mo%3A)+va%3A|+de%3A%2F+zu%3A|+fl%3A(+ss%3A)+ls%3A~+js%3A|
                Auch diese Signatur wird an korrekt konfigurierte Browser gzip-komprimiert übertragen.