Salida de los resultados del cursor de pyodbc como diccionario de Python

83

¿Cómo puedo serializar salida del cursor pyodbc (de .fetchone, .fetchmanyo .fetchall) como un diccionario de Python?

Estoy usando bottlepy y necesito devolver dict para que pueda devolverlo como JSON.

Pila de Foo
fuente
Y sí, noté que esto estaba en las preguntas frecuentes de PEPE249 , sin embargo, eso no cambia mi requisito.
Foo Stack

Respuestas:

163

Si usted no sabe columnas antes de tiempo, utilizar Cursor.description para construir una lista de nombres de columna y cremallera con cada fila para producir una lista de diccionarios. El ejemplo asume que la conexión y la consulta están construidas:

>>> cursor = connection.cursor().execute(sql)
>>> columns = [column[0] for column in cursor.description]
>>> print(columns)
['name', 'create_date']
>>> results = []
>>> for row in cursor.fetchall():
...     results.append(dict(zip(columns, row)))
...
>>> print(results)
[{'create_date': datetime.datetime(2003, 4, 8, 9, 13, 36, 390000), 'name': u'master'},   
 {'create_date': datetime.datetime(2013, 1, 30, 12, 31, 40, 340000), 'name': u'tempdb'},
 {'create_date': datetime.datetime(2003, 4, 8, 9, 13, 36, 390000), 'name': u'model'},     
 {'create_date': datetime.datetime(2010, 4, 2, 17, 35, 8, 970000), 'name': u'msdb'}]
Bryan
fuente
1
no conocía cursor.description. esto me ahorró un montón de tiempo.
TehTris
es necesario envolver paréntesis alrededor de la impresión (columnas) y la impresión (resultados) para que esto funcione
LJT
2
@LJT Solo en python3 ... pero dado que la función print () funciona en python2, es una buena práctica usarla.
Auspex
1
@BenLutgens Porque el ejemplo produce una lista de dictados, no un dictado.
Auspex
1
Actualización: de forma predeterminada, pypyodbc establece minúsculas = Verdadero. Puede sobrescribir esto así: import pypyodbc; pypyodbc.lowercase = Falso. Referencia: enlace
Weihui Guo
13

Usando el resultado de @ Beargle con bottlepy, pude crear esta consulta muy concisa que expone el punto final:

@route('/api/query/<query_str>')
def query(query_str):
    cursor.execute(query_str)
    return {'results':
            [dict(zip([column[0] for column in cursor.description], row))
             for row in cursor.fetchall()]}
Pila de Foo
fuente
3
¿Está esto expuesto a ataques de inyección SQL?
Ben
@Ben ¡Sí! Nunca debe usarlo a menos que esté 1000% seguro de que las solicitudes siempre vendrán de un cliente confiable.
Bora M. Alper
6

Aquí hay una versión de formato corto que podría usar

>>> cursor.select("<your SQL here>")
>>> single_row = dict(zip(zip(*cursor.description)[0], cursor.fetchone()))
>>> multiple_rows = [dict(zip(zip(*cursor.description)[0], row)) for row in cursor.fetchall()]

Como ya sabrá, cuando agrega * a una lista, básicamente elimina la lista, dejando las entradas individuales de la lista como parámetros para la función que está llamando. Al usar la cremallera, seleccionamos la entrada 1 an y la juntamos como la cremallera de tus pantalones.

entonces usando

zip(*[(a,1,2),(b,1,2)])
# interpreted by python as zip((a,1,2),(b,1,2))

usted obtiene

[('a', 'b'), (1, 1), (2, 2)]

Dado que la descripción es una tupla con tuplas, donde cada tupla describe el encabezado y el tipo de datos de cada columna, puede extraer el primero de cada tupla con

>>> columns = zip(*cursor.description)[0]

equivalente a

