这是 SQLite 数据库的 C 编程教程。 它涵盖了使用 C 语言进行 SQLite 编程的基础。
SQLite 数据库
_SQLite_
是嵌入式关系数据库引擎。 它的开发人员称其为自包含,无服务器,零配置和事务型 SQL 数据库引擎。 它目前非常流行,当今全球使用着数亿册。 SQLite 在 Solaris 10,Mac OS,Android 或 iPhone 中使用。 Qt4 库内置了对 SQLite 以及 Python 和 PHP 的支持。 许多流行的应用内部都使用 SQLite,例如 Firefox,Google Chrome 或 Amarok。
sqlite3 工具
sqlite3 工具是 SQLite 库的基于终端的前端。 它以交互方式评估查询,并以多种格式显示结果。 它也可以在脚本中使用。 它具有自己的元命令集,包括.tables
,.load
,.databases
或.dump
。 要获取所有指令的列表,我们键入.help
命令。
现在,我们将使用sqlite3
工具创建一个新数据库。
我们为sqlite3 tool
提供了一个参数; test.db
是数据库名称。 这是我们磁盘上的文件。 如果存在,则将其打开。 如果不是,则创建它。
.tables
命令提供test.db
数据库中的表的列表。 当前没有表。 .exit
命令终止sqlite3
命令行工具的交互式会话。 ls
Unix 命令显示当前工作目录的内容。 我们可以看到test.db
文件。 所有数据将存储在该单个文件中。
C99
本教程使用 C99。 对于 GNU C 编译器,我们需要使用-std=c99
选项。 对于 Windows 用户,强烈建议使用 Pelles C IDE。 (MSVC 不支持 C99。)
在 C99 中,我们可以将声明与代码混合使用。 在较早的 C 程序中,我们需要将这一行分成两行。
SQLite 版本
在第一个代码示例中,我们将获得 SQLite 数据库的版本。
sqlite3_libversion()
函数返回指示 SQLite 库的字符串。
该头文件定义了 SQLite 库提供给客户端程序的接口。 它包含定义,函数原型和注释。 它是 SQLite API 的权威来源。
我们使用 GNU C 编译器编译程序。
这是示例的输出。
在第二个示例中,我们再次获得 SQLite 数据库的版本。 这次我们将使用 SQL 查询。
SQLITE_VERSION()
查询用于获取 SQLite 库的版本。
sqlite3
结构定义数据库句柄。 每个打开的 SQLite 数据库均由数据库句柄表示。
sqlite3_stmt
结构表示单个 SQL 语句。
sqlite3_open()
功能打开一个新的数据库连接。 它的参数是数据库名称和数据库句柄。 :memory:
是一个特殊的数据库名称,使用它可以打开内存数据库。 该函数的返回代码指示数据库是否已成功打开。 成功建立连接后,将返回SQLITE_OK
。
如果返回代码指示错误,我们将消息打印到控制台,关闭数据库句柄,然后终止程序。 sqlite3_errmsg()
函数返回错误的描述。 无论打开时是否发生错误,都应通过将其传递给sqlite3_close()
函数来释放与数据库连接句柄关联的资源。
在执行 SQL 语句之前,必须先使用 sqlite3_prepare *函数之一将其编译为字节码。 (不推荐使用sqlite3_prepare()
功能。)
sqlite3_prepare_v2()
功能具有五个参数。 第一个参数是从sqlite3_open()
函数获得的数据库句柄。 第二个参数是要编译的 SQL 语句。 第三个参数是 SQL 语句的最大长度,以字节为单位。 传递-1 将导致 SQL 字符串被读取到第一个零终止符,即第一个零终止符。 根据文档,通过传递所提供的 SQL 字符串的确切字节数,可以获得一些小的性能优势。 第四个参数是语句句柄。 如果sqlite3_prepare_v2()
成功运行,它将指向预编译的语句。 最后一个参数是指向 SQL 语句未使用部分的指针。 只编译 SQL 字符串的第一条语句,因此该参数指向未编译的内容。 我们传递 0,因为参数对我们而言并不重要。
成功时,sqlite3_prepare_v2()
返回SQLITE_OK
; 否则返回错误代码。
这是sqlite3_prepare_v2()
函数调用的错误处理代码。
sqlite3_step()
运行 SQL 语句。 SQLITE_ROW
返回码表示还有另一行准备就绪。 我们的 SQL 语句仅返回一行数据,因此,我们只调用一次此函数。
sqlite3_finalize()
函数破坏预备语句对象。
sqlite3_close()
功能关闭数据库连接。
插入数据
我们创建一个Cars
表并在其中插入几行。
我们连接到test.db
数据库,创建Cars
表,然后在创建的表中插入 8 行。
如果发生错误,该指针将指向创建的错误消息。
与test.db
数据库的连接已创建。
这些 SQL 语句创建一个Cars
表并用数据填充它。 语句必须用分号分隔。
sqlite3_exec()
函数是sqlite3_prepare_v2()
,sqlite3_step()
和sqlite3_finalize()
的便利包装,它允许应用运行多个 SQL 语句而无需使用大量 C 代码。
该函数的第三个参数是为从评估的 SQL 语句中出来的每个结果行调用的回调函数。 第四个参数是回调函数的第一个参数。 如果不需要它们,可以将 0 传递给这些参数。
如果发生错误,则最后一个参数指向分配的错误消息。
必须使用sqlite3_free()
函数调用释放分配的消息字符串。
我们使用sqlite3
工具验证写入的数据。 首先,我们修改数据在控制台中的显示方式。 我们使用列模式并打开标题。
这是我们已写入Cars
表的数据。
最后插入的行 ID
有时,我们需要确定最后插入的行的 ID。 为此,我们具有sqlite3_last_insert_rowid()
功能。
在内存中创建一个Friends
表。 其Id
列会自动增加。
在 SQLite 中,INTEGER PRIMARY KEY
列自动增加。 还有一个AUTOINCREMENT
关键字。 当在INTEGER PRIMARY KEY AUTOINCREMENT
中应用时,会使用稍微不同的 ID 创建算法。
使用自动递增的列时,除了自动递增的列(省略了该列)外,我们需要明确声明列名。
sqlite3_last_insert_rowid()
返回表中最近一次成功插入的行 ID。
我们看到了程序的输出。
检索数据
我们已经将一些数据插入test.db
数据库。 在下面的示例中,我们从数据库中检索数据。
我们使用SELECT * FROM Cars
SQL 语句从Cars
表中检索所有行。
这是与sqlite3_exec()
函数结合使用的回调函数的函数原型。
我们连接到test.db
数据库。
在这里,我们定义 SQL 语句以从Cars
表中选择所有数据。
sqlite3_exec()
函数对 SQL 语句进行赋值。 对于从评估的 SQL 语句出来的每个结果行,都会调用其回调函数。
回调函数的第一个参数是sqlite3_exec()
的第 4 个参数中提供的数据; 它通常不被使用。 第二个参数是结果中的列数。 第三个参数是表示行中字段的字符串数组。 最后一个参数是代表列名的字符串数组。
在函数主体中,我们遍历所有列并打印其名称和内容。
这是示例的部分输出。
参数化查询
现在我们将提到参数化查询。 参数化查询(也称为预准备语句)可提高安全性和性能。 当使用参数化查询时,我们使用占位符,而不是直接将值写入语句。
在该示例中,问号(?)用作占位符,稍后将其替换为实际值。
问号用于为 SQL 查询提供 ID。
sqlite3_prepare_v2()
函数编译 SQL 查询。
sqlite3_bind_int()
将整数值绑定到预备语句。 占位符将替换为整数 3。该函数的第二个参数是要设置的 SQL 参数的索引,第三个参数是要绑定到该参数的值。
sqlite3_step()
函数评估 SQL 语句。
如果有可用的数据行,则可以使用sqlite3_column_text()
函数获得两列的值。
该示例返回 ID 和汽车的名称。
第二个示例使用带有命名占位符的参数化语句。
我们使用命名的占位符选择汽车的名称和价格。
命名的占位符以冒号(:)或符号(@)字符为前缀。
sqlite3_bind_parameter_index()
函数返回给定名称的 SQL 参数的索引。
插入图像
在本节中,我们将图像插入到 SQLite 数据库中。 请注意,有些人反对将图像放入数据库。 在这里,我们只展示如何做。 我们不讨论是否将图像保存在数据库中的技术问题。
对于此示例,我们创建一个名为 Images 的新表。 对于图像,我们使用BLOB
数据类型,表示二进制大型对象。
在此程序中,我们从当前工作目录中读取图像,并将其写入 SQLite test.db
数据库的Images
表中。
我们从文件系统读取二进制数据。 我们有一个名为woman.jpg
的 JPG 图像。 fopen()
功能打开指定的文件以供读取。 如果操作失败,它将返回一个指向 FILE 对象的指针或 NULL。
我们使用fseek()
函数将文件指针移到文件末尾。 我们需要确定图像的大小。 如果发生错误,则设置错误指示器。 我们使用fseek()
功能检查指示器。 如果发生错误,将关闭打开的文件处理程序。
对于二进制流,ftell()
函数返回文件开头的字节数,例如 图像文件的大小。 发生错误时,函数将返回-1 并设置errno
。 perror()
函数将 errno 的值解释为错误消息,并将其打印到标准错误输出流。
该数组将存储图像数据。
fread()
函数从文件指针读取数据并将其存储在数据数组中。 该函数返回成功读取的元素数。
读取数据后,我们可以关闭文件处理程序。
该 SQL 语句用于将映像插入数据库。
SQL 语句已编译。
sqlite3_bind_blob()
函数将二进制数据绑定到已编译的语句。 SQLITE_STATIC
参数表示指向内容信息的指针是静态的,不需要释放。
执行该语句,并将图像写入表中。
读取图像
在本节中,我们将执行相反的操作。 我们将从数据库表中读取图像。
我们从Images
表中读取图像数据,并将其写入另一个文件woman2.jpg
中。
我们以写入模式打开一个二进制文件。 来自数据库的数据被写入文件。
该 SQL 语句从 Images 表中选择数据。 我们从第一行获取二进制数据。
sqlite3_column_bytes()
函数返回 BLOB 中的字节数。
使用fwrite()
功能将二进制数据写入文件。 sqlite3_column_blob()
函数返回指向所选二进制数据的指针。
ferror()
功能检查与流相关的错误指示符是否已设置。
元数据
元数据是有关数据库中数据的信息。 SQLite 中的元数据包含有关表和列的信息,我们在其中存储数据。 受 SQL 语句影响的行数是元数据。 结果集中返回的行数和列数也属于元数据。
可以使用PRAGMA
命令获取 SQLite 中的元数据。 SQLite 对象可能具有属性,即元数据。 最后,我们还可以通过查询 SQLite 系统sqlite_master
表来获取特定的 metatada。
在此示例中,我们发出PRAGMA table_info(tableName)
命令,以获取有关Cars
表的一些元数据信息。
PRAGMA table_info(tableName)
命令为Cars
表中的每一列返回一行。 结果集中的列包括列顺序号,列名称,数据类型,该列是否可以为NULL
以及该列的默认值。
这是示例的输出。
在与元数据有关的下一个示例中,我们将列出test.db
数据库中的所有表。
该代码示例将当前数据库中的所有可用表打印到终端。
表名存储在系统sqlite_master
表中。
这是一个示例输出。
事务
事务是针对一个或多个数据库中数据的数据库操作的基本单位。 事务中所有 SQL 语句的影响可以全部提交给数据库,也可以全部回滚。
在 SQLite 中,除SELECT
以外的任何命令都将启动隐式事务。 同样,在事务中,诸如CREATE TABLE
…,VACUUM
和PRAGMA
之类的命令将在执行之前提交先前的更改。
手动事务以BEGIN TRANSACTION
语句开始,并以COMMIT
或ROLLBACK
语句结束。
SQLite 支持三种非标准事务级别:DEFERRED
,IMMEDIATE
和EXCLUSIVE
。
自动提交
默认情况下,SQLite 版本 3 在 自动提交模式 下运行。 在自动提交模式下,对数据库的所有更改将在与当前数据库连接关联的所有操作完成后立即提交。 自动提交模式由BEGIN
语句禁用,并由COMMIT
或ROLLBACK
重新启用。
本示例检查数据库是否处于自动提交模式。
如果数据库未处于自动提交模式,则sqlite3_get_autocommit()
函数返回零。 如果处于自动提交模式,它将返回非零值。
该示例确认默认情况下 SQLite 处于自动提交模式。
下一个示例进一步阐明了自动提交模式。 在自动提交模式下,每个非 SELECT 语句都是一个立即提交的小事务。
我们创建Friends
表,并尝试用数据填充它。
最后一条 SQL 语句有一个错误; 没有朋友表。
创建表并插入三行。
事务
在下一个示例中,我们将一些 SQL 语句放入事务中。
我们继续使用Friends
表。
第一个语句删除Friends
表(如果存在)。 其他语句放在事务内。 事务以全有或全无的方式工作。 要么什么都不做,要么什么都不做。
由于最后一条语句有错误,因此将回滚事务,并且不会创建Friends
表。