2015-10-11 4 views
0

Этот вопрос является источником предыдущего вопроса here, в котором была создана база данных. Однако, когда дело доходит до добавления информации к этому набору данных, я могу вручную добавить информацию или перейти через программный путь. Последний мой выбор по дидактической причине.Как вставить информацию в базу данных Sqlite с использованием языка программирования Genie?

эквивалент того, что я пытаюсь сделать в питона:

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 an iterator' method for var x in last_id ^^^^^^^ Compilation failed: 1 error(s), 0 warning(s)

Как решить эту проблему с помощью кода в Gen то есть? Должен ли я преобразовать его в другой тип, который принимает использование в качестве итератора? Кроме того, является ли синтаксис (%s), x правильным?

Благодаря

+0

Вы пытаетесь перебрать число. Это явно не сработает. –

ответ

1

Ключевой момент вашей проблемы, как получить последнее значение вставки (первичный ключ для таблицы Рецептов) и поместить его в следующую инструкцию.

Чтобы сделать вставку полностью безопасной (доказательство против SQL injection), вы должны использовать prepared statement.

Я также добавил намного больше ошибок проверки.

[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 с доменами ошибок. Используя эту абстракцию, код будет намного короче.

+0

Что '' 'assert (param_position> 0)' '' делает? –

+0

https://developer.gnome.org/glib/stable/glib-Testing.html#g-assert Это функция, которая гарантирует правильность утверждения/предположение. –

+0

См. Также https://en.wikipedia.org/wiki/Assertion_%28software_development%29 –

1

У вас возникла проблема с использованием last_insert_rowid(), чтобы сделать внешний ключ. last_insert_rowid() - это одно значение, а не набор значений. Поэтому нет необходимости перебирать его в цикле for.

В следующем примере используются подготовленные операторы для вставки значений в две таблицы. Первая таблица содержит имя пользователя, а вторая таблица содержит внешний ключ для таблицы пользователя и случайно сгенерированный идентификатор ссылки.

Проблемная область, на которую вы смотрите, - это загрузка данных. Таким образом, эта программа может стать основой программы загрузки данных, которая использует преимущества производительности 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 позволяет программа останавливается при возникновении ошибки путем удаления ссылки на любой объект, когда заканчивается блок try, а затем блок except может безопасно использовать Process.exit(-1).При возврате -1 программа может сигнализировать любому вызывающему скрипту, что ошибка загрузки
  • Программа была разделена на отдельные функции и классы, обратите внимание, что соединение с базой данных передается как аргумент каждой функции, это принцип инкапсуляции в программировании
  • DataGenerator класс также обеспечивает пример инкапсуляции, он отслеживает, сколько примеров он генерируется, а затем останавливается, когда _max_iterations предел превышен
  • DataGenerator класс может так же легко использовать для чтения из файла , Надеюсь, вы можете начать видеть, как Genie, как и любой другой объектно-ориентированный язык программирования, может помочь модулизировать ваш код.
  • Каждый пользователь имеет два сообщения, поэтому программе необходимо сохранить last_insert_rowid(), иначе данные будут повреждены, когда last_insert_rowid() изменится на Идентификатор первого поста вставлен
  • DataGenerator создает десять тысяч примеров, и они загружаются примерно на четверть секунды на моей машине. Прокомментируйте строки BEGIN TRANSACTION и END TRANSACTION, и программа занимает около ста шестидесяти секунд! Таким образом, для загрузки данных в SQLite транзакция представляет собой огромный прирост производительности.
  • В этом примере подготовленные операторы в транзакции быстрее, чем загрузка дампа базы данных.
    sqlite3 example.sqlite .dump > backup.sql 
    time cat backup.sql | sqlite3 test.sqlite
    занимает около 0,8 секунды на моей машине, тогда как программа занимает около 0,25 с
+0

Это гладкий, а также довольно легко читаемый. Несколько основных вопросов: (i) является классом функцией? Каково определение класса? (ii) Каково определение инструкции в контексте SQL? И какое отношение оно имеет к exec? (iii) что означает знак подчеркивания _ перед переменными? Опять же, спасибо за советы. –

+0

(i) Класс - это тип данных, так же как строки и целые числа являются типами данных. Класс позволяет создавать собственные типы данных. Когда класс «создается», он называется объектом. Именно здесь исходит термин объектно-ориентированное программирование (ООП). Чтобы понять, как создавать большие программы с ООП, начните читать о шаблонах проектирования и принципах SOLID. Это много, чтобы принять участие и взял меня на долгое время, но мне очень помог. (Ii) Заявление - это просто подготовленный оператор SQL - см. Valadoc и т. Д. (iii) Подчеркивание делает его закрытым, это внутренняя работа класса. Это большие темы! – AlThomas

 Смежные вопросы

  • Нет связанных вопросов^_^