>>> columns = [column[0] for column in cursor.description]
Tommy Strand
fuente
Con python3.4 obtengo:, TypeError: 'zip' object is not subscriptableasí que no puedo usar el zip(*description)[0]truco.
malat
En Python 3.4, zip es un iterador. Puede envolver el zip en una lista de lista (zip (* descripción)) [0] @malat
Tommy Strand
Guardó una línea con columnsvariable, pero multiplicó la complejidad de la función al calcular los nombres de las columnas para cada fila por separado
Sergey Nudnov
3

Principalmente saliendo de la respuesta @Torxed, creé un conjunto completo de funciones generalizadas para encontrar el esquema y los datos en un diccionario:

def schema_dict(cursor):
    cursor.execute("SELECT sys.objects.name, sys.columns.name FROM sys.objects INNER JOIN sys.columns ON sys.objects.object_id = sys.columns. object_id WHERE sys.objects.type = 'U';")
    schema = {}

    for it in cursor.fetchall():
        if it[0] not in schema:
            schema[it[0]]={'scheme':[]}
        else:
            schema[it[0]]['scheme'].append(it[1])

    return schema


def populate_dict(cursor, schema):
    for i in schema.keys():
        cursor.execute("select * from {table};".format(table=i))

        for row in cursor.fetchall():
            colindex = 0

            for col in schema[i]['scheme']:
                if not 'data' in schema[i]:
                    schema[i]['data']=[]

                schema[i]['data'].append(row[colindex])
                colindex += 1

    return schema

def database_to_dict():
    cursor = connect()
    schema = populate_dict(cursor, schema_dict(cursor))

Siéntase libre de hacer todo el código de golf en esto para reducir las líneas; pero mientras tanto, ¡funciona!

;)

Pila de Foo
fuente
2

Para situaciones en las que el cursor no está disponible, por ejemplo, cuando las filas han sido devueltas por alguna llamada de función o método interno, aún puede crear una representación de diccionario usando row.cursor_description

def row_to_dict(row):
    return dict(zip([t[0] for t in row.cursor_description], row))
Kevin Campbell
fuente
1

Sé que esta pregunta es antigua, pero me ayudó a descubrir cómo hacer lo que necesitaba, que es ligeramente diferente de lo que pedía OP, así que pensé en compartir, para ayudar a cualquier otra persona que necesite lo que yo necesitaba: Si desea generalizar completamente una rutina que realiza consultas de selección de SQL, pero necesita hacer referencia a los resultados mediante un número de índice, no un nombre, puede hacerlo con una lista de listas en lugar de un diccionario. Cada fila de datos devueltos se representa en la lista devuelta como una lista de valores de campo (columna). Los nombres de las columnas se pueden proporcionar como la primera entrada de la lista devuelta, por lo que analizar la lista devuelta en la rutina de llamada puede ser realmente fácil y flexible. De esta manera, la rutina que realiza la llamada a la base de datos no necesita saber nada sobre los datos que está manejando. Aquí hay una rutina de este tipo:

    def read_DB_Records(self, tablename, fieldlist, wherefield, wherevalue) -> list:

        DBfile = 'C:/DATA/MyDatabase.accdb'
        # this connection string is for Access 2007, 2010 or later .accdb files
        conn = pyodbc.connect(r'Driver={Microsoft Access Driver (*.mdb, *.accdb)};DBQ='+DBfile)
        cursor = conn.cursor()

        # Build the SQL Query string using the passed-in field list:
        SQL = "SELECT "
        for i in range(0, len(fieldlist)):
            SQL = SQL + "[" + fieldlist[i] + "]"
            if i < (len(fieldlist)-1):
                SQL = SQL + ", "
        SQL = SQL + " FROM " + tablename

        # Support an optional WHERE clause:
        if wherefield != "" and wherevalue != "" :
            SQL = SQL + " WHERE [" + wherefield + "] = " + "'" + wherevalue + "';"

        results = []    # Create the results list object

        cursor.execute(SQL) # Execute the Query

        # (Optional) Get a list of the column names returned from the query:
        columns = [column[0] for column in cursor.description]
        results.append(columns) # append the column names to the return list

        # Now add each row as a list of column data to the results list
        for row in cursor.fetchall():   # iterate over the cursor
            results.append(list(row))   # add the row as a list to the list of lists

        cursor.close()  # close the cursor
        conn.close()    # close the DB connection

        return results  # return the list of lists
