20

ネストされたループ内の単一のsqliteデータベースで複数のカーソルを使用する際に問題が発生しました。私は自分に合った解決策を見つけましたが、それは限られており、この特定の問題がオンラインで文書化されているのを見たことがありません。私はこれを投稿しています:

  • 明確な問題/解決策が利用可能です
  • より良い解決策があるかどうかを確認するには
  • おそらく私はsqlite3Pythonモジュールに欠陥を見つけました

私のPythonアプリは、社会関係データをsqliteに保存しています。データセットには、myConnectionsとsharedConnectionsの2つのテーブル間の1対多の関係が含まれています。前者には、接続ごとに1つの行があります。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()  

結果は奇妙です。テーブルは、の最初の2つのレコードの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データベース内の異なるテーブルに対して2つのカーソルを使用することは、機能していないようです。さらに、失敗することはなく、奇妙な結果をもたらすだけです。

  • これは本当に最良の解決策ですか?
  • より良い解決策はありますか?
  • これは対処すべき欠陥ですか?
4

4 に答える 4

5

これは、Python 2.7.13、3.5.3、および3.6.0b1で修正された問題10513に遭遇しているようです。

トランザクションの処理方法にバグがあり、特定の状況ですべてのカーソル状態がリセットされました。これは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'

上記のテストスクリプトのステップ1でcommitインデントするなどして、パッケージに入れると状況が変わります。OPに示されているように、カーソルの2番目だけがカーソルをリセットするconnection.commit()ため、動作は非常に奇妙です。上記のコードをいじった後、OPはサンプルコードに示されているように1つを実行しなかったが、パッケージで実行したと思います。 commitwritereadcommitcommit

備考:別の質問への回答で提案されているように、パッケージをサポートするためにカーソルreadを別々の接続から描画することは、 sが外部ロックに対して実行されるため、機能しません。writecommitcommit

于 2015-11-08T16:31:07.453 に答える