如何从SQLite查询中获取字典?

180
db = sqlite.connect("test.sqlite")
res = db.execute("select * from table")

通过迭代,我得到与行对应的列表。

for row in res:
    print row

我可以获取列的名称

col_name_list = [tuple[0] for tuple in res.description]

但是是否有一些函数或设置可以获取字典而不是列表?

{'col1': 'value', 'col2': 'value'}

还是我必须自己去做?


可能是重复的问题:Python - mysqlDB,sqlite结果作为字典 - vy32
8
这个问题来自2010年7月,你链接的那个是2010年11月的。所以那个是重复的。而且顾名思义,相反的评论已经被放在那个帖子上了 :-) - aneroid
16个回答

282

你可以使用row_factory,就像文档中的示例一样:

import sqlite3

def dict_factory(cursor, row):
    d = {}
    for idx, col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d

con = sqlite3.connect(":memory:")
con.row_factory = dict_factory
cur = con.cursor()
cur.execute("select 1 as a")
print cur.fetchone()["a"]

或者按照文档中这个例子后面给出的建议进行操作:

如果返回元组不足以满足您的需求, 并且您希望基于名称访问列,那么您应该考虑将 row_factory设置为高度优化的 sqlite3.Row类型。Row提供了基于索引和大小写不敏感的名称访问列的两种方法,几乎没有任何内存开销。它通常比您自己的自定义基于字典的方法或甚至是基于db_row的解决方案更好。

以下是第二个解决方案的代码:

con = sqlite3.connect(…)
con.row_factory = sqlite3.Row   #   add this row
cursor = con.cursor()

如果您的列名中有特殊字符,例如 SELECT 1 AS "dog[cat]",那么 cursor 将无法正确描述以创建字典。 - Crazometer
我已经设置了 connection.row_factory = sqlite3.Row,并尝试了如下的 connection.row_factory = dict_factory,但是 cur.fetchall() 仍然返回一个元组列表 - 有任何想法为什么这不起作用? - Stefan Falk
1
@displayname,文档中不是说“它试图在大多数功能上模仿元组”吗?我相当确定它与collections.namedtuple所提供的类似。当我使用cur.fetchmany()时,我会得到像<sqlite3.Row object at 0x...>这样的条目。 - ony
5
即使7年过去了,这个答案仍然是我在SO上找到的最有用的复制粘贴内容。谢谢! - WillardSolutions

71

虽然Adam Schmideg和Alex Martelli的回答中部分提到了这个问题的答案,但我仍然想回答这个问题。为了让像我一样有同样问题的人可以很容易地找到答案。

conn = sqlite3.connect(":memory:")

#This is the important part, here we are setting row_factory property of
#connection object to sqlite3.Row(sqlite3.Row is an implementation of
#row_factory)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute('select * from stocks')

result = c.fetchall()
#returns a list of dictionaries, each item in list(each dictionary)
#represents a row of the table

56
目前fetchall()函数似乎返回sqlite3.Row对象。但是,这些对象可以通过使用dict()函数简单地转换为字典:result = [dict(row) for row in c.fetchall()] - Gonçalo Ribeiro
不幸的是,sqlite3.Rowdict 不兼容,因为 row.get('col') 失败了。唉...我不得不编写自己的内容并猴子补丁 Row 类。 - John Henckel
是的,由sqlite3.Row工厂返回的对象是不同的,可以使用dir(row)来检查两种类型的可用方法。使用sqlite3.Row,调用row.keys()会返回['col'](或列的列表),而row['col']会返回该列的值,这对许多目的来说可能已经足够了。 - undefined

24

即使使用sqlite3.Row类--你仍然无法使用字符串格式化,如:

print "%(id)i - %(name)s: %(value)s" % row
为了解决这个问题,我使用一个帮助函数来获取行并转换为字典。只有在字典对象比行对象更合适时才使用它(例如对于像字符串格式化这样的东西,在那里行对象不能本地支持字典API)。但是在其他情况下都使用行对象。
def dict_from_row(row):
    return dict(zip(row.keys(), row))       

10
sqlite3.Row实现了映射协议,您只需要执行print "%(id)i - %(name)s: %(value)s" % dict(row)即可。 - Mzzzzzz

24

连接到SQLite后:con = sqlite3.connect(.....),只需运行以下命令:

con.row_factory = sqlite3.Row

看这里!


9

正如@gandalf的答案所述,你需要使用conn.row_factory = sqlite3.Row,但结果不是直接的字典。你还需要在最后一个循环中添加一个额外的“cast”到dict

import sqlite3
conn = sqlite3.connect(":memory:")
conn.execute('create table t (a text, b text, c text)')
conn.execute('insert into t values ("aaa", "bbb", "ccc")')
conn.execute('insert into t values ("AAA", "BBB", "CCC")')
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute('select * from t')
for r in c.fetchall():
    print(dict(r))

# {'a': 'aaa', 'b': 'bbb', 'c': 'ccc'}
# {'a': 'AAA', 'b': 'BBB', 'c': 'CCC'}

8

来自PEP 249

Question: 

   How can I construct a dictionary out of the tuples returned by
   .fetch*():

Answer:

   There are several existing tools available which provide
   helpers for this task. Most of them use the approach of using
   the column names defined in the cursor attribute .description
   as basis for the keys in the row dictionary.

   Note that the reason for not extending the DB API specification
   to also support dictionary return values for the .fetch*()
   methods is that this approach has several drawbacks:

   * Some databases don't support case-sensitive column names or
     auto-convert them to all lowercase or all uppercase
     characters.

   * Columns in the result set which are generated by the query
     (e.g.  using SQL functions) don't map to table column names
     and databases usually generate names for these columns in a
     very database specific way.

   As a result, accessing the columns through dictionary keys
   varies between databases and makes writing portable code
   impossible.

所以,是的,请自己动手。

不同的数据库之间可能会有差异,比如SQLite 3.7和3.8之间的差异? - Nucular
@user1123466:就像SQLite、MySQL、Postgres、Oracle、MS SQL Server和Firebird之间的选择...... - Ignacio Vazquez-Abrams

6

简化版:

db.row_factory = lambda c, r: dict([(col[0], r[idx]) for idx, col in enumerate(c.description)])

5

与前述解决方案类似,但更加紧凑:

db.row_factory = lambda C, R: { c[0]: R[i] for i, c in enumerate(C.description) }

这段代码对我有效,而上面的答案db.row_factory = sqlite3.Row对我无效(因为它导致了JSON TypeError)。 - ballade4op52

5

在我的测试中速度最快:

conn.row_factory = lambda c, r: dict(zip([col[0] for col in c.description], r))
c = conn.cursor()

%timeit c.execute('SELECT * FROM table').fetchall()
19.8 µs ± 1.05 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

vs:

conn.row_factory = lambda c, r: dict([(col[0], r[idx]) for idx, col in enumerate(c.description)])
c = conn.cursor()

%timeit c.execute('SELECT * FROM table').fetchall()
19.4 µs ± 75.6 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

你决定 :)

2

获取查询结果

output_obj = con.execute(query)
results = output_obj.fetchall()

选项1) 使用Zip的显式循环

for row in results:
    col_names = [tup[0] for tup in output_obj.description]
    row_values = [i for i in row]
    row_as_dict = dict(zip(col_names,row_values))

选项2)使用字典推导式加快循环速度

for row in results:
    row_as_dict = {output_obj.description[i][0]:row[i] for i in range(len(row))}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接