Grimravus
fuente
1

Me gustan las respuestas de @bryan y @ foo-stack. Si está trabajando con postgresql y está usando psycopg2, podría usar algunos beneficios de psycopg2 para lograr lo mismo especificando que el cursorfactory sea a DictCursoral crear su cursor desde la conexión, así:

cur = conn.cursor( cursor_factory=psycopg2.extras.DictCursor )

Así que ahora puede ejecutar su consulta SQL y obtendrá un diccionario para obtener sus resultados, sin la necesidad de mapearlos a mano.

cur.execute( sql_query )
results = cur.fetchall()

for row in results:
    print row['row_no']

Tenga en cuenta que tendrá que hacerlo import psycopg2.extraspara que funcione.

Matteo
fuente
0

¡Suponiendo que conozca los nombres de las columnas! Además, aquí hay tres soluciones diferentes,
¡probablemente quieras ver la última!

colnames = ['city', 'area', 'street']
data = {}

counter = 0
for row in x.fetchall():
    if not counter in data:
        data[counter] = {}

    colcounter = 0
    for colname in colnames:
        data[counter][colname] = row[colcounter]
        colcounter += 1

    counter += 1

Esa es una versión indexada, no es la solución más hermosa, pero funcionará. Otra sería indexar el nombre de la columna como clave de diccionario con una lista dentro de cada clave que contiene los datos en orden de número de fila. haciendo:

colnames = ['city', 'area', 'street']
data = {}

for row in x.fetchall():
    colindex = 0
    for col in colnames:
        if not col in data:
            data[col] = []
        data[col].append(row[colindex])
        colindex += 1

Al escribir esto, entiendo que hacer for col in colnamespodría ser reemplazado por, for colindex in range(0, len())pero entiendes la idea. El último ejemplo sería útil cuando no se obtienen todos los datos, sino una fila a la vez, por ejemplo:

Usando dict para cada fila de datos

def fetchone_dict(stuff):
    colnames = ['city', 'area', 'street']
    data = {}

    for colindex in range(0, colnames):
        data[colnames[colindex]] = stuff[colindex]
    return data

row = x.fetchone()
print fetchone_dict(row)['city']

Obtener nombres de tablas (creo ... gracias a Foo Stack): ¡
una solución más directa de Beargle a continuación!

cursor.execute("SELECT sys.objects.name, sys.columns.name FROM sys.objects INNER JOIN sys.columns ON sys.objects.object_id = sys.columns. object_id WHERE sys.objects.type = 'U';")
schema = {}
for it in cursor.fetchall():
    if it[0] in schema:
       schema[it[0]].append(it[1])
    else:
        schema[it[0]] = [it[1]]
Torxed
fuente
Gracias, pero ¿existe una solución generalizada para cuando no conozco los nombres de mis columnas?
Foo Stack
Sí, se llama sintaxis SQL. Puede consultar en su base de datos los nombres de la tabla contra la que está consultando. stackoverflow.com/questions/4645456/…
Torxed
He escrito un pequeño y agradable recopilador de esquemas generalizados:
Foo Stack
1
cursor.execute("SELECT sys.objects.name, sys.columns.name FROM sys.objects INNER JOIN sys.columns ON sys.objects.object_id = sys.columns. object_id WHERE sys.objects.type = 'U';") schema = {} for it in cursor.fetchall(): if it[0] in schema: schema[it[0]].append(it[1]) else: schema[it[0]] = [it[1]]
Foo Stack
@FooStack Los nombres de las columnas ya se devuelven en cursor.description . No se necesita una consulta separada.
Bryan