PHP Schleifen-Pr0bleme mit "NULL"

Pfandfinder

Lieutenant
Registriert
Nov. 2020
Beiträge
619
ich habe eine Datenbank, in der sich in der Tabelle "bestand" Datensätze in dieser Art befinden:

ID, name1, name2, name3

eine weitere Tabelle "ergebnisliste" wird vom Script erzeugt. eine schleife geht alle Datensätze in bestand durch und prüft immer, ob bereits in der Tabelle "ergebnisliste" ein Datensatz besteht, wo name1, name2 und name3 wie in bestand vorhanden ist. dann wird da drin die Anzahl um 1 erhöht. Falls nicht, wird ein neuer Datensatz eingetragen in ergebnisliste.

PHP:
$statement = $pdo->prepare('SELECT * FROM bestand WHERE name1 = ? AND name2 = ? AND name3 = ?');
$statement->execute(array($row['bestand_name1'], $row['bestand_name2'], $row['bestand_name3']));
$ergebnisliste = $statement->fetch();

if(empty($ergebnisliste))
{
    // anlegen in Tabelle
}else{
    // Anzahl um 1 addieren
}

($row ist aus bestand)

Problem ist aber, wenn name1 gefüllt ist im bestand aber name2 und name3 leer, dann trägt er immer einen neuen datensatz ein... egal ob die Felder leer sind oder NULL. wie kann ich das Problem lösen ?
 
Wo kommen denn die Ergebnisse der "Ergebnisliste" her?

Idealerweise: Diese Ergebnisliste in die Datenbank schreiben, zB als TEMP table, und dann das DBMS den Abgleich durchführen lassen.

Signifikant effizienter.
 
  • Gefällt mir
Reaktionen: madmax2010
Setz ordentliche Unique Keys und arbeite mit ON DUPLICATE KEY UPDATE. Weiterhin ist '' und NULL ein Unterschied, denn NULL evaluiert in einem Vergleich nie zu true, sondern immer zu NULL. Deshalb gibts in SQL speziell den Vergleichsoperator IS [NOT] NULL, wo du gegen NULL prüfen kannst. Wenn deine Felder NULLABLE sind, würde ich das schleunigst ändern, denn daraus ergeben sich häufig nur Probleme und du kannst bei jedem Feld ein OR IS [NOT] NULL hinzufügen. Alternativ verwendest du ein ORM, was den Fall bedeutend besser bewältigt, weil es dynamisch auf = oder IS NULL reagiert.
 
  • Gefällt mir
Reaktionen: RalphS
Da Du es ansprichst, zB Oracle kennt keine leeren Strings.
WHERE column = '' gibt dort immer eine leere Ergebnismenge, egal ob was eingetragen war oder nicht.

Needless to say einfach NULLABLE verbieten geht natürlich auch gegen den Baum. Ich hab nicht immer Daten. Wenn ein Feld NULLABLE sein muß, dann ist das so, dann muß ich das in der Abfrage berücksichtigen und NICHT im Schema.

=> also stellt sich die Frage nach dem verwendeten DBMS.

Für die Abfrage kann COALESCE() helfen. Das sollte von den meisten DBMS in irgendeiner Form unterstützt werden.

Coalesce(a,b,c,...x): Bewerte der Reihe nach die Spalten a, b, c usw bis x, ob da NULL drinsteht. Wenn nein, gib diese zurück. Wenn ja, teste die nächste. Steht überall NULL, gib das letzte Argument zurück (hier x).

