我在嵌套循环中的单个sqlite数据库上使用多个游标时遇到问题。我找到了一个适合我的解决方案,但它有限,我没有看到在线记录这个具体问题。我发布的是这样的: - 有明确的问题/解决方案 - 看看是否有更好的解决方案 - 也许我在sqlite3 python模块中发现了一个缺陷
情况如下: 我的Python应用程序将社交关系数据存储在sqlite中。数据集包括两个表之间的一对多关系:myConnections和sharedConnections。前者每个连接都有一行。 sharedConnections表具有0:N行,具体取决于共享的连接数。为了构建结构,我使用嵌套循环。在外部循环中,我访问myConnections中的每一行。在内部循环中,我填充sharedConnections表。代码如下所示:
curOuter = db.cursor()
for row in curOuter.execute('SELECT * FROM myConnections'):
id = row[0]
curInner = db.cursor()
scList = retrieve_shared_connections(id)
for sc in scList:
curInner.execute('''INSERT INTO sharedConnections(IdConnectedToMe, IdShared) VALUES (?,?)''', (id,sc))
db.commit()
结果很奇怪。 sharedConnections
表获取myConnections
中前两个记录的重复条目。他们有点整理。 A的连接,B的连接,然后是A,然后是B。在最初的口吃之后,处理是正确的!例如:
myConnections
-------------
a
b
c
d
sharedConnections
-------------
a->b
a->c
b->c
b->d
a->b
a->c
b->c
b->d
解决方案不完善。不是使用外部循环游标中的迭代器,而是使用SELECT
,然后fetchall()
并遍历结果列表。由于我的数据集非常小,所以没关系。
curOuter = db.cursor()
curOuter.execute('SELECT * FROM myConnections'):
rows = curOuter.fetchall()
for row in rows:
id = row[0]
curInner = db.cursor()
scList = retrieve_shared_connections(id)
for sc in scList:
curInner.execute('''INSERT INTO sharedConnections(IdConnectedToMe, IdShared) VALUES (?,?)''', (id,sc))
db.commit()
你有它。在嵌套循环中对同一sqlite数据库中的不同表使用两个游标似乎不起作用。更重要的是,它不会失败,只会产生奇怪的结果。
所以: - 这真的是最好的解决方案吗? - 有更好的解决方案吗? - 这是一个应该解决的缺陷吗?
解答: 在这一点上,问题已经提出,有一些讨论,我认为我们已经完成了。这是看起来如何:
感谢您的关注和建议。
祝你好运, -Jim
答案 0 :(得分:2)
虽然构建内存列表似乎是最好的解决方案,但我发现使用显式事务减少了外部查询中返回的重复数量。这会使它像:
with db:
curOuter = db.cursor()
for row in curOuter.execute('SELECT * FROM myConnections'):
id = row[0]
with db:
curInner = db.cursor()
scList = retrieve_shared_connections(id)
for sc in scList:
curInner.execute('''INSERT INTO sharedConnections(IdConnectedToMe, IdShared) VALUES (?,?)''', (id,sc))
答案 1 :(得分:1)
我看,这有点老了。但是在遇到这个问题时,我想知道,sqlite3在python-2.7中是否还有这样的问题。我们来看看:
#!/usr/bin/python
import sqlite3
import argparse
from datetime import datetime
DBFILE = 'nested.sqlite'
MAX_A = 1000
MAX_B = 10000
parser = argparse.ArgumentParser(description='Nested SQLite cursors in Python')
parser.add_argument('step', type=int)
args = parser.parse_args()
connection = sqlite3.connect(DBFILE)
connection.row_factory = sqlite3.Row
t0 = datetime.now()
if args.step == 0:
# set up test database
cursor = connection.cursor()
cursor.execute("""DROP TABLE IF EXISTS A""")
cursor.execute("""DROP TABLE IF EXISTS B""")
# intentionally omitting primary keys
cursor.execute("""CREATE TABLE A ( K INTEGER )""")
cursor.execute("""CREATE TABLE B ( K INTEGER, L INTEGER )""")
cursor.executemany("""INSERT INTO A ( K ) VALUES ( ? )""",
[ (i,) for i in range(0, MAX_A) ])
connection.commit()
for row in cursor.execute("""SELECT COUNT(*) CNT FROM A"""):
print row['CNT']
if args.step == 1:
# do the nested SELECT and INSERT
read = connection.cursor()
write = connection.cursor()
for row in read.execute("""SELECT * FROM A"""):
bs = [ ( row['K'], i ) for i in range(0, MAX_B) ]
for b in bs: # with .executemany() it would be twice as fast ;)
write.execute("""INSERT INTO B ( K, L ) VALUES ( ?, ? )""", b)
connection.commit()
for row in connection.cursor().execute("""SELECT COUNT(*) CNT FROM B"""):
print row['CNT']
elif args.step == 2:
connection = sqlite3.connect(DBFILE)
connection.row_factory = sqlite3.Row
control = connection.cursor()
ca = cb = 0 # will count along our expectation
for row in control.execute("""SELECT * FROM B ORDER BY K ASC, L ASC"""):
assert row['K'] == ca and row['L'] == cb
cb += 1
if cb == MAX_B:
cb = 0
ca += 1
assert ca == MAX_A and cb == 0
for row in connection.cursor().execute("""SELECT COUNT(*) CNT FROM B"""):
print row['CNT']
print datetime.now() - t0
输出
$ ./nested.py 0
1000
0:00:04.465695
$ ./nested.py 1
10000000
0:00:27.726074
$ ./nested.py 2
10000000
0:00:19.137563
该测试使用
完成$ python
Python 2.7.6 (default, Jun 22 2015, 17:58:13) [GCC 4.8.2] on linux2
>>> import sqlite3
>>> sqlite3.version
'2.6.0'
>>> sqlite3.sqlite_version
'3.8.2'
当我们commit
打包时,情况会发生变化,例如通过缩进上述测试脚本的第1步中的connection.commit()
。这种行为很奇怪,因为只有第二个 commit
到write
光标才会重置read
光标,完全如OP中所示。在摆弄上面的代码后,我假设OP 不执行一个commit
,如示例代码所示,但在包中做commit
。
备注:根据an answer中对另一个问题的建议,从单独的连接中绘制游标read
和write
以支持打包的commit
不起作用,因为{{ 1}} s将针对外部锁定运行。