20

我在嵌套循环中的单个 sqlite 数据库上使用多个游标时遇到问题。我找到了一个适合我的解决方案,但它是有限的,而且我还没有看到在线记录这个特定问题。我发布这个是这样的:

  • 有明确的问题/解决方案可用
  • 看看有没有更好的解决方案
  • 也许我在sqlite3python 模块中发现了一个缺陷

我的 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  

解决方案不完善。不是使用来自外部循环游标的迭代器,而是 I 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 数据库中的不同表使用两个游标似乎不起作用。更重要的是,它不会失败,它只会给出奇怪的结果。

  • 这真的是最好的解决方案吗?
  • 有更好的解决方案吗?
  • 这是一个应该解决的缺陷吗?
4

4 回答 4

5

这看起来像您遇到问题 10513,已在 Python 2.7.13、3.5.3 和 3.6.0b1 中修复。

处理事务的方式存在错误,在某些情况下会重置所有游标状态。这导致重新curOuter从头开始。

解决方法是升级,或者在您可以升级之前,不要在事务提交中使用游标。通过使用curOuter.fetchall()你实现了后者。

于 2016-12-03T21:08:31.447 回答
2

您可以建立一个行列表以插入内部循环,然后在循环外建立 cursor.executemany() 。这不能回答多光标问题,但可能是您的解决方法。

curOuter = db.cursor()
rows=[]
for row in curOuter.execute('SELECT * FROM myConnections'):    
    id  = row[0]    
    scList = retrieve_shared_connections(id)  
    for sc in scList:

        rows.append((id,sc))
curOuter.executemany('''INSERT INTO sharedConnections(IdConnectedToMe, IdShared) VALUES (?,?)''', rows)  
db.commit()

更好的是只从 myConnections 中选择 ID:

curOuter.execute('SELECT id FROM myConnections')
于 2012-11-08T20:56:10.990 回答
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))
于 2015-03-01T23:07:08.290 回答
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在包中时情况会发生变化,例如通过缩进connection.commit()上述测试脚本的第 1 步。这种行为很奇怪,因为只有光标的第二个 commitwrite重置read光标,完全如 OP 中所示。在摆弄了上面的代码之后,我假设 OP 没有commit示例代码中所示那样做,而是在commit包中做了。

备注:如另一个问题的答案中所建议的那样,从单独的连接中绘制游标read并支持 packaged不起作用,因为s 将针对外部锁运行。writecommitcommit

于 2015-11-08T16:31:07.453 回答