在python-2.7的sqlite3中的嵌套循环中使用多个游标

时间:2012-11-05 18:04:33

标签: python sqlite python-2.7

我在嵌套循环中的单个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数据库中的不同表使用两个游标似乎不起作用。更重要的是,它不会失败,只会产生奇怪的结果。

所以: - 这真的是最好的解决方案吗? - 有更好的解决方案吗? - 这是一个应该解决的缺陷吗?

解答: 在这一点上,问题已经提出,有一些讨论,我认为我们已经完成了。这是看起来如何:

  1. 使用python中的sqlite3模块在嵌套循环中无法可靠地使用多个游标。不幸的是,我们没有得到明确的确认。
  2. 有一个比我发布的更好的解决方案。 2a:将选择减少到你需要的字段(我正在使用*)。 2b:构造循环,使最小的占用空间进入内存。对于这种情况,sharedConnections< = connections(它是一个子集)。因此,在连接上使用游标并在内存中累积sharedConnections。 2c:在累积的sharedConnections列表上使用.executemany应该比内部循环上的.execute更有效。
  3. 这是缺陷吗?我们没有得到答案。我想这就是生活!
  4. 感谢您的关注和建议。

    祝你好运, -Jim

2 个答案:

答案 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()。这种行为很奇怪,因为只有第二个 commitwrite光标才会重置read光标,完全如OP中所示。在摆弄上面的代码后,我假设OP 执行一个commit,如示例代码所示,但在包中做commit

备注:根据an answer中对另一个问题的建议,从单独的连接中绘制游标readwrite以支持打包的commit不起作用,因为{{ 1}} s将针对外部锁定运行。