Datenbank API und die Abfrage von generierten Schlüsseln

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
ghostrifle
User
Beiträge: 2
Registriert: Sonntag 23. Oktober 2005, 18:25

Hi,

ich hab'mich soeben ein klein wenig in die Datenbankschnittstellen von python eingearbeitet. SELECTs sowie INSERTs sind soweit kein Problem, jedoch bekomm ich es nicht auf die Backe generierte Schlüssel mittels python zu erhalten.

MySQL z.B. kennt in der C-API eine Funktion welche mysql_last_insert_id() (oder so ähnlich) lautet. Leider kann ich in der python API dazu nichts finden. Auch in der API Spezifikation(http://www.python.org/peps/pep-0249.html) find ich dazu nix.

Das kann doch nicht so schwer sein ,oder ?? Hab'schon gegoogelt etc aber keinen brauchbaren Lösungsansatz gefunden! Die Jungs im IRC auf freenode wussten dazu auch nicht viel.

Auf einer Seite fande ich dann etwas zu Cursor und insert_id..aber auch das funktioniert nicht... hier ein klein wenig Code von mir:

Code: Alles auswählen

# copies the configuration tables
def copy_configuration_table(cursorV1,cursorV2):
	print 'copying configuration tables...'
	cursorV1.execute('SELECT page_header,page_footer FROM t6_config')
	values = cursorV1.fetchone()

	exStatement = 'INSERT INTO configuration (page_header,page_footer) VALUES(\'%s\',\'%s\')' % (values[0],values[1])
	cursorV2.execute(exStatement)
	print int(cursorV2.insert_id())
	print 'copying configuration tables...DONE'
Bye, Alex

Edit (Leonidas): Code in Python-Tags gesetzt.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

ghostrifle hat geschrieben:Auch in der API Spezifikation(http://www.python.org/peps/pep-0249.html) find ich dazu nix.
Was ist hiermit:
pep-0249 hat geschrieben: Cursor Attribute .lastrowid

This read-only attribute provides the rowid of the last
modified row (most databases return a rowid only when a single
INSERT operation is performed). If the operation does not set
a rowid or if the database does not support rowids, this
attribute should be set to None.

The semantics of .lastrowid are undefined in case the last
executed statement modified more than one row, e.g. when
using INSERT with .executemany().

Warning Message: "DB-API extension cursor.lastrowid used"

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
ghostrifle
User
Beiträge: 2
Registriert: Sonntag 23. Oktober 2005, 18:25

:oops: uhh... das ist nun peinlich. Hab'ich irgendwie übersehen.

Danke für den Hinweis !
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Aber gut das zu mal nachgefragt hast, ich kannte .lastrowid auch noch nicht und kann es gebrauchen :lol: Ist schließlich viel einfacher, als nach einem INSERT ein SELECT nach dem ID zu machen ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Mist! Jetzt hab ich doch schön gebrauch von lastrowid gemacht und lokal funktioniert alles prima, aber auf Hosteurope mal wieder nicht:
exceptions.AttributeError : DictCursor instance has no attribute 'lastrowid'
Ich geh mal davon aus, es liegt mal wieder an der alten MySQLdb :( Leider steht mir da nur die v0.9.1 zur Verfügung...

Gibt es evlt. ein work-a-round den ich erstellen kann? Ist lastrowid eigentlich eine "normale" SQL-Funktion, die ich unabhängig vom DB-Modul nutzten kann???

EDIT: Da gibt es wohl was: http://dev.mysql.com/doc/refman/4.0/de/ ... rt-id.html

EDIT2:
OK es geht also mit:

Code: Alles auswählen

SELECT LAST_INSERT_ID()
Ich benutze lastrowid z.B. so:

Code: Alles auswählen

return self.cursor.lastrowid
Nun frage ich mich, ob ich den cursor nicht mit der select-Abfrage erweitern kann, wenn lastrowid halt nicht existiert...
Einfach wäre das wenn es return self.cursor.lastrowid() lauten würde... Dann kann ich die Funktion recht easy erweitern/nachbilden...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

ghostrifle hat geschrieben:Auf einer Seite fande ich dann etwas zu Cursor und insert_id..aber auch das funktioniert nicht... hier ein klein wenig Code von mir:
AUA!!!!!

Mach das doch bitte so:

Code: Alles auswählen

# copies the configuration tables
def copy_configuration_table(cursorV1,cursorV2):
	print 'copying configuration tables...'
	cursorV1.execute('SELECT page_header,page_footer FROM t6_config')
	values = cursorV1.fetchone()

	exStatement = 'INSERT INTO configuration (page_header,page_footer) VALUES(%s, %s)'
	cursorV2.execute(exStatement, (values[0], values[1]))
	print int(cursorV2.insert_id())
	print 'copying configuration tables...DONE'
Und da das mit "%s" nur bei paramstyle "pyformat/format" funktioniert schau dir mal im Wiki folgende Seite an: http://pythonwiki.pocoo.org/Dict_Cursor
TUFKAB – the user formerly known as blackbird
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

blackbird hat geschrieben:Mach das doch bitte so:

Code: Alles auswählen

print int(cursorV2.insert_id())
Aber moment mal... Die DB-API schreibt doch aber vor, das der cursor das Attribut .lastrowid haben soll und nicht das es eine Methode .insert_id() existieren muß, siehe: http://www.python-forum.de/viewtopic.ph ... owid#25124

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
mitsuhiko
User
Beiträge: 1790
Registriert: Donnerstag 28. Oktober 2004, 16:33
Wohnort: Graz, Steiermark - Österreich
Kontaktdaten:

jens hat geschrieben:
blackbird hat geschrieben:Mach das doch bitte so:

Code: Alles auswählen

print int(cursorV2.insert_id())
Aber moment mal... Die DB-API schreibt doch aber vor, das der cursor das Attribut .lastrowid haben soll und nicht das es eine Methode .insert_id() existieren muß, siehe: http://www.python-forum.de/viewtopic.ph ... owid#25124
Ich hab nur das fehlendes quoting bemängelt. Den Rest nichtmal angesehen
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

blackbird hat geschrieben:Ich hab nur das fehlendes quoting bemängelt. Den Rest nichtmal angesehen
:(

Also ich verstehe das nicht so ganz... Hab mir jetzt ein Test geschrieben:

Code: Alles auswählen

...
print "lastrowid:", hasattr(cursor, "lastrowid")
print "insert_id:", hasattr(cursor, "insert_id")
...
Bei mySQLdb v1.2.1g3 sieht das so aus:
lastrowid: True
insert_id: False
Bei mySQLdb v0.9.1 so:
lastrowid: 0
insert_id: 1
:evil:

In meiner lokalen, neuen Version von MySQLdb, wird in cursors.py folgendes gemacht:

Code: Alles auswählen

self.lastrowid = db.insert_id()
Das könnte ich ja eigentlich für die alte Version übernehmen... Mal sehen, ob das geht...

EDIT: So einfach geht das aber nicht:

Code: Alles auswählen

if hasattr(cursor, "lastrowid") == False:
    cursor.lastrowid = cursor.insert_id()
ProgrammingError: execute() first
__doc__ = 'Exception raised for programming errors, e.g. ta...t, wrong number\n of parameters specified, etc.'
__getitem__ = <bound method ProgrammingError.__getitem__ of <_...ceptions.ProgrammingError instance at 0x81dec1c>>
__init__ = <bound method ProgrammingError.__init__ of <_mys...ceptions.ProgrammingError instance at 0x81dec1c>>
__module__ = '_mysql_exceptions'
__str__ = <bound method ProgrammingError.__str__ of <_mysq...ceptions.ProgrammingError instance at 0x81dec1c>>
args = ('execute() first',)
EDIT2: OK, ich glaube ich bin fast am Ziel:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: UTF-8 -*-

print "Content-type: text/html; charset=utf-8\r\n"

import cgitb;cgitb.enable()
import os, sys, cgi
import MySQLdb as dbapi

import config # PyLucid's "config.py"
conn   = dbapi.connect(
    host    = config.dbconf["dbHost"],
    user    = config.dbconf["dbUserName"],
    passwd  = config.dbconf["dbPassword"],
    db      = config.dbconf["dbDatabaseName"],
)
cursor = conn.cursor( dbapi.cursors.DictCursor )

def _lastrowid():
    result = cursor.execute("SELECT LAST_INSERT_ID() AS id;")
    result = cursor.fetchall()[0]["id"]
    return result

if hasattr(cursor, "lastrowid") == False:
    cursor.lastrowid = _lastrowid()

print "<pre>"
print "lastrowid:", hasattr(cursor, "lastrowid")
print "insert_id:", hasattr(cursor, "insert_id")
print "-"*80

#-------------------------------------------------

print "cursor.lastrowid:", cursor.lastrowid
print "_lastrowid()....:", _lastrowid()

#-------------------------------------------------

cursor.execute(
    "CREATE TABLE TempTestTable ("
    "id INT( 11 ) NOT NULL AUTO_INCREMENT,"
    "data1 VARCHAR( 50 ) NOT NULL,"
    "PRIMARY KEY ( id )"
    ") COMMENT = 'temporary test table';"
)

#-------------------------------------------------

print "INSERT"
cursor.execute("INSERT INTO TempTestTable (data1) VALUES ('test1');")
print "cursor.lastrowid:", cursor.lastrowid
print "_lastrowid()....:", _lastrowid()

#-------------------------------------------------

print "INSERT"
cursor.execute("INSERT INTO TempTestTable (data1) VALUES ('test2');")
print "cursor.lastrowid:", cursor.lastrowid
print "_lastrowid()....:", _lastrowid()

#-------------------------------------------------

cursor.execute("DROP TABLE TempTestTable")

print "</pre>"
Das dumme ist nur, das ein cursor.lastrowid = _lastrowid() es nicht bringt, weil man normalerweise ein cursor.lastrowid macht und kein cursor.lastrowid() :x

Vorschläge?

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

Wozu gibt's `property()`? Andererseits könntest Du auf diesen nicht gerade portablen Kram verzichten. Wieder andererseits gibt's für das Problem keine elegante portable Lösung.

Gibt's bei MySQL nicht den `serial` Typ? Oder `autoincrement` oder irgend so etwas in der Richtung?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Gibt's bei MySQL nicht den `serial` Typ? Oder `autoincrement` oder irgend so etwas in der Richtung?
Es geht um den autoincrement Wert, nach einem INSERT. Wie geschrieben, kann/soll man das laut PEP mit cursor.lastrowid abfragen können. Das Problem ist allerdings das es noch nicht implementiert ist, in der alten mySQLdb (die ich auf Hosteurope hab). Eine Lösung wäre, die MySQLdb einfach mit zu liefern, aber schöner ist ein Work-a-Round ;)

Den hab ich jetzt: http://pylucid.python-hosting.com/file/ ... m/mySQL.py

Der work-a-round stekt im Cursorobjekt (class IterableDictCursor):

Code: Alles auswählen

    def __getattr__(self, attr):
        if attr == 'lastrowid':
            if hasattr(self._cursor, 'insert_id'):
                # Patch für alte MySQLdb Version, die kein lastrowid (s. PEP-0249)
                # besitzt, aber eine insert_id() Methode hat
                return self._cursor.insert_id()
        return getattr(self._cursor, attr)
Das Klappt nun erstmal nur so, wie ich es gebrauchen kann. Man kann aber auch noch eine zweite Variante einbauen, die einfach das Ergebniss von SELECT LAST_INSERT_ID() zurück liefert, wenn es auch kein .insert_id() gibt ;)

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

So! Eine neue, IMHO bessere Version:

Code: Alles auswählen

class IterableDictCursor(object):

    def __init__(self, cnx, placeholder, prefix):
        self._cursor = cnx.cursor()
        self._placeholder = placeholder
        self._prefix = prefix

        if not hasattr(self._cursor, "lastrowid"):
            # Patch, wenn die DB-API kein lastrowid (s. PEP-0249) hat
            if hasattr(self._cursor, 'insert_id'):
                # Ältere MySQLdb Versionen haben eine insert_id() Methode
                IterableDictCursor.lastrowid = property(IterableDictCursor._insert_id)
            else:
                # Manuelle Abfrage
                IterableDictCursor.lastrowid = property(IterableDictCursor._manual_lastrowid)

    def _insert_id(self):
        return self._cursor.insert_id()

    def _manual_lastrowid(self):
        return self._cursor.execute("SELECT LAST_INSERT_ID() AS id;")

    def __getattr__(self, attr):
        return getattr(self._cursor, attr)

...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

jens hat geschrieben:
BlackJack hat geschrieben:Gibt's bei MySQL nicht den `serial` Typ? Oder `autoincrement` oder irgend so etwas in der Richtung?
Es geht um den autoincrement Wert, nach einem INSERT. Wie geschrieben, kann/soll man das laut PEP mit cursor.lastrowid abfragen können.
Das Attribut ist im Abschnitt `Optional DB API Extensions` beschrieben. Da sollte man sich also sowieso nicht drauf verlassen können, das es das gibt.
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

BlackJack hat geschrieben:Das Attribut ist im Abschnitt `Optional DB API Extensions` beschrieben. Da sollte man sich also sowieso nicht drauf verlassen können, das es das gibt.
Darauf hab ich eigentlich nicht geachtet :oops: Aber ist auch egal, wie du in Zeile 15 bzw. 21 sehen kannst, gibt es auch die komplett Manuelle Abfrage ;)
Ob die allerdings bei allen SQL-Servern funktioniert, weiß ich allerdings noch nicht...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Antworten