0

この質問は、データベースが作成された以前の質問hereから派生したものです。ただし、そのデータセットに情報を追加する場合は、手動で情報を追加するか、プログラムを使用して情報を追加できます。後者は、教訓的な理由で私の選択です。

私がPythonでやろうとしていることと同等のものは次のとおりです。

for x in cursor.execute(sql):
    lastid = x[0]
    # Insert data into the instructions table
    sql = 'INSERT INTO Instructions (recipeID,instructions) VALUES( %s,"Brown hamburger. Stir in all other ingredients. Bring to a boil. Stir. Lower to simmer. Cover and cook for 20 minutes or until all liquid is absorbed.")' % lastid
    cursor.execute(sql)

私が行っている方法は次のとおりです。

//Insert the rest of instructions
var last_id = db.last_insert_rowid()
for var x in last_id
    query = """INSERT INTO Instructions (recipeID,instructions) VALUES(
         %s,
         "Brown hamburger. Stir in all other ingredients. Bring to a boil. Stir. Lower to simmer. Cover and cook for 20 minutes or until all liquid is absorbed."
         ), x
            """

しかし、私が得るエラーによると、 last_id はイテレータになれない int64 型のようです:

valac --pkg sqlite3 cookcreate.gs cookcreate.gs:55.18-55.24: エラー: last_id int64' does not have anの var x の反復子メソッド ^^^^^^^ コンパイルに失敗しました: 1 エラー、0 警告

Genie のコードでこの問題を解決するにはどうすればよいですか? イテレータとしての使用を受け入れる別の型に変換する必要がありますか? また、その構文は(%s), x正しいですか?

ありがとう

4

2 に答える 2

1

問題の重要なポイントは、最後の挿入値 (Recipes テーブルの主キー) を取得し、それを次のステートメントに入れる方法です。

挿入を完全に安全にする ( SQL インジェクションに対する証拠) には、準備済みステートメントを使用する必要があります。

また、さらに多くのエラー チェックを追加しました。

[indent=4]

def check_ok (db : Sqlite.Database, ec : int)
    if (ec != Sqlite.OK)
        stderr.printf ("Error: %d: %s\n", db.errcode (), db.errmsg ())
        Process.exit (-1)

def checked_exec (db : Sqlite.Database, sql : string)
    check_ok (db, db.exec (sql))

init
    // Opening/creating database. Database name is cookbook.db3
    db : Sqlite.Database? = null
    if (Sqlite.Database.open ("cookbook.db3", out db) != Sqlite.OK)
        stderr.printf ("Error: %d: %s\n", db.errcode (), db.errmsg ())
        Process.exit (-1)
    checked_exec (db, "CREATE TABLE Recipes (pkiD INTEGER PRIMARY KEY, name TEXT, servings TEXT, source TEXT)")
    checked_exec (db, "CREATE TABLE Instructions (pkID INTEGER PRIMARY KEY, instructions TEXT, recipeID NUMERIC)")
    checked_exec (db, "CREATE TABLE Ingredients (pkID INTEGER PRIMARY KEY, ingredients TEXT, recipeID NUMERIC)")

    // Insert data into Recipe table
    checked_exec (db, """INSERT INTO Recipes (name, servings, source) VALUES ("Spanish Rice", 4, "Greg")""")
    lastid : int64 = db.last_insert_rowid ()

    // Insert data into Inctructions table
    instr_sql : string = """INSERT INTO Instructions (recipeID, instructions) VALUES($recipeID, "Brown hamburger. Stir in all other ingredients. Bring to a boil. Stir. Lower to simmer. Cover and cook for 20 minutes or until all liquid is absorbed.")"""
    instr_stmt : Sqlite.Statement = null
    check_ok (db, db.prepare_v2 (instr_sql, instr_sql.length, out instr_stmt))
    param_position : int = instr_stmt.bind_parameter_index ("$recipeID")
    assert (param_position > 0)
    check_ok (db, instr_stmt.bind_int64 (param_position, lastid))
    // Warning: Statment.step uses a different return value mechanism
    //          check_ok can't be used here
    if (instr_stmt.step () != Sqlite.DONE)
        stderr.printf ("Error: %d: %s\n", db.errcode (), db.errmsg ())
        Process.exit (-1)

PS: 私が実際のプログラムを書くとしたら、おそらく最初に、エラー ドメインを含む高レベルの SQLite 抽象化を作成するでしょう。この抽象化を使用すると、コードはずっと短くなります。

于 2015-10-12T08:45:36.700 に答える
1

あなたが抱えていると思われる問題は、を使用しlast_insert_rowid()て外部キーを作成することです。last_insert_rowid()値のコレクションではなく、単一の値です。したがって、ループでループする必要はありませんfor

次の例では、準備済みステートメントを使用して、2 つのテーブルに値を挿入します。最初のテーブルにはユーザー名が保持され、2 番目のテーブルにはユーザー テーブルへの外部キーとランダムに生成された参照 ID が保持されます。

あなたが見ている問題の領域は、データの読み込みです。したがって、このプログラムは、Genie のパフォーマンスを利用するデータ ロード プログラムの基礎を形成できます。たとえば、ロードする前に何らかの方法でデータを整理したい場合は、Genie が適している可能性があります。パフォーマンスの詳細については後述します。