COALESCE(a, 0) stellt also sicher, daß meinte INTEGER-Spalte "a" eine numerische 0 zurückliefest, wenn nichts eingetragen war. oder (wenn das DBMS mitspielt -- coalesce(b,'') für Strings.
 
Zuletzt bearbeitet von einem Moderator:
(Ich gehe hier von MySQL aus, da PHP)
RalphS schrieb:
Needless to say einfach NULLABLE verbieten geht natürlich auch gegen den Baum.
Nein das sag ich nicht, das interpretierst du rein. Ich spreche von seinen Feldern in diesem Beispiel - explizit genannt name1, name2, name3. Und wie man im Query sieht, vergleicht er explizit diese Namen und wundert sich dann, warum ein Datensatz doppelt angelegt wird, weil mindestens irgendwo ein NULL enthalten ist, obwohl er '' übermittelt. Das ist eben genau ein Fall, wo NULL zu Problemen führt und man diese damit eben nicht NULLABLE machen sollte. Ich rede hier nicht von Flags, Datetime oder sonstigen Daten.

x = NULL trifft eben einfach nie zu. Der korrekte Weg wäre es die Tabelle zu normalisieren und die Namen über ne Relationstabelle aufzulösen und das dann über die ID und nen JOIN aufzulösen. Aber er macht damit einen Dublettencheck, wofür eben Unique Keys gemacht sind und dafür gibts im INSERT ein ON DUPLICATE KEY UPDATE, wenn ich eben eine Konstellation übergebe, wo ein Unique Key getroffen wird.

Der Query wäre dann wunderbar einfach:
SQL:
INSERT INTO bestand
SET
    name1 = :name1,
    name2 = :name2,
    name3 = :name3,
    anzahl = :anzahl,
    -- ...weitere Spalten...
ON DUPLICATE KEY UPDATE
    anzahl = anzahl + 1
und fertig. Ein Query, die DB kümmert sich um die Constraints. So wie es sein sollte.

COALESCE bringt ihm hier nichts, denn in der Spalte steht nun mal NULL. Das kannst du nur mit x IS [NOT] NULL abfangen. Ein COALESCE bringt ihm beim INSERT was, wenn dort NULL drin stehen würde (was ich extrem unwahrscheinlich halte). Weiterhin würdest du beim Query mit COALESCE, IFNULL o.ä. jeglichen Index torpedieren und nen Full Table Scan provozieren. Beim SELECT hat er dadurch keinen Nutzen.

Deshalb standardmäßig einfach Finger weg von NULL s, erst Recht bei Identifiern. Natürlich kann man deshalb trotzdem NULL s verwenden, aber nen ganzen Aufsatz wann und wo will ich nun auch nicht schreiben. Wie gesagt: Flags, Datetime, ints, Spalten zur Aggregation usw. Da sind NULL s möglich. Aber doch nicht hier und auch nicht bei seinem Query und nicht bei seinen überlieferten Daten. Dann würde der Query auch anders aussehen, da man sich der NULL -Problematik bewusst ist.

Statt Spalten standardmäßig NULLABLE zu machen, sollte es nen expliziten Grund geben, warum eine Spalte NULL beinhalten darf. Falls nicht, hat dort einfach kein NULL drinzustehen. Nie.

https://developpaper.com/why-use-not-null-for-mysql-fields/
(Wobei ich bei Standardwerten für Datetime nicht zustimme und diese ohne Wert NULL sein sollten, statt so nem ominösem Timestamp 0 - bei 0000-00-00 00:00:00 würde ich noch mitgehen.)

Oder anders gefragt: Welchen Nutzen würdest du daraus ziehen, dass [name1,name2,name3] NULL sein könnte? Ausgehend davon, dass die Spalte nicht später via Migration hinzugekommen wäre, wo NULL durchaus Sinn ergibt, da diese Daten noch nicht ergänzt oder gesetzt wurden. Dann würde der Query aber eben auch anders aussehen und sich die Problematik nicht stellen. Bei normalisierten Daten mittels JOIN sowieso nicht.
RalphS schrieb:
Da Du es ansprichst, zB Oracle kennt keine leeren Strings.
Wirklich? Also wenn ich was bestelle und das Kommentarfeld leer lasse, ist das Comment-Feld dann einfach NULL statt leer ''? Und wenn ich keine Telefonnummer angebe, steht in meinem Adressdatensatz auch einfach NULL? Du kannst also nicht unterscheiden ob der Kunde keinen Kommentar (leer) abgegeben hat oder einfach (doof gesagt) "irgendwas schief lief" und die Spalte einfach nicht befüllt wurde? Oder dass keine Telefonnummer angegeben wurde, obwohl er das Formular definitiv ausgefüllt und abgeschickt hat...?
 
  • Gefällt mir
Reaktionen: RalphS
Yuuri schrieb:
Leider ja. Ist absoluter Käse. Wobei es auch, wenn ich die Doku nicht falsch in Erinnerung habe, auf den konkreten Feldtypen ankommt... da bin ich inzwischen nicht mehr so hinterher.

Zunächst erstmal grundlegend: Ja, ich stimme prinzipiell zu, für den konkreten Anwendungsfall mag das so hinkommen. Ich hab es mir nur abgewöhnt, Annahmen zu treffen... was zugegeben auch in Overkill und Mehraufwand resultiert.

Ich sehe spontan halt im Input keinen constraint, auch keinen impliziten, daß ein Feld nicht unbelegt sein kann. Steht im OP ja auch so drin: Felder sind leer und deswegen gibt es die Probleme.

As an aside, wenn schon PHP, dann natürlich Postgres. Hust


Also ja, Feld (nur) dann NULLable, wenn es erforderlich ist.
Primary keys haben nicht NULL zu sein, das ist hoffentlich auch in MySQL ausgeschlossen.
UNIQUE und NULL hätte ich auch ein Problem damit.

Aber wenn ich einen Wert nicht kenne, dann ist er NULL. Nicht DateTime.MinValue, nicht -1, nicht '', sondern NULL.
Du sprichst es ja an. Man sollte zwischen "explizit nichts angegeben" und "ich konnte nichts angeben, weil ich nichts hatte" unterscheiden können.

Hab ich eine Tabelle PKW und da stehen Drei- und Fünftürer drin, dann kann ich bei FrontLR eine Teilenummer reinschreiben, kann für Fünftürern bei BackLR eine Teilenummer reinschreiben... und muß bei den Dreitürern NULL eintragen, die haben nämlich keine.

Auf der anderen Seite gibt es nur sehr sehr wenig Netzwerkkarten ohne MAC-Adresse. Also wirklich ohne. Hier kann und darf ich NOT NULL verlangen.


Ansonsten kommt es konkret für Coalesce() natürlich immer auf die Situation an. Im Beispiel oben habe ich bei meinen Hintertüren NULL eingetragen, weil Dreitürer halt keine Hintertüren haben können. Aber vielleicht will ich eine Summe über erforderliche Hintertüren haben, damit ich meinen Bedarf abschätzen kann. Also fange ich mit COALESCE(BackLR, 0) ab und behandle sie so, als ob eine numerische 0 dagestanden hätte.

Coalesce() insbesondere dann, wenn eine Spalte NULL annehmen kann. Alles andere macht dafür auch keinen Sinn.


Das einzige was ich wirklich zuverlässig sehe, ist daß @Pfandfinder seine Datenbanklogik nach PHP auszulagern versucht. Und da sage ich halt "halt*.
 
  • Gefällt mir
Reaktionen: Pfandfinder
RalphS schrieb:
Hab ich eine Tabelle PKW und da stehen Drei- und Fünftürer drin, dann kann ich bei FrontLR eine Teilenummer reinschreiben, kann für Fünftürern bei BackLR eine Teilenummer reinschreiben... und muß bei den Dreitürern NULL eintragen, die haben nämlich keine.
Und wenn bei BackLR '' drin steht? ;)
In diesem Beispiel muss ich NULL und '' ja nicht unterscheiden.
Was mich an NULL besonders ärgert (zumindest ist das in MSSQL so) ist, dass WHERE Col1 != 1 keine rows selected die in Col1 den Wert NULL haben.
 
Ganz einfach. Bei einer ID wie bei Teilenummer habe ich entweder rein numerische Werte oder ich habe solche, die mit einem CHECK belegt sind, damit nur gültige Einträge möglich sind. Plus, foreign key.

'' ist keine gültige Teilenummer. NULL schon. Und wenn das ein FK war, dann wäre ich GENÖTIGT, einen gültigen(!) Eintrag für '' in der Teiletabelle zu haben. Das führt das gesamte Modell ad absurdum.

Es gibt hier wirklich einen grundlegenden Unterschied zwischen "Kenne ich, ist '' " und "kenne ich nicht, ist NULL".
Wenn ich @floq0r in eine DB bei mir aufnehmen will, kann ich unter Nachname nicht '' eintragen, weil er sehr wahrscheinlich einen hat, welcher sich von '' unterscheidet.
 
Zurück
Oben