与 PostgreSQL 相似,可使用许多不同的语言来访问 MySQL,包括 C、C++、Java 和 Perl。从 Professional Linux Programming 中第 5 章有关 MySQL 的下列章节中,Neil Matthew 和 Richard Stones 使用详尽的 MySQL C 接口向我们介绍了如何在 MySQL 数据库中执行 SQL 语句。他们将讨论返回数据的语句,例如 INSERT 以及不返回数据的语句,例如 UPDATE 和 DELETE。然后,他们将编写从数据库检索数据的简单程序。
现在,我们已经有了一个连接,并且知道如何处理错误,是时候讨论使用我们的数据库来作一些实际工作了。执行所有类型的 SQL 的主关键字是 mysql_query:
int mysql_query(MYSQL *connection, const char *query)
|
正如您所见,它非常简单。它取一个指向连接结构的指针和包含要执行的 SQL 的文本字符串;与命令行工具不同,将不使用结束分号。成功之后,返回 0。在需要包含二进制数据的特殊情况下,可以使用相关的函数,mysql_real_query。虽然出于本章的目的,我们仅需要讨论 mysql_query。
我们将先讨论 UPDATE、DELETE 和 INSERT 语句。因为它们不返回数据,所以更易于使用。
这里我们将介绍的另一个重要函数是检查受影响的行数的函数:
my_ulonglong mysql_affected_rows(MYSQL *connection);
|
可能关于这一函数的最显而易见的事就是其非同寻常的返回结果。由于可移植性原因,这是一个特殊的无符号类型。为了在 printf 中使用,建议将其强制转换成使用 %lu 格式规范的无符号长整数。这个函数返回受以前的 UPDATE、INSERT 或 DELETE 查询影响的行数,这些查询是使用 mysql_query 执行的。
通常对于 mysql_ 函数,返回码 0 表示没有行受影响;正数表示实际结果,通常是受影响的行数。
如前所述,当使用 mysql_affected_rows 时可能出现未期望的结果。让我们先讨论受 INSERT 语句影响的行数,它将按预期进行操作。将下列代码添加到程序 connect2.c 中,并且称其为 insert1.c:
#include
#include
#include "mysql.h"
int main(int argc, char *argv[]) {
MYSQL my_connection;
int res;
mysql_init(&my_connection);
if (mysql_real_connect(&my_connection, "localhost",
"rick", "bar", "rick", 0, NULL, 0)) {
printf("Connection success\n");
res = mysql_query(&my_connection, "INSERT INTO children(fname,age)
VALUES('Ann',3)");
if (!res) {
printf("Inserted %lu rows\n",
(unsigned long)mysql_affected_rows(&my_connection));
} else {
fprintf(stderr, "Insert error %d: s\n",mysql_errno(&my_connection),
mysql_error(&my_connection));
}
mysql_close(&my_connection);
} else {
fprintf(stderr, "Connection failed\n");
if (mysql_errno(&my_connection)) {
fprintf(stderr, "Connection error %d: %s\n",
mysql_errno(&my_connection),mysql_error(&my_connection));
}
}
return EXIT_SUCCESS;
}
|
正如预期,插入的行数为 1。
现在,我们更改代码,所以 'insert' 部分被替换成:
mysql_errno(&my_connection), mysql_error(&my_connection));
}
}
res = mysql_query(&my_connection, "UPDATE children SET AGE = 4
WHERE fname = 'Ann'");
if (!res) {
printf("Updated %lu rows\n",
(unsigned long)mysql_affected_rows(&my_connection));
} else {
fprintf(stderr, "Update error %d: %s\n",
mysql_errno(&my_connection),
mysql_error(&my_connection));
}
|
现在假设子表中有的数据,如下:
childno |
fname |
age |
1
2
3
4
5
6
7
8
9
10
11 |
Jenny
Andrew
Gavin
Duncan
Emma
Alex
Adrian
Ann
Ann
Ann
Ann |
14
10
4
2
0
11
5
3
4
3
4 |
如果我们执行 update1,希望报告的受影响行数为 4,但是实际上程序报告 2,因为它仅必须更改 2 行,虽然 WHERE 子句标识了 4 行。如果想让 mysql_affected_rows 报告的结果为 4(这可能是熟悉其它数据库的人所期望的),则需要记住将 CLIENT_FOUND_ROWS 标志传递到 mysql_real_connect,在 update2.c 中的程序如下:
if (mysql_real_connect(&my_connection, "localhost",
"rick", "bar", "rick", 0, NULL, CLIENT_FOUND_ROWS)) {
|
如果我们在数据库中复位数据,然后运行带有这种修改的程序,则它报告的行数为 4。
函数 mysql_affected_rows 还有最后一个奇怪之处,它发生在从数据库中删除数据时。如果使用 WHERE 子句,则 mysql_affected_rows 将按预期返回删除行数。但是,如果没有 WHERE 子句,则删除所有行,报告受影响的行数却为 0。这是因为由于效率原因优化删除整个表。这种行为不受 CLIENT_FOUND_ROWS 选项标志的影响。
现在是时候讨论 SQL 的最普遍用法了,从数据库检索数据的 SELECT 语句。
MySQL 还支持返回结果的 SHOW、DESCRIBE 和 EXPLAIN SQL 语句,但是这里不考虑它们。按惯例,手册中包含这些语句的说明。
您将会从 PostgreSQL 章记起,可以从 PQexec 中的 SQL SELECT 语句检索数据,这里马上获取所有数据,或者使用游标从数据库中逐行检索数据,以便搞定大数据。
由于完全相同的原因,MySQL 的检索方法几乎完全相同,虽然它实际上不用游标的形式描述逐行检索。但是,它提供了缩小这两种方法间差异的 API,如果需要,它通常使两种方法的互换更加容易。
通常,从 MySQL 数据库中检索数据有 4 个阶段:
象以前一样,我们使用 mysql_query 发出查询。数据检索是使用 mysql_store_result 或 mysql_use_result 完成的,这取决于想如何检索数据,随后使用 mysql_fetch_row 调用序列来处理数据。最后,必须调用 mysql_free_result 以允许 MySQL 执行任何所需的整理。
可以从 SELECT 语句(或其他返回数据的语句)中检索完所有数据,在单一调用中,使用 mysql_store_result:
MYSQL_RES *mysql_store_result(MYSQL *connection);
|
必须在 mysql_query 检索数据后才能调用这个函数,以在结果集中存储该数据。这个函数从服务器中检索所有数据并立即将它存储在客户机中。它返回一个指向以前我们从未遇到过的结构(结果集结构)的指针。如果语句失败,则返回 NULL。
使用等价的 PostgreSQL 时,应该知道返回 NULL 意味着已经发生了错误,并且这与未检索到数据的情况不同。即使,返回值不是 NULL,也不意味着当前有数据要处理。
如果未返回 NULL,则可以调用 mysql_num_rows 并且检索实际返回的行数,它当然可能是 0。
my_ulonglong mysql_num_rows(MYSQL_RES *result);
|
它从 mysql_store_result 取得返回的结果结构,并且在该结果集中返回行数,行数可能为 0。如果 mysql_store_result 成功,则 mysql_num_rows 也总是成功的。
这种 mysql_store_result 和 mysql_num_rows 的组合是检索数据的一种简便并且直接的方法。一旦 mysql_store_result 成功返回,则所有查询数据都已经存储在客户机上并且我们知道可以从结果结构中检索它,而不用担心会发生数据库或网络错误,因为对于程序所有数据都是本地的。还可以立即发现返回的行数,它可以使编码更简便。如前所述,它将所有结果立即地发送回客户机。对于大结果集,它可能耗费大量的服务器、网络和客户机资源。由于这些原因,使用更大的数据集时,最好仅检索需要的数据。不久,我们将讨论如何使用 mysql_use_result 函数来完成该操作。
一旦检索了数据,则可以使用 mysql_fetch_row 来检索它,并且使用 mysql_data_seek、mysql_row_seek、mysql_row_tell 操作结果集。在开始检索数据阶段之前,让我们先讨论一下这些函数。
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
|
这个函数采用从存储结果中获取的结果结构,并且从中检索单一行,在行结构中返回分配给您的数据。当没有更多数据或者发生错误时,返回 NULL。稍后,我们将回来处理这一行中的数据。
void mysql_data_seek(MYSQL_RES *result, my_ulonglong offset);
|
这个函数允许您进入结果集,设置将由下一个获取操作返回的行。offset 是行号,它必须在从 0 到结果集中的行数减 1 的范围内。传递 0 将导致在下一次调用 mysql_fetch_row 时返回第一行。
MYSQL_ROW_OFFEST mysql_row_tell(MYSQL_RES *result);
|
这个函数返回一个偏移值,它表示结果集中的当前位置。它不是行号,不能将它用于 mysql_data_seek。但是,可将它用于:
MYSQL_ROW_OFFSET mysql_row_seek(MYSQL_RES *result, MYSQL_ROW_OFFSET offset);
|
它移动结果集中的当前位置,并返回以前的位置。
有时,这一对函数对于在结果集中的已知点之间跳转很有用。请注意,不要将 row tell 和 row seek 使用的偏移值与 data_seek 使用的行号混淆。这些是不可交换的,结果将是您所希望看到的。
在迫不及待地使用这些新函数之前,我们需要知道的最后一个函数是 mysql_free_result。
void mysql_free_result(MYSQL_RES *result);
|
完成结果集时, 必须总是 调用这个函数,以允许 MySQL 库整理分配给它的对象。
现在开始编写从数据库中检索数据的第一个程序。我们将选择所有年龄大于 5 的行的内容。不幸的是我们还不知道如何处理这个数据,所以我们能做的只有循环检索它。这便是 select1.c:
#include
#include
#include "mysql.h"
MYSQL my_connection;
MYSQL_RES *res_ptr;
MYSQL_ROW sqlrow;
int main(int argc, char *argv[]) {
int res;
mysql_init(&my_connection);
if (mysql_real_connect(&my_connection, "localhost", "rick",
"bar", "rick", 0, NULL, 0)) {
printf("Connection success\n");
res = mysql_query(&my_connection, "SELECT childno, fname,
age FROM children WHERE age > 5");
if (res) {
printf("SELECT error: %s\n", mysql_error(&my_connection));
} else {
res_ptr = mysql_store_result(&my_connection);
if (res_ptr) {
printf("Retrieved %luows\n",(unsignedlong)mysql_num_rows(res_ptr));
while ((sqlrow = mysql_fetch_row(res_ptr))) {
printf("Fetched data...\n");
}
if (mysql_errno(&my_connection)) {
fprintf(stderr, "Retrive error: s\n",mysql_error(&my_connection));
}
}
mysql_free_result(res_ptr);
}
mysql_close(&my_connection);
} else {
fprintf(stderr, "Connection failed\n");
if (mysql_errno(&my_connection)) {
fprintf(stderr, "Connection error %d: %s\n",
mysql_errno(&my_connection),mysql_error(&my_connection));
}
}
return EXIT_SUCCESS;
}
|
检索结果集并循环通过已检索的数据的重要部分都已突出显示。
要按需要逐行检索数据,而不是立即获取全部数据并将它存储在客户机中,可以将 mysql_store_result 调用替换成 mysql_use_result:
MYSQL_RES *mysql_use_result(MYSQL *connection);
|
这个函数还取得一个连接对象并返回结果结合指针,或者出错时返回 NULL 。与 mysql_store_result 相似,它返回指向结果集对象的指针;关键的不同点在于返回时,实际上没有将任何数据检索到结果集,只是初始化结果集以准备好检索数据。
|