SQLite:将C结构插入多个表 - 帮助设计

时间:2010-07-01 10:00:01

标签: c sqlite

这是一个简短的程序,它创建一个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 database -lsqlite3&amp;&amp; ./database&amp;&amp; sqlite3 database.db
SQLite版本3.6.23.1
输入“.help”获取说明 输入以“;”结尾的SQL语句 源码&GT; 。上的 源码&GT; .mode csv
源码&GT; SELECT
   ...&GT; titles.name AS标题,
   ...&GT; albums.name AS专辑,
   ...&GT; artists.name AS艺术家
   ...&GT;来自标题
   ...&GT; INNER JOIN专辑USING(album_id)
   ...&GT; INNER JOIN艺术家使用(artist_id);
标题,专辑,艺术家
“自毁先生”,“向下螺旋”,“九寸钉” “坐下来,站起来”,“向小偷致敬”,Radiohead
“猪的三月”,“向下的螺旋”,“九英寸的钉子”

因为每个INSERT都可以处理数据库中已有的数据,所以我使用的是INSERT或IGNORE,这意味着我不能再依赖sqlite_last_insert_rowid()为artist_id和album_id提供ROWID,这就是为什么我然后搜索数据库以获取ROWID。任何有关更好设计的建议都会很棒!

2 个答案:

答案 0 :(得分:1)

回答我自己的问题感觉有点不对劲;我仍然愿意接受更好的建议!如果数据不存在,则此通用插入函数将插入数据,并返回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 );
}

答案 1 :(得分:0)

如果使用INSERT OR REPLACE怎么办?插入内容应占用相同的时间。重复不应影响任何事情(可能不会比整体进行二次搜索花费更长的时间)。文档说它被视为成功,最后的rowid将被更新。但是,我不知道替换是否会实际更改表中现有的rowid,丢掉主键。