NOOB: SELCT Ergebnis, 1. Ergebnis wird immer übersprungen

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
saltcream
User
Beiträge: 4
Registriert: Samstag 5. März 2016, 21:28

Hallo Gemeinde,

ich fange frisch mit Python an, komme eigentlich aus der PhP - Ecke, weiß also was Variablen und Datentypen sind.
System ist 3.4.4 , 64 bit mit entsprechendem MySQL-Connector. Technisch funktioniert auch alles. Aber:
Den Unterschied zwischen fetchall() und fetchone() habe ich verstanden, aber in der Praxis verwundert mich das Ergebnis, bei dem ich trotz Manuals und Lernvideos einfach nicht weiterkomme.

Ich habe eine MySQLDatenbank mit einer Tabelle mit 18 Einträgen (zum Testen), deren Einträge eine fortlaufende ID von 1-18 haben.
Jetzt kommt mein Problem.

Code: Alles auswählen

import mysql.connector
conn=mysql.connector.connect(user='user', password='password', host='192.168.130.20', database='datenbank')
mycursor=conn.cursor()
mycursor.execute("SELECT * FROM tabelle")
ergebnis=mycursor.fetchone()
for ergebnis in mycursor:
        print("ID: ", ergebnis[0])
        print("Hersteller: ", ergebnis[2])
        print("Modell: ", ergebnis[1])
        print("-------")
Ergebnis: Datensatz 4, Datensatz 3, Datensatz 12, ...., Datensatz 1,.....Datensatz 18

Das Ergebnis sind alle 18 Datensätze, jedoch komplett durcheinander, obwohl in der Tabelle ordentlich nach ID sortiert.
Die "gleiche" Abfrage in PhP ergibt eine Ausgabe von Datensatz1 bis Datensatz 18.
Wenn Python also von oben nach unten ausliest, müsste schon jetzt 1-18 sortiert ausgeben werden, tut es aber nicht?!
OK, also extra sortiert ausgeben:

Code: Alles auswählen

mycursor.execute("SELECT * FROM tabelle ORDER BY ID")
ergebnis=mycursor.fetchone()
for ergebnis in mycursor:
        print("ID: ", ergebnis[0])
        print("Hersteller: ", ergebnis[2])
        print("Modell: ", ergebnis[1])
        print("-------")
Ergebnis:
Datensatz 2
Datensatz 3
Datensatz 4
....
Datensatz 18

Wo ist Datensatz 1?
Teste ich mal andersrum:

Code: Alles auswählen

mycursor.execute("SELECT * FROM tabelle WHERE ID <= 3 ORDER BY ID")
ergebnis=mycursor.fetchone()
for ergebnis in mycursor:
        print("ID: ", ergebnis[0])
        print("Hersteller: ", ergebnis[2])
        print("Modell: ", ergebnis[1])
        print("-------")
Ergebnis:
Datensatz 2
Datensatz 3

Auch hier kein Datensatz 1.
Nochmal Gegentest:

Code: Alles auswählen

mycursor.execute("SELECT * FROM actioncam WHERE ID > 10 ORDER BY ID")
ergebnis=mycursor.fetchone()
for ergebnis in mycursor:
        print("ID: ", ergebnis[0])
        print("Hersteller: ", ergebnis[2])
        print("Modell: ", ergebnis[1])
        print("-------")
Ergebnis:
Datensatz 12
Datensatz 13
...
Datensatz 18

Er überspringt also stets den 1. gefundenen Datensatz, in diesem Fall fehlt Datensatz 11.
Das Ergebnis von fetchall() zeigt mir natürlich das komplette Ergebnis (inkl. 1. Datensatz) an, ich brauche aber fetchone() zur Bearbeitung und Anzeige einzelner Datensätze.

Jetzt die Masterfrage(n): wieso zeigt er das Ergebnis nicht in der Reihenfolge an, wie die Datensätze in der Tabelle sequentiell vorkommen (also 1-18) und wieso überspringt er ständigden ersten gefundenen Datensatz? Habe ich irgendwo Pflaumen auf den Augen?
Bitte um Nachsicht, wenn das der Fall ist! ;)
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

saltcream hat geschrieben:Jetzt die Masterfrage(n): wieso zeigt er das Ergebnis nicht in der Reihenfolge an, wie die Datensätze in der Tabelle sequentiell vorkommen (also 1-18)
Du hast hier ein falsches Verständnis von Datenbanken. Die Datensätze in einer relationalen Datenbank haben keine Reihenfolge, zumindest keine die du irgendwie sinnvoll herleiten könntest und die man dir auch noch garantieren würde. Genau dafür gibt es ja die Sortierung mittels ORDER BY.

