Lin: MySQL: Datensätze + totale Anzahl in einem Statement

Hi zusammen,

Hau mir hier grad meine Freitagnacht um die Ohren um noch paar
finale Sachen an meiner Seite zu machen.

Ich hab jetzt folgendes Problem:

ich hole mir per mod_rewrite IDs zur Abfrage aus einer Tabelle:

/news/12/
/news/215/
etc.

Wird eine ID übergeben, die nicht in der Tabelle enthalten ist,
frage ich ab, ob die Länge des Datensatzes >0 und gebe einen
entsprechenden Hinweis aus.

Jetzt würde ich gerne statt der Meldung, direkt einen 404-Status
schicken (mod_perl), allerdings _nicht_ wenn die Tabelle noch
leer ist, also noch keine News vorhanden.

Also hab ich versucht, mir mit einem Subquery den COUNT(*) der
Tabelle zu holen, etwa so:

  
SELECT n.id, n.title, n.text, (SELECT COUNT(*) FROM tbnews)  
AS count FROM tbnews n WHERE n.id = 12;  

Ausgabe in etwa:

id | title | text | count
-------------------------
12 | xxx   | foo  | 33
12 | bar   | baz  | 33

Und wollte dann eben gerne abfragen ob Datensatzlänge > 0 _und_
count > 0. Tja, falsch gedacht. Wenn ein leerer Satz zurückkommt,
komme ich ja auch nicht an die 'count' Column.

Ich hoffe, ihr versteht einigermassen, was ich will und am liebsten
würde ich das Ganze in einem Statement abhandeln, bin mir sicher
da gibts ne Möglichkeit.

Schonmal danke,
Lin