[indent=4]
uses Sqlite

exception DatabaseError
    FAILED_TO_CREATE_DATABASE
    FAILED_TO_CREATE_TABLES
    FAILED_TO_LOAD_DATA

init
    try
        database:Database = create_database( "example.sqlite" )
        create_tables( database )
        load_data( database )
    except error:DatabaseError
        print error.message
        Process.exit( -1 )

def load_data( db:Database ) raises DatabaseError
    user_insert_stmnt:Statement = prepare_user_insert_stmnt( db )
    posts_insert_stmnt:Statement = prepare_posts_insert_stmnt( db )

    var data = new DataGenerator()
    user_id:int64 = 0
    db.exec( "BEGIN TRANSACTION" )
    while data.read()
        user_insert_stmnt.bind_text( 
                    user_insert_stmnt.bind_parameter_index( "@name" ), 
                    data.user_name
                    )
        user_insert_stmnt.step()
        user_insert_stmnt.reset()
        user_id = db.last_insert_rowid()
        for var reference_id in data.reference_ids
            posts_insert_stmnt.bind_int64( 
                        posts_insert_stmnt.bind_parameter_index( "@user_id" ),
                        user_id
                        )
            posts_insert_stmnt.bind_int64( 
                        posts_insert_stmnt.bind_parameter_index( "@reference_id" ),
                        reference_id
                        )
            posts_insert_stmnt.step()
            posts_insert_stmnt.reset()
    db.exec( "END TRANSACTION" )

def prepare_user_insert_stmnt( db:Database ):Statement
    statement:Statement
    db.prepare_v2( """
insert into users( 
    name
    )
    values( @name )
""", -1, out statement )
    return statement

def prepare_posts_insert_stmnt( db:Database ):Statement
    statement:Statement
    db.prepare_v2( """
insert into posts( 
    user_id,
    reference_id
    )
    values( @user_id, @reference_id )
""", -1, out statement )
    return statement

class DataGenerator
    user_name:string = ""
    reference_ids:array of uint = new array of uint[ 2 ]

    _iteration:int = 0
    _max_iterations:int = 10000

    def read():bool
        user_name = "User%06d".printf( _iteration )
        _iteration++

        for a:int = 0 to (reference_ids.length -1)
            reference_ids[ a ] = Random.next_int()

        more:bool = true
        if _iteration > _max_iterations
            more = false
        return more

def create_database( db_name:string ):Database raises DatabaseError
    db:Database
    result:int = Database.open( db_name, out db )
    if result != OK
        raise new DatabaseError.FAILED_TO_CREATE_DATABASE( 
                                 "Can't create %s SQLite error %d, \"%s\"", 
                                 db_name,
                                 db.errcode(),
                                 db.errmsg()
                                 )
    return db

def create_tables( db:Database ) raises DatabaseError
    sql:string = """
create table users ( id integer primary key,
                    name varchar not null
                    );
create table posts ( id integer primary key,
                    user_id integer not null,
                    reference_id integer not null
                    );
"""
    if db.exec( sql ) != OK
        raise new DatabaseError.FAILED_TO_CREATE_TABLES( 
                                 "Can't create tables. SQLite error %d, \"%s\"", 
                                 db.errcode(),
                                 db.errmsg()
                                 )

注意すべき点:

  • データベースとテーブルを作成する関数はプログラムの最後にあります。これは、例が機能するためだけにあるためです。
  • を使用すると、ブロックの終了try...except時に任意のオブジェクトを逆参照することでエラーが発生したときにプログラムを停止でき、その後ブロックを安全に使用できます。-1 を返すことにより、プログラムは呼び出し元のスクリプトにロードが失敗したことを知らせることができます。tryexceptProcess.exit( -1 )
  • プログラムは個別の関数とクラスに分割されています。データベース接続は各関数に引数として渡されることに注意してください。これはプログラミングにおけるカプセル化の原則です。
  • クラスはカプセル化のDataGenerator例も提供し、生成した例の数を追跡し、_max_iterations制限を超えると停止します
  • このDataGeneratorクラスは、ファイルから読み取るのと同じくらい簡単に使用できます。Genie が他のオブジェクト指向プログラミング言語と同様に、コードのモジュール化にどのように役立つかを理解していただければ幸いです。
  • 各ユーザーには 2 つの投稿があるため、最初に挿入された投稿の ID が変更されると、プログラムは を保存する必要があり、そうlast_insert_rowid()しないとデータが破損します。last_insert_rowid()
  • DataGeneratorは 1 万個のサンプルを作成し、これらは私のマシンでは約 4 分の 1 秒でロードされます。BEGIN TRANSACTIONand行をコメントアウトするEND TRANSACTIONと、プログラムは約 160 秒かかります! したがって、SQLite でデータをロードする場合、トランザクションはパフォーマンスを大幅に向上させます
  • この例では、トランザクション内の準備されたステートメントは、データベースのダンプをロードするよりも高速です
    sqlite3 example.sqlite .dump > バックアップ.sql
    時間猫backup.sql | sqlite3 テスト.sqlite
    私のマシンでは約0.8秒かかりますが、プログラムは約0.25秒かかります
于 2015-10-14T17:04:05.877 に答える