Ansonsten bleibt mir nur noch die Frage nach dem Sinn des Einsatzes von fetchone. Du liest explizit mit fetchone vor der Schleife den ersten Satz aus dem Ergebnis und machst damit nichts. Dann läufst du über die restlichen vorhandenen Ergebnisse und wunderst dich warum der zuvor bereits geholte und ignorierte Datensatz nicht vorhanden ist.
BlackJack

@saltcream: Wo Du `fetchall()` erwähnst: Das hat bei MySQL eine sehr komische und IMHO sehr überraschende Semantik: man kann dort mit `fetchall()` alle Datensätze abfragen und danach *trotzdem* über den Cursor iterieren und bekommt dabei alle Datensätze *nochmal*. Das ist ein Verhalten auf das man sich *nicht* verlassen sollte. Das ist nirgends garantiert und auch völlig abwegig IMHO.
saltcream
User
Beiträge: 4
Registriert: Samstag 5. März 2016, 21:28

Zur Reihenfolge. Eine Reihenfolge in den Tabellen ist sehr wohl vorhanden. Die muss sich zwar nicht immer mit einer fortlaufenden ID aufsteigend decken, dennoch gibt es eine Reihenfolge.
In meinem Fall ist diese identisch mit der aufsteigenden ID. Einmal in der Listung in MySQL selber (der SELECT in der SQL GUI gibt ja auch das Ergebnis 1-18 raus), in PHP UND auch in Python selbst, wenn ich KEIN fetchone() nutze!
Beispiel:

Code: Alles auswählen

mycursor.execute("SELECT * FROM tabelle WHERE ID > 10")
for ergebnis in mycursor:
        print("ID: ", ergebnis[0], ergebnis[2], ergebnis[1])
       
==>
ID: 11 Rollei 400
ID: 12 Rollei 410
ID: 13 Rollei 420
ID: 15 Sony FDR-X1000 VR
ID: 18 GoPro Hero 4 Black
ID: 16 SJCAM M10+
ID: 17 SJCAM M20
ID: 14 Drift Stealth 2
Dagegen:

Code: Alles auswählen

alle.execute("SELECT ID FROM tabelle WHERE ID > 10")
ergebnis2=alle.fetchall()
print("ID:", ergebnis2)
==>ID: [(11,), (12,), (13,), (14,), (15,), (16,), (17,), (18,)]

Sorry, das macht für mich Python-Einsteiger wenig Sinn, bzw. ist es schwer verständlich, gerade wenn man mit PHP solche Verhaltensweisen gar nicht kennt?!

Zum übersprungenen Ergebnis: Das waren wohl dann die Pflaumen!
Hatte mich vom Quellcode in http://www.python-kurs.eu/sql_python.php blenden lassen, die auch erst die Zuweisung und dann die Schleife machen, bei fetchall() wohlgemerkt *g*

Code: Alles auswählen

cursor.execute("SELECT * FROM employee") 
print("fetchall:")
result = cursor.fetchall() 
for r in result:
    print(r)
Aber da hast du natürlich vollkommen recht, jetzt wo ich sofort die Schleife ansetzte (Code siehe oben) überspringt er nicht mehr den ersten gefundenen Datensatz! Danke dir, das war der "Eye-Opener" ;)

@blackjack
Ich befürchte/hoffe, fetchall() nicht wirklich so oft nutzen zu wollen ;) Außer jetzt in der Lernphase.
Dennoch danke für den Hinweis.
saltcream
User
Beiträge: 4
Registriert: Samstag 5. März 2016, 21:28

PS: Ich vergaß, natürlich kann man ein ORDER BY setzen, mich würde nur die unterschiedliche Herangehensweise in Python und bei fetchall und fetchone im speziellen interessieren, falls das jemand weiß.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@saltcream: es wundert mich, das fetchall auch noch sortiert, weil sortieren ist ja teuer und wenn es nicht explizit gewünscht wird... Wenn es nicht irgendwo dokumentiert ist, sollte man sich auf keinen Fall darauf verlassen, weil es könnte in der nächsten Version schon anders sein; auch sonst nicht, weil alle anderen Datenbankanbindungen auch bei fetchall unsortierte Ergebnisse liefern.
BlackJack

@saltcream: Es gibt keine feste Reihenfolge. Natürlich müssen die Datensätze in irgendeiner Reihenfolge geliefert werden, und das kann auch bei jedem Abrufen die gleiche sein, aber wovon die abhängt und ob es tatsächlich immer die selbe ist, das garantiert Dir halt niemand. Die Reihenfolge wird in der Regel die sein, die für das DBMS am wenigsten zusätzlich Arbeit erfordert. Also die Reihenfolge auf dem Hintergrundspeicher oder der Datenstruktur in der der Index gespeichert ist für den bei der Abfrage zugegriffen wird. Wenn Du eine bestimmte Sortierung *sicher* haben möchtest, dann musst Du mit ORDER BY dafür sorgen. Das gilt für SQL und auch für jede Programmiersprache.

