0

这个问题是前一个问题的衍生,这里创建了一个数据库。但是,在向该数据集添加信息时,我可以手动添加信息或通过编程方式进行。出于教学原因,后者是我的选择。

我在 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: error: int64' does not have aniterator' method for var x in last_id ^^^^^^^ 编译失败: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

以下示例使用准备好的语句将值插入到两个表中。第一个表保存用户名,第二个表保存用户表的外键和随机生成的参考 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()
                                 )

需要注意的几点:

  • 创建数据库和表的函数位于程序的末尾,因为它们仅用于示例工作
  • Usingtry...except允许程序在发生错误时停止,方法是在try块结束时取消引用任何对象,然后except块可以Process.exit( -1 )安全地使用。通过返回 -1,程序可以向任何调用脚本发出加载失败的信号
  • 程序已经被拆分成单独的函数和类,注意数据库连接作为参数传递给每个函数,这是编程中的封装原则
  • 该类DataGenerator还提供了一个封装示例,它跟踪它生成了多少个示例,然后在_max_iterations超出限制时停止
  • 该类DataGenerator可以很容易地用于从文件中读取。希望您可以开始了解 Genie 是如何像任何其他面向对象的编程语言一样帮助您模块化代码的
  • 每个用户有两个帖子,因此程序必须存储它们,否则当插入的第一个帖子的 ID 发生更改last_insert_rowid()时,数据将被破坏last_insert_rowid()
  • 创建一万个示例,这些DataGenerator示例在我的机器上加载大约四分之一秒。注释掉BEGIN TRANSACTIONEND TRANSACTION行,程序大约需要一百六十秒!因此,对于 SQLite 中的数据加载,事务是一个巨大的性能提升
  • 在此示例中,事务中的准备好的语句比加载数据库的转储更快
    sqlite3 example.sqlite .dump > 备份.sql
    时间猫备份.sql | sqlite3 test.sqlite
    在我的机器上大约需要 0.8 秒,而程序大约需要 0.25 秒
于 2015-10-14T17:04:05.877 回答