web-dev-qa-db-fra.com

Afficher les résultats du curseur pyodbc sous forme de dictionnaire python

Comment sérialiser la sortie du curseur pyodbc (à partir de .fetchone, .fetchmany ou .fetchall) en tant que dictionnaire Python?

J'utilise bottlepy et j'ai besoin de renvoyer dict pour qu'il puisse le renvoyer sous forme de fichier JSON.

42
Foo Stack

Si vous ne connaissez pas les colonnes à l'avance, utilisez cursor.description pour créer une liste de noms de colonnes et Zip avec chaque ligne pour générer une liste de dictionnaires. L'exemple suppose que la connexion et la requête sont construites:

>>> 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'}]
87
Bryan

En utilisant le résultat de @ Beargle avec bottlepy, j'ai pu créer cette requête très concise exposant le point de terminaison:

@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()]}
8
Foo Stack

Voici une version abrégée que vous pourrez peut-être utiliser

>>> 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()]

Comme vous le savez peut-être lorsque vous ajoutez * à une liste, vous supprimez la liste en laissant les entrées individuelles de la liste comme paramètres de la fonction que vous appelez. En utilisant Zip, nous sélectionnons les entrées 1 à n et les zippons ensemble comme une fermeture à glissière dans votre pantalon. 

donc en utilisant

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

vous recevez 

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

Comme la description est un tuple avec des tuples, où chaque tuple décrit l'en-tête et le type de données de chaque colonne, vous pouvez extraire le premier de chaque tuple avec

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

équivalent à 

>>> columns = [column[0] for column in cursor.description]
3
Tommy Strand

Principalement en dehors de la réponse @Torxed, j'ai créé un ensemble complet et généralisé de fonctions pour trouver le schéma et les données dans un dictionnaire:

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))

N'hésitez pas à utiliser tous les codes-golf pour réduire les lignes; mais en attendant, ça marche!

;)

2
Foo Stack

Je sais que cette question est ancienne, mais elle m’a aidé à comprendre comment faire ce que j’avais besoin, ce qui est légèrement différent de ce que demande OP, c’est pourquoi j’ai pensé partager pour aider tous ceux qui en ont besoin Si vous souhaitez généraliser complètement une routine qui effectue des requêtes de sélection SQL, mais que vous devez référencer les résultats par un numéro d'index, et non par un nom, vous pouvez le faire avec une liste de listes au lieu d'un dictionnaire. Chaque ligne de données renvoyées est représentée dans la liste renvoyée sous forme de liste de valeurs de champs (colonnes). Les noms de colonne peuvent être fournis en tant que première entrée de la liste renvoyée. L'analyse de la liste renvoyée dans la routine d'appel peut donc être très simple et flexible. De cette manière, la routine effectuant l'appel à la base de données n'a pas besoin de connaître les données qu'elle traite. Voici une telle routine:

    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
1
Grimravus

Dans les cas où le curseur n'est pas disponible - par exemple, lorsque les lignes ont été renvoyées par un appel de fonction ou une méthode interne, vous pouvez toujours créer une représentation du dictionnaire à l'aide de row.cursor_description.

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

En supposant que vous connaissiez vos noms de colonnes! De plus, voici trois solutions différentes,
vous voulez probablement regarder le dernier!

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

C'est une version indexée, pas la plus belle solution mais cela fonctionnera .. Un autre serait d'indexer le nom de la colonne sous forme de clé de dictionnaire avec une liste dans chaque clé contenant les données dans l'ordre du numéro de ligne. en faisant:

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

En écrivant cela, je comprends que faire for col in colnames pourrait être remplacé par for colindex in range(0, len()) mais vous avez l’idée ... L’exemple le plus récent serait utile pour ne pas récupérer toutes les données, mais une ligne à la fois, par exemple:

Utiliser dict pour chaque ligne de données

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']

Obtenir des noms de tables (je pense .. grâce à Foo Stack):
une solution plus directe de beargle ci-dessous!

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]]
0
Torxed