Ich habe glaube ich immer noch nicht so ganz verstanden was Du da nicht verstehst, oder wovon Du den Sinn nicht siehst. Könnte daran liegen das ich nicht weiss was Du von PHP kommend erwartest‽ Zumal PHP verschiedene APIs anbietet die unterschiedlich funktionieren.

Es gibt drei Varianten: `fetchone()` wenn Du wirklich nur einen Datensatz erwartest, also in der Regel nur wenn die Abfrage auch wirklich nur einen Datensatz liefert. `fetchall()` wenn Du alle Datensätze abfragen willst, weil Du die *auf einmal* im Speicher benötigst, oder iterieren über den Cursor, wenn die Datensätze satzweise verarbeitet werden sollen.

Bei dem verlinkten Kurs: Vergiss sofort das Beispiel wo Werte mit `format()` in eine Zeichenkette mit SQL formatiert werden. Böses Foul. Im schlimmsten Fall Einfallstor für eine SQL-Injection, im besten Fall ineffizient weil die Datenbank die SQL-Anweisung nicht unabhängig von den Werten behandeln und damit Ablaufpläne cachen kann.
saltcream
User
Beiträge: 4
Registriert: Samstag 5. März 2016, 21:28

Ich habe nicht verstanden, warum fetchall und fetchone unterschiedlich sortieren. Unabhängig von anderen Programmiersprachen.
Aber im Endeffekt ist es egal, sortiere ich halt in der SQL-Anweisung.
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

saltcream hat geschrieben:Ich habe nicht verstanden, warum fetchall und fetchone unterschiedlich sortieren. Unabhängig von anderen Programmiersprachen.
Aber im Endeffekt ist es egal, sortiere ich halt in der SQL-Anweisung.
Weil sie garnicht sortieren! Versuch mal ein Dictionary zu befüllen und gibt dir die Keys mit `.keys()` aus, das ist auch nicht sortiert! Die Datenbank liefert Werte zurück, die können zufällig sortiert sein, müssen aber nicht. fetchall und fetchone reichen lediglich die Werte von der Datenbank an dich weiter (wenn implizit sortiert wird ist das imo falsch).
the more they change the more they stay the same
BlackJack

@saltcream: Grundsätzlich gilt das jede Abfrage, egal ob nun mit `fetchall()` oder mit `fetchone()`, auch bei gleicher Abfrage jedesmal unterschiedliche Reihenfolgen liefern kann (wenn im SQL keine Sortierung angewiesen wird). Um die Reihenfolge zu verstehen müsste man wissen wie die Datenbank das intern implementiert. Aber das will man ja gar nicht wissen, denn das sind Implementierungsdetails um die man sich nicht kümmern möchte. Sonst würde man diese Aufgaben nicht an ein DBMS delegieren.

Das im Hinterkopf behaltent, kann man sich natürlich trotzdem Gedanken machen, aber dann sollte man zumindest gleiche Abfragen verwenden! Du hast einmal eine Abfrage die nur die ID-Abfragt, also nur auf einer Spalte operiert für die es *sehr* wahrscheinlich einen Index gibt. Das heisst dafür braucht die Datentabelle überhaupt nicht abgefragt werden. Das wiederum führt zur Reihenfolge die bei der Indexabfrage geliefert wird. Wenn das beispielsweise eine sortierter Baum ist, dann könnte das erklären warum die Daten in der ”richtigen” Reihenfolge geliefert werden. Oder die Datenbank hat den kompletten Index bereits sortiert im Speicher gecached für die Bereichsanfrage. Vielleicht sogar weil Du vorher die andere Anfrage gemacht hast, die ja auch die ID betrifft. Es kann also auch passieren das die Reihenfolge in der die Datensätze geliefert werden, davon abhängt was Du vorher abgefragt hast und welche (Teil)Ergebnisse deswegen noch im Cache der Datenbank liegen könnten.

Die Datenbank wird in der Regel immer versuchen so effezient wie möglich die Datensätze zusammenzustellen. Und das muss nicht eine irgendwie sortierte Reihenfolge sein. Das ist sogar relativ unwahrscheinlich, denn wenn die Daten nicht zufällig sortiert vorliegen, auf dem Hintergrundspeicher oder im Cache, dann ist sortieren ein zusätzlich Arbeitsschritt, der bei grösseren Datenmengen zudem noch reichlich zusätzlichen Arbeitsspeicher verbrauchen kann, und im Extremfall noch Platz auf dem Hintergrundspeicher benötigt, falls nicht alles in den Arbeitsspeicher passt.
Antworten