以下讨论提供了 MySQL 检测函数依赖关系的多种方式。这些示例使用以下符号:
{X} -> {Y}
理解为 “X
唯一确定 Y
,”,这也意味着 Y
在 X
上具有函数依赖关系。
这些示例使用 world
数据库,可以从 https://dev.mysql.com/doc/index-other.html 下载。您可以在同一页上找到安装数据库的详细信息。
以下查询选择每个国家的语言数量:
SELECT co.Name, COUNT(*)
FROM countrylanguage cl, country co
WHERE cl.CountryCode = co.Code
GROUP BY co.Code;
co.Code
是 co
的主键,因此 co
的所有列都在 co.Code
上具有函数依赖关系,如下所示:
{co.Code} -> {co.*}
因此,co.name
在 GROUP BY
列和查询有效。
可以使用 UNIQUE
索引覆盖 NOT NULL
列,而不是主键,并且相同的函数依赖关系将适用。(这不适用于允许 NULL
值的 UNIQUE
索引,因为它允许多个 NULL
值,从而失去了唯一性。)
以下查询选择每个国家的所有语言和使用人数:
SELECT co.Name, cl.Language,
cl.Percentage * co.Population / 100.0 AS SpokenBy
FROM countrylanguage cl, country co
WHERE cl.CountryCode = co.Code
GROUP BY cl.CountryCode, cl.Language;
对 (cl.CountryCode
, cl.Language
) 是 cl
的复合主键,因此该列对唯一确定 cl
的所有列:
{cl.CountryCode, cl.Language} -> {cl.*}
此外,因为 WHERE
子句中的等式:
{cl.CountryCode} -> {co.Code}
并且,因为 co.Code
是 co
的主键:
{co.Code} -> {co.*}
“唯一确定”关系是传递的,因此:
{cl.CountryCode, cl.Language} -> {cl.*,co.*}
因此,查询有效。
与前一个示例相同,UNIQUE
键覆盖 NOT NULL
列可以代替主键。
可以使用 INNER JOIN
条件代替 WHERE
。相同的函数依赖关系适用:
SELECT co.Name, cl.Language,
cl.Percentage * co.Population/100.0 AS SpokenBy
FROM countrylanguage cl INNER JOIN country co
ON cl.CountryCode = co.Code
GROUP BY cl.CountryCode, cl.Language;
虽然 WHERE
子句或 INNER JOIN
条件中的等式测试是对称的,但外连接条件中的等式测试不是,因为表格扮演着不同的角色。
假设 referential integrity 已经被意外破坏,并且存在一个没有对应 country
行的 countrylanguage
行。考虑与前一个示例相同的查询,但使用 LEFT JOIN
:
SELECT co.Name, cl.Language,
cl.Percentage * co.Population/100.0 AS SpokenBy
FROM countrylanguage cl LEFT JOIN country co
ON cl.CountryCode = co.Code
GROUP BY cl.CountryCode, cl.Language;
对于给定的 cl.CountryCode
值,join 结果中的 co.Code
值要么在匹配的行中找到(由 cl.CountryCode
确定),要么如果没有匹配则为空补充(也由 cl.CountryCode
确定)。在每种情况下,以下关系都适用:
{cl.CountryCode} -> {co.Code}
cl.CountryCode
本身对 {cl.CountryCode
, cl.Language
} 函数依赖,这是一个主键。
如果在 join 结果中 co.Code
为空补充,那么 co.Name
也为空补充。如果 co.Code
不为空补充,那么因为 co.Code
是主键,它确定 co.Name
。因此,在所有情况下:
{co.Code} -> {co.Name}
这就产生了:
{cl.CountryCode, cl.Language} -> {cl.*,co.*}
因此,查询是有效的。
然而,假设表格被交换,如在这个查询中:
SELECT co.Name, cl.Language,
cl.Percentage * co.Population/100.0 AS SpokenBy
FROM country co LEFT JOIN countrylanguage cl
ON cl.CountryCode = co.Code
GROUP BY cl.CountryCode, cl.Language;
现在这个关系不适用:
{cl.CountryCode, cl.Language} -> {cl.*,co.*}
实际上,所有为空补充的行都被分配到一个组中(它们都有相同的 GROUP BY
列为空),在这个组中 co.Name
的值可以变化。查询是无效的,MySQL 拒绝了它。
因此,在外连接中,函数依赖关系取决于确定列是否属于左侧或右侧的 LEFT JOIN
。如果有嵌套的外连接或连接条件不仅仅是相等比较,那么确定函数依赖关系将变得更加复杂。
假设一个国家视图产生了它们的代码、名称的大写形式和官方语言的数量:
CREATE VIEW country2 AS
SELECT co.Code, UPPER(co.Name) AS UpperName,
COUNT(cl.Language) AS OfficialLanguages
FROM country AS co JOIN countrylanguage AS cl
ON cl.CountryCode = co.Code
WHERE cl.isOfficial = 'T'
GROUP BY co.Code;
这个定义是有效的,因为:
{co.Code} -> {co.*}
在视图结果中,第一个选择的列是 co.Code
,它也是组列,因此确定了所有其他选择的表达式:
{country2.Code} -> {country2.*}
MySQL 理解了这一点,并使用了这些信息,如下所述。
这个查询显示了国家、它们的官方语言数量和城市数量,通过将视图与 city
表连接:
SELECT co2.Code, co2.UpperName, co2.OfficialLanguages,
COUNT(*) AS Cities
FROM country2 AS co2 JOIN city ci
ON ci.CountryCode = co2.Code
GROUP BY co2.Code;
这个查询是有效的,因为之前所见:
{co2.Code} -> {co2.*}
MySQL 能够在视图结果中发现函数依赖关系,并使用该信息来验证使用视图的查询。如果 country2
是一个派生表(或公共表表达式),那么也是如此:
SELECT co2.Code, co2.UpperName, co2.OfficialLanguages,
COUNT(*) AS Cities
FROM
(
SELECT co.Code, UPPER(co.Name) AS UpperName,
COUNT(cl.Language) AS OfficialLanguages
FROM country AS co JOIN countrylanguage AS cl
ON cl.CountryCode=co.Code
WHERE cl.isOfficial='T'
GROUP BY co.Code
) AS co2
JOIN city ci ON ci.CountryCode = co2.Code
GROUP BY co2.Code;