Python und MS SQL FILESTREAM

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

Hallo,
Würde gerne wissen wie ich unter Python eine Datei, in diesem Falle, eine Bilddatei in eine MsSQL Filestreamtabelle einfüge. Finde bei Google und auch im Forum dazu nichts weiter. Könnte mir jemand helfen?

Code: Alles auswählen

	tabelle = 'Records'
        bild = "data/Temp/Kartei.gif"
        wer ="Archive"
        test = "123"

        try:
            #verbindung zum server
            con = pyodbc.connect(r"DRIVER={ODBC Driver 17 for SQL Server};"
                                     "SERVER=" + sql_server + ";"
                                     "DATABASE=" + wer + ";"
                                     "UID=" + sql_benutzer + ";"
                                     "PWD=" + sql_pw + ";"
                                     "Trusted_Connection = Yes")
            cur = con.cursor()
        except:
            tkinter.messagebox.showwarning("info", "Keine Verbindung zum Server.")
        else:
            cur.execute("SELECT Id FROM " + tabelle +" WHERE Id LIKE '" + s_name + "%'")
            result = cur.fetchone()
            
            if result is None:
                 cur.execute("insert into " + tabelle + \
                "([SerialNumber], [Chart])values(" + test + ", " + bild +")")
Fehlermeldung:
"([SerialNumber], [Chart])values(" + test + ", " + bild +")")
pyodbc.ProgrammingError: ('42S22', "[42S22] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Ungültiger Spaltenname 'data'. (207) (SQLExecDirectW); [42S22] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Ungültiger Spaltenname 'Temp'. (207); [42S22] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Der mehrteilige Bezeichner 'Kartei.gif' konnte nicht gebunden werden. (4104)

Liebe Grüße.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na du musst den INHALT deiner Bilddatei benutzen. Nicht den Dateinamen. Desweiteren solltest du auf das zusammenstoppeln von Strings mit + verzichten, und format verwendet:

Code: Alles auswählen

"SERVER={sql_server};DATABASE={db}".format(sql_server=sql_server, db=database)
Und last but not least fuer Parameter an das eigentliche SQL statement NICHT strings zusammenbasteln (egal ob mit + oder format), sondern die parametrisierte Form der execute-Methode verwenden. So wie hier dargestellt:

http://pymssql.org/en/stable/pymssql_ex ... compliance

Wenn tabelle ueberall gleich ist, solltest du das auch gleich in den String schreiben. Statt es rein zu formatieren.
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@badi113: Was Du auf jeden Fall *nicht* machen solltest ist SQL per Zeichenkettenoperationen zusammensetzen. Für SQL-Werte gibt es Platzhalter und das zweite Argument von der `execute()`-Methode.

Das nächste was negativ auffällt ist das nackte ``except:``. Da sollte man immer angeben welche Ausnahme(n) damit behandelt werden sollen, denn so wird *jede* Ausnahme behandelt, auch solche die überhaupt gar nichts mit der eigentlichen Verbindung zu tun haben. Beispielsweise wenn Du Dich bei einem Namen vertippt hast, oder wenn einer der Werte die da ”addiert” werden, nicht zu einer Zeichenkette addiert werden kann, oder wenn der Benutzer just in dem Augenblick Strg+C drückt, oder wenn einer der Aufrufe einen RecursionError auslöst, oder wenn der Speicher ausgegangen ist, oder… was auch immer, was man dadurch aber nie erfahren wird wenn alle Ausnahmen gleich behandelt werden.

Zeichenketten und Werte mit ``+`` zusammenstückeln ist auch eher BASIC als Python. Python kennt dafür Zeichenkettenformatierung mit der `format()`-Methode und ab Python 3.6 f-Zeichenkettenliterale.

Datenbankverbindungen und Cursor-Objekte sollte man nach Gebrauch mit der `close()`-Methode schliessen. ``with`` und `contextlib.closing()` sind in dem Zusammenhang nützlich.

Bei der Frage zur Filestreamtabelle muss ich passen, das ist ja wohl kein Standard-SQL, also ist auch die Frage ob man das durch ODBC irgendwie durch bekommt. Einfach einen Pfad/Dateinamen zu übergeben wird wohl nicht funktionieren, denn das SQL wird ja an die Datenbank übertragen und *dort* auf dem Server ausgeführt.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

Hy,
und Danke für die schnellen antworten.
Also die Datenbank wird geschlossen, habe nur vergessen es mit zu kopieren.
Wie lese ich den ein bild aus?

Code: Alles auswählen

with open(bild, "rb") as file:
            binData = file.read()
           
        test = pyodbc.Binary(binData)
in dem ich es in Binary umwandel?.

Das mit format kenn ich garnicht, das kommt bestimmt davon das ich ein Leihe bin, aber gut zu wissen. Da muss ich mich denn über format noch belesen wie ich das einsetzen tue.
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Musst du ausprobieren, laut Doku reicht einfaches einlesen als Bytes. Hast du das mal probiert?
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Das Tabellendesign sieht seltsam aus. Du hast eine ID die Du per like vergleichst, ob es schon ein Duplikat gibt, wobei s_name nirgends definiert ist, dann schreibst Du aber einen neuen Eintrag nur mit einer SerialNumber und dem Bild, aber nicht die ID? Wie passt das zusammen?
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

Hy,
habe mal weiter auprobiert.

Code: Alles auswählen

 
 	s_name = 'Peter'       
        tabelle = 'Records'
        bild = "data/Temp/Kartei.gif"
        wer ="Archive"
        test = "123"
        
        file = open(bild, "rb")
        bild1 = file.read()
        file.close()
           
        
        db = open("Wkz/master8888","w")
        db.write(str(bild1) + '\n')#1
        db.close()

        try:
            #verbindung zum server
            con = pyodbc.connect(r"DRIVER={ODBC Driver 17 for SQL Server};"
                                     "SERVER=" + sql_server + ";"
                                     "DATABASE=" + wer + ";"
                                     "UID=" + sql_benutzer + ";"
                                     "PWD=" + sql_pw + ";"
                                     "Trusted_Connection = Yes")
        except:
            tkinter.messagebox.showwarning("info", "Keine Verbindung zum Server.")
        else:
            cur = con.cursor()
            cur.execute("SELECT Id FROM " + tabelle +" WHERE Id LIKE '" + s_name + "%'")
            result = cur.fetchone()
            
            if result is None:
                cur.execute("insert into " + tabelle + \
                "([SerialNumber], [Chart])values({a}, {b})".format(a=test,  b=bild1))    
                
                
            else:
                tkinter.messagebox.showwarning("info", "Werkzeug schon vorhanden")

            con.commit()
            con.close()
            abbrechen()
Habe die eingabe in die Datenbank auch mal mit Format geschrieben und das bild in bite ausgelesen, bekomme aber immer noch ne fehlermeldung.
Falls das mit dem format so richtig ist :D .

([SerialNumber], [Chart])values({a}, {b})".format(a=test, b=bild1))
pyodbc.ProgrammingError: ('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server]Syntax error, permission violation, or other nonspecific error (0) (SQLExecDirectW)')
Benutzeravatar
__blackjack__
User
Beiträge: 13079
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@badi113: Man formatiert keine Werte in SQL. Dafür sieht die DB-API V2 Platzhalter vor und `execute()` das zweite Argument.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

@_blackjack_: So ganz verstehe ich das mit dem zweiten Argument nicht, kannst du mir mal ein Beispiel geben?.
Wäre echt dankbar.
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

Ich habe da noch was gefunden und ausprobiert, ich weiß auch nicht ob es das ist was ihr meint mit zweiten Argument. :D

Code: Alles auswählen

 if result is None:
                sql_insert = ("INSERT INTO " + tabelle + " ([SerialNumber], [Chart]) "
                "VALUES (%s, %s"
                )
                sql_eintrag = ( test, bild1)
                
                cur.execute(sql_insert, sql_eintrag)
                #cur.execute("insert into " + tabelle + \
                #"([SerialNumber], [Chart])values({a}, {b})".format(a=test,  b=bild1))    
                
                
            else:
                print("weiter")
                tkinter.messagebox.showwarning("info", "Werkzeug schon vorhanden")
                
            con.commit()
            con.close()
            abbrechen()
Bekomme aber immer noch ein Fehler.

cur.execute(sql_insert, sql_eintrag)
pyodbc.ProgrammingError: ('The SQL contains 0 parameter markers, but 2 parameters were supplied', 'HY000').

Klappt das überhaupt mit pyodbc? :?:
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

Schau in der Dokumentation deines Datenbankmoduls wie man ein .execute() ausführt.

Niemals eine SQL Anweisung selbst zusammenbasteln. Immer die Platzhalter in .execute() verwenden. Das hat nichts mit Bequemlichkeit zu tun, sondern vermeidet Sicherheitsprobleme.
Benutzeravatar
sparrow
User
Beiträge: 4187
Registriert: Freitag 17. April 2009, 10:28

1. warum ist dein SQL-Befehl in einer Liste und ist syntaktisch falsch?

2. Wo steht in der pyodbc Dokumentation, dass man %s als Platzhalter verwendet? Den Link würde ich gerne mal sehen.
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

https://dev.mysql.com/doc/connector-pyt ... ecute.html

ist aber nicht pyodbc.

Wie gesagt ich bin anfänger was das alles betrift und probiere mich nun mal aus :D
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du kannst halt nicht jede beliebige doku hernehmen. Sondern musst schon die von pyodbc nehmen.
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

So, jetzt hab ich noch mal was probiert, da ich das hier gefunden hab.
https://github.com/mkleehammer/pyodbc/wiki/Cursor
klappt aber leider auch nicht.

Code: Alles auswählen

            if result is None:
                
                sql_eintrag = [(123,  test ,  bild1)]

                cur.execute("insert into " + tabelle + \
                "(Id, SerialNumber, Chart)values(?)", sql_eintrag)    
                
                
            else:
                print("weiter")
                tkinter.messagebox.showwarning("info", "Werkzeug schon vorhanden")
                
            con.commit()
            con.close()
            abbrechen()
Fehler:

"(Id, SerialNumber, Chart)values(?)", sql_eintrag)
pyodbc.ProgrammingError: ("A TVP's rows must be Sequence objects.", 'HY000')
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du brauchst schon einen Platzhalter PRO Argument. Also 3 in deinem Fall.
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

