SQLite: вставка структуры C в несколько таблиц - помощь в разработке - PullRequest
2 голосов
/ 01 июля 2010

Вот короткая программа, которая создает базу данных SQLite для хранения некоторых основных музыкальных метаданных.В базе данных есть три таблицы для трех полей метаданных;название песни, альбом, из которого вышла песня, и исполнитель, который сделал альбом.Из-за характера данных дублированная информация в метаданных является достоверной (как показано в коде).

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <sqlite3.h>

// Error checks replaced with assert for brevity

#define STRING_MAX 32

// metadata for a audio track
typedef struct {
    char title[ STRING_MAX ];
 char artist[ STRING_MAX ];
 char album[ STRING_MAX ];
} metadata_t;

// some metadata for testing
static metadata_t tracks[] = {
    { "Mr Self Destruct", "Nine Inch Nails", "The Downward Spiral" },
    { "Sit Down, Stand Up", "Radiohead", "Hail to the Thief" },
    { "March of the Pigs", "Nine Inch Nails", "The Downward Spiral" },
};
// number of test tracks in the above array
#define TRACK_COUNT ( sizeof( tracks ) / sizeof( tracks[ 0 ] ) )

int main( int argc, char **argv ) {
    sqlite3 *db;
    int result = sqlite3_open_v2( "database.db3", &db,
     SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, "unix-none" );
    assert( result == SQLITE_OK );

    // create the three tables

    // artists have a name
    result = sqlite3_exec( db, "CREATE TABLE IF NOT EXISTS artists(\
        artist_id INTEGER PRIMARY KEY,\
        name TEXT UNIQUE\
        );",
        NULL, NULL, NULL );
    assert( result == SQLITE_OK );

    // albums have a name and an artist
    result = sqlite3_exec( db, "CREATE TABLE IF NOT EXISTS albums(\
        album_id INTEGER PRIMARY KEY,\
        name TEXT UNIQUE,\
        artist_id INTEGER,\
        UNIQUE( name, artist_id )\
        );",
        NULL, NULL, NULL );
    assert( result == SQLITE_OK );

    // titles have a name and belong to an album
    result = sqlite3_exec( db, "CREATE TABLE IF NOT EXISTS titles(\
        title_id INTEGER PRIMARY KEY,\
        name TEXT,\
        album_id INTEGER\
        );",
        NULL, NULL, NULL );
    assert( result == SQLITE_OK );

    // insert the metadata into the databse, one track at a time
    int i;
    for ( i = 0; i < TRACK_COUNT; ++i ) {
        char command[ 1024 ]; // The SQL to execute
        int result;
        sqlite3_stmt *stmt;

        // Ignore the UNIQUE error if the artist is already in the table.
        // This makes sqlite3_last_insert_rowid() not work.
        (void)sqlite3_snprintf( 1024, command,
            "INSERT OR IGNORE INTO artists( name )\
            VALUES( '%q' );",
            tracks[ i ].artist );
        result = sqlite3_exec( db, command, NULL, NULL, NULL );
        assert( result == SQLITE_OK );

        // Get the rowid for the newly inserted artist
        (void)sqlite3_snprintf( 1024, command,
            "SELECT artist_id FROM artists WHERE name='%q';",
            tracks[ i ].artist );
        sqlite3_prepare( db, command, strlen( command ), &stmt, NULL );
        assert( sqlite3_column_count( stmt ) == 1 );
        sqlite3_step( stmt );
        int artist_id = sqlite3_column_int( stmt, 0 );
        assert( sqlite3_step( stmt ) == SQLITE_DONE );
        sqlite3_finalize( stmt );

        // Ignore the UNIQUE error if the album/artist_id combo is
        // already in the table
        (void)sqlite3_snprintf( 1024, command,
            "INSERT OR IGNORE INTO albums( name, artist_id )\
            VALUES( '%q', %d );",
            tracks[ i ].album, artist_id );
        result = sqlite3_exec( db, command, NULL, NULL, NULL );
        assert( result == SQLITE_OK );

        // Get the rowid for the newly inserted album
        (void)sqlite3_snprintf( 1024, command,
            "SELECT album_id FROM albums WHERE name='%q';",
            tracks[ i ].album );
        sqlite3_prepare( db, command, strlen( command ), &stmt, NULL );
        assert( sqlite3_column_count( stmt ) == 1 );
        sqlite3_step( stmt );
        int album_id = sqlite3_column_int( stmt, 0 );
        assert( sqlite3_step( stmt ) == SQLITE_DONE );
        sqlite3_finalize( stmt );

        // Finally, insert the track title and the album it came from
        (void)sqlite3_snprintf( 1024, command,
            "INSERT INTO titles( name, album_id )\
            VALUES( '%q', %d );",
            tracks[ i ].title, album_id );
        result = sqlite3_exec( db, command, NULL, NULL, NULL );
        assert( result == SQLITE_OK );
    }
    sqlite3_close( db );
    return ( 0 );
}

