创建一个新的数据库,然后将其作为当前的数据库:
create database blpcd;
use blpcd;
只考虑数据的两种最重要的关系
首先,每张CD由不同数目的曲目组成,所以将把曲目数据存在一个独立于其他CD数据的表中。
其次,每位艺术家经常有多张专辑,所以将艺术家的信息存储一次,然后单独提取属于该艺术家的所有CD。
8.4.1 创建表
需要去确定表的实际结构。从主表——CD表开始,它保存大部分的信息。需要保存一个CD ID、一个分类号、一个标题以及一些标注。还需要一个来自artist表的ID号来表明是哪位艺术家制作了这张专辑。
artist表很简单,它仅仅保存艺术家的名字和一个唯一的艺术家ID号。track表也很简单,只需要一个CD ID来表明曲目属于哪张CD、一个曲目号和一个曲目标题。
首先是CD表:
CREATE TABLE cd(
id int auto_increment NOT NULL primary key,
title varchar(70) NOT NULL,
artist_id int NOT NULL,
catalogue varchar(30) NOT NULL,
notes varchar(100)
);
创建表cd,它包含下面一些列:
id列,包含一个自动增加的整数,它是表的主键
最长为70个字符的title
artist_id,在artist表中使用的一个整数
最长为30个字符的catalogue号
最长为100个字符的notes
只有notes列可以为NULL,其他列都必须包含值
其次是artist表:
CREATE TABLE artist(
id int auto_increment NOT NULL primary key,
name varchar(100) NOT NULL
);
最后是track表:
CREATE TABLE track(
cd_id int NOT NULL,
track_id int NOT NULL,
title varchar(70),
primary key(cd_id, track_id)
);
这次使用不同的方法来声明主键,track表的不同之处在于每张CD的ID会出现多次,而对于任何指定曲目的ID,例如曲目1,也会在不同CD中出现多次。但是,这两者的结合将永远是唯一的,所以将主键声明为这两列的结合,这被称为联合键。
将这些SQL语句存储在文件create_table.sql中,并将该文件保存在当前目录中,然后开始创建数据库以及其中的表。
8.4.2 添加数据
编写insert_data.sql文件,使用\.命令执行。
注意这个文件将删除数据库blpcd中所有的数据以确保脚本是干净的
此外,这个文件在ID字段中插入数值,而不是让auto_increment自动分配的。
使用mysql命令客户端和一些SQL语句来检查。首先,从数据库中选出每张专辑的头两首曲目:
SELECT artist.name, cd.title AS "CD Title", track.track_id, track.title AS "Track" FROM artist, cd, track WHERE artist.id = cd.artist_id AND track.cd_id = cd.id AND track.track_id < 3;
这条SQL语句的第一部分是:
SELECT artist.name, cd.title, track.track.id, track.title
它通过使用标记tablename.column来说明想要显示哪些列。
SELECT语句的AS部分SELECT artist.name, cd.title AS "CD Title", track.track_id和track.title AS "Track"只是在输出时重命名列名。因此,来自cd表的title列(cd.title)的标题栏被命名为"CD Title",track.title列被命名为"Track"。AS的使用给了更好的输出,它是在命令行中针对SQL语句的一个有用的字句,但是当通过其他编程语言来调用SQL语句时,几乎不会用到它。
FROM artist, cd, track
它告诉服务器使用的表名
WHERE子句是需要点技巧的部分:
WHERE artist.id = cd.artist_id AND track.cd_id = cd.id AND track.track_id < 3
第一部分告诉服务器artist表中的ID应与cd表中的artist_id相同。第二部分告诉服务器track表的cd_id列应该与cd表中的id列相同,第三部分track.track_id<3减少了返回数据的数量以使得仅仅从每张CD中得到曲目1和曲目2.最后,使用AND把3个条件结合起来,因为需要这3个条件同时都为真。
8.4.3 使用C语言访问数据
编写类似于CD应用程序的一个常见问题是无法知道返回的结果数,以及如何在客户端代码和访问数据库的代码间传递这些结果。在这个应用程序中,为了保持简单并专注于数据库接口,将使用固定大小的结构。但在实际的程序中,这可能是不能接受的。一种常见的解决办法(它同时也有助于减少网络流量)是每次总是提取一行数据,正如前面介绍的mysql_use_result和mysql_fetch_row一样。
1.接口定义
先从头文件app_mysql.h开始,它定义了结构和函数:
首先是一些结构:
/* A simplistic structure to represent the current CD, excluding the track information */
struct current_cd_st{
int artist_id;
int cd_id;
char artist_name[100];
char title[100];
char catalogue[100];
};
/* A simplistic track details structure */
struct current_tracks_st{
int cd_id;
char track[20][100];
};
#define MAX_CD_RESULT 10
struct cd_search_st{
int cd_id[MAX_CD_RESULT];
}
然后是一对函数,它们用于连接数据库以及从数据库断开连接
/* Database backend functions */
int database_start(char *name, char *password);
void database_end();
现在,转向操纵数据的函数,注意,没有创建或三处艺术家的函数,将在后台实现它,根据需要创建艺术家条目,然后当它们不在被任何专辑使用的时候将其删除。
/* Functions for adding a CD */
int add_cd(char *artist, char *title, char *catalogue, int *cd_id);
int add_tracks(struct current_tracks_st *track);
/* Functions for finding and retrieving a CD */
int find_cds(char *search_str, struct cd_search_st *results);
int get_cd(int cd_id, struct current_cd_st *dest);
int get_cd_tracks(int cd_id, struct current_tracks_st *dest);
/* Function for deleting items */
int delete_cd(int cd_id);
搜索函数相当通用:当传递一个字符串,然后它将在artist、title或catalogue条目中搜索该字符串。
2.测试应用程序接口
在实现接口之前,先编一些代码使用它,这看起来有点奇怪,但在开始实现接口之前了解一下它将如何运转通常是个好办法。
下面是app_test.c的源代码,首先是一些includes和structs:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "app_mysql.h"
int main(){
struct current_cd_st cd;
struct cd_search_st cd_res;
struct current_tracks_st ct;
int cd_id;
int res, i;
/* 应用程序的第一件事始终是初始化一个数据库连接并提供一个正确的用户名和密码 */
database_start("rick", "secret");
/* 测试添加一张CD */
res = add_cd("Mahler", "Symphony No 1", "4596102", &cd_id);
printf("Result of adding a cd was %d, cd_id is %d\n", res, cd_id);
memset(&ct, 0, sizeof(ct));
ct.cd_id = cd_id;
strcpy(ct.track[0], "Langsam Schleppend");
strcpy(ct.track[1], "Krafig bewag");
strcpy(ct.track[2], "Feierlich und gemeseen");
strcpy(ct.track[3], "Sturmish bewegt");
add_tracks(&ct);
/* 现在搜索CD,并从找到的第一张CD中提取信息 */
res = find_cds("Symphony", &cd_res);
printf("Found %d cds, first has ID %d\n", res, cd_res.cd_id[0]);
res = get_cd(cd_res.cd_id[0], &cd);
printf("get_cd returned %d\n", res);
memset(&ct, 0, sizeof(ct));
res = get_cd_tracks(cd_res.cd_id[0], &ct);
printf("get_cd_tracks returned %d\n", res);
printf("Title: %s\n", cd.title);
i = 0;
while(i < res){
printf("\ttrack %d is %s\n", i, ct.track[i]);
i++;
}
/* 最后删除CD */
res = delete_cd(cd_res.cd_id[0]);
printf("Delete_cd returned %s\n", res);
/* 断开连接并退出 */
database_end();
return EXIT_SUCCESS;
}
3.实现接口
实现指定的接口,这些包含在文件app_mysql.c中
首先是一些基本的includes、需要的全局连接结构和一个标志dbconnected,使用它来确保程序不会在没有建立连接的情况下尝试访问数据。