Hab ich ausprobiert, leider ohne erfolg. :(

Code: Alles auswählen

	if result is None:
                
                sql_eintrag = [(123,  test ,  bild1)]

                cur.execute("insert into " + tabelle + \
                "(Id, SerialNumber, Chart)values(?, ?, ?)", sql_eintrag)    
                
                
            else:
                print("weiter")
                tkinter.messagebox.showwarning("info", "Werkzeug schon vorhanden")
                
            con.commit()
            con.close()
            abbrechen()
Fehler:
"(Id, SerialNumber, Chart)values(?, ?, ?)", sql_eintrag)
pyodbc.ProgrammingError: ('The SQL contains 3 parameter markers, but 1 parameters were supplied', 'HY000')
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Und dann musst du statt einer Liste von drei Parametern eben drei Parameter angeben.
badi113
User
Beiträge: 22
Registriert: Mittwoch 27. März 2019, 14:53

So, habe jetzt variablen einzeln hingesetzt

Code: Alles auswählen

	if result is None:
                
                sql_id = 1234

                cur.execute("insert into " + tabelle + \
                "([Id], [SerialNumber], [Chart])values(?, ?, ?)", sql_id, test,  bild1)    
                
                
            else:
                print("weiter")
                tkinter.messagebox.showwarning("info", "Werkzeug schon vorhanden")
                
            con.commit()
            con.close()
            abbrechen()
Fehler:
"([Id], [SerialNumber], [Chart])values(?, ?, ?)", sql_id, test, bild1)
pyodbc.DataError: ('22018', '[22018] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Operandentypkollision: bigint ist inkompatibel mit uniqueidentifier (206) (SQLExecDirectW); [22018] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Anweisung(en) konnte(n) nicht vorbereitet werden. (8180)')
>>>

Aber müsste sich Id nicht selbst generieren?
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Keine Ahnung. Das ist jetzt ODBC/Microsoft spezifisch. Nicht meine Baustelle. Mein googeln so gut wie dein googeln.
Antworten