--
Mister rabbit says, "A moment of realization is worth a thousand prayers."
  1. yo,

    Tja, falsch gedacht. Wenn ein leerer Satz zurückkommt,
    komme ich ja auch nicht an die 'count' Column.

    klar, die unterabfrage wird nur ausgeführt, wenn es mindestens einen entsprechenden datensatz gibt. mir fallen spontan zwei möglichkeiten ein, dein problem zu lösen. die erste ist, indem du das problem per datenbankabfrage lößt, nämlich zwei abfragen per UNION miteinander verbindest.

    (SELECT COUNT(*) AS Spalte1, NULL, NULL, "1" AS Sortierung FROM tbnews WHERE ID = 12 ORDER BY 4)
    UNION
    (SELECT id, title, text, "2" FROM tbnews WHERE ID = 12)

    Dabei ist zu beachten, dass die Spaltenanzahl beider Abfragen gleich sein muss und die letzte künstliche Spalte dafür sorgt, dass der COUNT durch die Sortierung ganz oben als erster datensatz steht.

    die zweite möglichkeit finde ich aber besser, indem du ganz normal deine abfrage nach der ID machst, ganz ohne COUNT, und dann die anzahl der datensätze über die verwendete sprache abrufst, zum beispiel bei php über mysql_num_rows. die liefert dir nämlich schon "autmatisch" die anzahl der betroffenen datensätze der ergebnismenge.

    Ilja

    1. echo $begrüßung;

      die zweite möglichkeit finde ich aber besser, indem du ganz normal deine abfrage nach der ID machst, ganz ohne COUNT, und dann die anzahl der datensätze über die verwendete sprache abrufst, zum beispiel bei php über mysql_num_rows. die liefert dir nämlich schon "autmatisch" die anzahl der betroffenen datensätze der ergebnismenge.

      Das dachte ich auch zuerst, ist aber nicht das geforderte Ergebnis. Es wird die Gesamtanzahl der Datensätze in der Tabelle benötigt, nicht nur die der Ergebnismenge.
      Aber vielleicht kannst du mir ja meinen Denkfehler meines zweiten Lösungsvorschlags aufzeigen.

      echo "$verabschiedung $name";

      1. yo,

        Aber vielleicht kannst du mir ja meinen Denkfehler meines zweiten Lösungsvorschlags aufzeigen.

        du benutzt eine aggregat-funktion, ohne eine gruppierung zu verwenden. das klappt nur, wenn du auch keine weiteren spalten anzeigst, zum beispiel SELECT COUNT(*) FROM tabelle.

        Ilja

        1. yo,

          mal von der syntax abgesehen, macht der SELFJOIN auch keinen sinn, da selbst bei einem FULL OUTER JOIN du immer noch keine ergebnismenge bekommst, wenn die tabelle leer ist. sprich der OUTER JOIN hat keinen ansatz (datensatz). eine JOIN über zwei leere tabellen ergibt auch wiederum eine leere ergebnistabelle.

          was gehen würde, wenn man vorher eine temporäre tabelle nur mit der anzahl der datensätze bildet, dann hat man schon mal einen datensatz mit einer spalte als ansatz. aber dann würde ich doch eher die UNION weise vorziehen.

          Ilja

          1. echo $begrüßung;

            mal von der syntax abgesehen, macht der SELFJOIN auch keinen sinn, da selbst bei einem FULL OUTER JOIN du immer noch keine ergebnismenge bekommst, wenn die tabelle leer ist. sprich der OUTER JOIN hat keinen ansatz (datensatz). eine JOIN über zwei leere tabellen ergibt auch wiederum eine leere ergebnistabelle.

            Ich nahm die Abfrage nach der Anzahl auf die links Seite, denn diese ergibt immer einen Datensatz, auch bei leerer Tabelle. Hinzukommen sollten die Daten der abzufragenden ID oder NULL-Werte bei nicht vorhandener ID.

            echo "$verabschiedung $name";

            1. yo,

              Ich nahm die Abfrage nach der Anzahl auf die links Seite, denn diese ergibt immer einen Datensatz, auch bei leerer Tabelle. Hinzukommen sollten die Daten der abzufragenden ID oder NULL-Werte bei nicht vorhandener ID.

              du bekommst aber wie gesagt erst gar nicht die anzahl, weil die tabelle leer ist. das geht nur, wenn du unabhängig von der leeren tabelle die anzahl ermittelst, sprich zum beispiel über eine temporäre tabelle. alle versuche über eine unterabfrage oder einen SELFJOIN führen sprichwörtlich ins leere....

              Ilja

              1. echo $begrüßung;

                du bekommst aber wie gesagt erst gar nicht die anzahl, weil die tabelle leer ist.

                Nö, entweder meinst du was anderes als ich oder hast hier einen Denkfehler. SELECT COUNT(*) FROM tabelle; liefert immer einen Ergebnisdatensatz. Darin steht entweder eine wunderschöne Integer-0 (leere Tabelle) oder eine andere natürliche Zahl.

                echo "$verabschiedung $name";

                1. yo,

                  Nö, entweder meinst du was anderes als ich oder hast hier einen Denkfehler. SELECT COUNT(*) FROM tabelle; liefert immer einen Ergebnisdatensatz. Darin steht entweder eine wunderschöne Integer-0 (leere Tabelle) oder eine andere natürliche Zahl.

                  aber nicht in abhängigkeit von einem JOIN. der COUNT alleine für sich betrachtet liefert immer ein ergebnis. dann ist er aber auch unabhängig davon, ob nun ein join gebildet wird oder nicht. willst du den COUNT aber zusammen mit den SELFJOIN haben oder aber den COUNT in einer unterabfrage, dann läuft das ins leere. bei der unterabfrage ist es ja das gleiche, sie würde immer ein ergebnis liefern, wenn sie das dbms doch nur mal zur ausführung bringen würde. das es aber keine ergebnismenge gibt, macht es genau das nicht.

                  Ilja

        2. echo $begrüßung;

          du benutzt eine aggregat-funktion, ohne eine gruppierung zu verwenden. das klappt nur, wenn du auch keine weiteren spalten anzeigst, zum beispiel SELECT COUNT(*) FROM tabelle.

          OK. In einem weiteren Versuch nahm ich die Zählabfrage in ein Subselect, was diese ganze Geschichte noch komplizierter machte und auch kein befriedigendes Ergebnis lieferte.

          Beim Nachdenken über das Problem kann ich aber allgemein zu dem Schluss, dass das ursprüngliche Problem, keinen 404er bei leerer Tabelle ausgeben zu wollen, vermutlich nur im Labor, beim Aufsetzen des Systems, auftritt. Durch diese kleine Schönheitsoperation wird aber zusätzlicher Code bei jeder regulären Abfrage ausgeführt, was am Ende mehr Aufwand bedeutet, als es in meinen Augen Nutzen bringt.

          Wann möchte man denn ein News-System einsetzen? Meist dann, wenn man etwas mitzuteilen hat. Man setzt also das System auf, schreibt die erste Nachricht rein und hat nie wieder den Fall einer leeren Tabelle, aber den Code zum Abfragen dieses Zustands schleppt das System das gesamte Leben mit sich rum.

          echo "$verabschiedung $name";

          1. yo,

            OK. In einem weiteren Versuch nahm ich die Zählabfrage in ein Subselect, was diese ganze Geschichte noch komplizierter machte und auch kein befriedigendes Ergebnis lieferte.

            ist es nicht dann der gleiche ansatz, den auch Lin versucht hat und zu dem problem führt, es wird keine unterabfrage ausgeführt ohne datensatz in der tabelle ?

            Wann möchte man denn ein News-System einsetzen? Meist dann, wenn man etwas mitzuteilen hat. Man setzt also das System auf, schreibt die erste Nachricht rein und hat nie wieder den Fall einer leeren Tabelle, aber den Code zum Abfragen dieses Zustands schleppt das System das gesamte Leben mit sich rum.

            grundsätzlich würde ich dir rechte geben, man behandelt ein problem, dass in der praxis eigentlich nie so richtig auftreten wird. auf der anderen seite bedeutet sauberer implementieren, dass man schon möglichst alle fälle sauber abhandelt, auch wenn sie sehr unwahrscheinlich sind. wie gesagt, ich würde die anzahl nicht direkt über die SQL abfragen, sondern es über die programmierung regeln oder aber besser zwei abfragen stellen.

            Ilja

            1. echo $begrüßung;

              grundsätzlich würde ich dir rechte geben, man behandelt ein problem, dass in der praxis eigentlich nie so richtig auftreten wird. auf der anderen seite bedeutet sauberer implementieren, dass man schon möglichst alle fälle sauber abhandelt, auch wenn sie sehr unwahrscheinlich sind.

              Als Kompromiss hätte ich folgenden Vorschlag: Die Datenbank wird mit einem einfachen SELECT-Statement nach der News mit der gewünschten ID befragt. Wird diese gefunden (if numRows > 0) erfolgt die Ausgabe und alles ist gut. Wird sie nicht gefunden (else), wird eine zweite Abfrage nach der Anzahl der Datensätze der Tabelle abgesetzt, um daraufhin die Fehlermeldung zu verfeinern ("Nachricht nicht gefunden" vs. "Keine Nachrichten vorhanden"). Somit hat man im häufiger auftretenden Nachricht-gefunden-Fall keine unnötigen Schnörkel und erst im Fehlerfall einen geringen Mehraufwand.

              (Auch bei diesem System muss man beachten, dass man sich über die vom Client gesendete ID eine SQL-Injection einfangen kann, und dagegen Maßnahmen treffen sollte. (nur Zahlen zulassen oder ID als mit mysql_real_escape_string()  - solch eine Funktion sollte Perl kennen, da sie zur MySQL-API gehört - behandelten Stringwert übergeben))

              echo "$verabschiedung $name";

          2. Hi dedlfix,

            was am Ende mehr Aufwand bedeutet, als es in meinen Augen Nutzen bringt.

            Da hast du schon recht. Da ich es aber modular auch woanders, also
            nicht nur für News einsetzen möchte, will ich schon jede Eventualität
            erschlagen wissen. Ging mir bisschen auch nur darum, ob sowas in
            einem Statement möglich ist, man will ja auch noch was lernen ;)

            Deine Idee mit der zweiten Abfrage wenn Resultsetlänge == 0 finde
            ich ganz gut.

            Danke,
            Lin

            --
            Mister rabbit says, "A moment of realization is worth a thousand prayers."
  2. echo $begrüßung;

    Jetzt würde ich gerne statt der Meldung, direkt einen 404-Status
    schicken (mod_perl), allerdings _nicht_ wenn die Tabelle noch
    leer ist, also noch keine News vorhanden.

    Keine falsche Scheu! Ein SELECT COUNT(*) geht unter MySQL ziemlich schnell. Diese Zahl wird aus den Metadaten der Tabelle ermittelt und nicht etwa durch Durchzählen.

    Wenn du das partout nicht in zwei Abfragen lösen möchtest, fallen mir nur zwei Arten der Verknüpfung ein:

    SELECT NULL AS id, NULL AS title, NULL AS `text`, COUNT(*) AS `count` FROM tbnews  
    UNION  
    SELECT id, title, `text`, NULL FROM tbnews WHERE id = 12;
    

    Der erste Datensatz des Ergebnisses enthält außer null-Werten nur die Anzahl, der zweite ist nicht vorhanden oder enthält die News.

    SELECT COUNT(c.*) AS `count`, n.id, n.title, n.`text` FROM tbnews c  
    LEFT JOIN tbnews n ON n.id = 12
    

    Diese zweite Lösung funktioniert aber nicht. Es wird mir ein Syntax-Fehler in Nähe des * vorgeworfen, war wohl doch zu abwegig.

    echo "$verabschiedung $name";

  3. Hey,

    Danke für euer Antworten.
    Ich werd mich wohl ein bisschen mit UNION beschäftigen, oder halt
    eine extra Abfrage einbauen.

    Schönes WE noch,
    Lin

    --
    Mister rabbit says, "A moment of realization is worth a thousand prayers."