Скомпилируйте и протестируйте:
$ gcc -Wall database.c -o база данных -lsqlite3 && ./database && sqlite3 database.db
SQLite версии 3.6.23.1
Введите ".help" для инструкций
Введите операторы SQL, оканчивающиеся на ";"
sqlite> .headers на
sqlite> .mode csv
sqlite> SELECT
...> title.name AS title,
...> album.name AS album,
...> Artist.Name AS artist
...> ИЗ названий
...> INNER JOIN альбомы USING (album_id)
...> INNER JOIN художники USING (artist_id);
название, альбом, исполнитель
"Мистер Самоуничтожение »,« Нисходящая спираль »,« Nine Inch Nails »
« Сядь, встань »,« Приветствую вора », Radiohead
« Марш свиней »,« Нисходящая спираль », "Nine Inch Nails"

Поскольку каждый INSERT может иметь дело с данными, которые уже находятся в базе данных, я использую INSERT OR IGNORE, что означает, что я не могубольше полагаться на sqlite_last_insert_rowid (), чтобы дать мне ROWID для artist_id и album_id, поэтому я затем ищу в базе данных, чтобы получить ROWID.Любые предложения по лучшему дизайну были бы великолепны!

Ответы [ 2 ]

1 голос
/ 13 июля 2010

Отвечая на мой вопрос, я чувствую себя немного неправильно; Я все еще открыт для лучших предложений! Эта универсальная функция вставки вставит данные, если они не существуют, и вернет ROWID.

sqlite3_int64 _insert_generic(
    sqlite3 *db,
    const char *table,
    const char *key,
    const char *value
    ) {

    char sql[ SQL_MAX ];
    int result;
    sqlite3_stmt *stmt;
    sqlite3_int64 id;

    /* check if this key/value is already in table */
    (void)sqlite3_snprintf( SQL_MAX, sql,
    "SELECT %s FROM %s WHERE name='%q';",
        key,
        table,
        value );
    result = sqlite3_prepare_v2( db, sql, strlen( sql ), &stmt, NULL );
    assert( result == SQLITE_OK );

    if ( sqlite3_step( stmt ) == SQLITE_ROW ) {
        /* a key/value was found in table, get the ROWID */
        id = sqlite3_column_int( stmt, 0 );
        assert( sqlite3_step( stmt ) == SQLITE_DONE );
        sqlite3_finalize( stmt );
    } else {
        /* key/value is not in table, so insert it */
        sqlite3_finalize( stmt );
        (void)sqlite3_snprintf( SQL_MAX, sql,
            "INSERT INTO %s( name )\
            VALUES( '%q' );",
            table,
            value );
        result = sqlite3_exec( db, sql, NULL, NULL, NULL );
        assert( result == SQLITE_OK );
        id = sqlite3_last_insert_rowid( db );
    }
    assert( id > 0 );
    return ( id );
}
0 голосов
/ 01 июля 2010

Что делать, если вы использовали INSERT OR REPLACE?Вставки должны занимать столько же времени.Дубликаты не должны влиять на что-либо (может занять больше времени, чем общий поиск).Документы говорят, что это считается успешным, и последний rowid будет обновлен.Однако я не знаю, изменит ли замена существующий rowid в таблице, исключая ваши первичные ключи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...