XQuery/XQuery 从 SQL
一个广泛用于教授关系数据库的经典数据库,涉及员工、部门和薪资等级。在 Oracle 培训资料中,它以演示用户名和密码 Scott/Tiger 而闻名。
这三个表被转换为 XML(通过 Excel 的 XML 附加程序 XQuery/Excel 和 XML),如下所示
Oracle SQL 文件的 MySQL 端口可以在这里找到 这里.
eXist 演示服务器用于 XQuery 示例。这些示例以纯 XML 或转换为表格格式返回。
等效的 SQL 查询在 w:MySQL 服务器上执行,该服务器也位于 布里斯托尔西英格兰大学
SQL: select count(*) from Emp; MySQL
XQuery: count(//Emp) XML
SQL: select count(*) from dept MySQL
XQuery: count(//Dept) XML
SQL: select * from emp where sal > 1000; MySQL
XQuery: //Emp[Sal>1000] XML 表格
SQL: select * from emp where sal between 1000 and 2000; MySQL
XQuery: //Emp[Sal>1000][Sal<2000] XML 表格
这里,连续的过滤条件取代了“between”隐含的与条件。
尽管 XQuery 中没有“between”函数,但编写一个很简单
declare function local:between($value as xs:decimal, $min as xs:decimal, $max as xs:decimal) as xs:boolean { $value >= $min and $value <= $max };
这简化了查询为 //Emp[local:between(Sal,1000,2000)] XML 表格
并且具有将 Sal 转换为数字现在在函数签名中隐含的优点。
SQL: select * from emp where comm is null; MySQL
XQuery: //Emp[empty(Comm/text())] XML 表格
请注意,empty(Comm) 不够,因为这只有在元素本身不存在时才为真,在本示例 XML 中它并不存在。
XQuery: //Emp[empty(Comm)] XML
任务: 选择前 5 名员工
[edit | edit source]SQL: select * from emp limit 5; MySQL
XQuery: //Emp[position() <=5]
选择列
[edit | edit source]列出员工姓名和薪资
[edit | edit source]SQL: Select ename,sal from emp MySQL
令人惊讶的是,XPath 不支持仅选择节点中子节点的子集(修剪)。
//Emp/(Ename,Sal) XML 检索了所需的元素,但父 Emp 节点已丢失。
//Emp/(Ename|Sal) XML 更好,因为它按顺序保持元素,但它不会返回仅包含 Ename 和 Sal 子节点的 Emp 节点,如所要求的那样。
//Emp/*[name(.) = ("Ename","Sal")] XML 使用元素名称的反射。
XQuery
for $emp in //Emp return <Emp> {$emp/(Ename|Sal)} </Emp>
这里使用 XQuery FLWOR 表达式从原始元素创建新的 EMP 元素。
计算值
[edit | edit source]计算年薪
[edit | edit source]任务: 计算所有员工的年薪。年薪按月薪的 12 倍加上佣金计算。由于佣金可能是空值,因此必须用合适的数值替换。
SQL: select 12 * sal + ifnull(comm,0) from emp; MySQL
XQuery: //Emp/(12*number(Sal)+(if(exists(Comm/text())) then number(Comm) else 0)) XML
SQL 函数 COALESCE 与 IFNULL 相同,但它接受多个参数。
SQL: select 12 * sal + coalesce(comm,0) from emp; MySQL
XQuery: //Emp/(12*number(Sal)+ number((Comm/text(),0)[1])) XML
在这个简单的例子中,由于缺少用于承载项目类型的信息的模式,因此需要显式地将字符串转换为数字。
注意 XQuery 习惯用法
(Comm/text(),0)[1]
计算序列中的第一个非空项目,与 COALESCE 相对应。
选择和创建列
[edit | edit source]任务: 列出员工姓名及其年薪。
SQL: select ename, 12 * sal + ifnull(comm,0) as "Annual Salary" from emp; MySQL
XQuery
for $emp in //Emp return <Emp> {$emp/Ename} <AnnualSalary> {12*number($emp/Sal)+ (if (exists($emp/Comm/text())) then number($emp/Comm) else 0) } </AnnualSalary> </Emp>
我们再次遇到了树修剪的问题,但现在还增加了嫁接,这也需要显式地构造 XML 节点。
SQL 运算符
[edit | edit source]IN
[edit | edit source]任务: 显示所有工作职位为 ANALYST 或 MANAGER 的员工。
SQL: select * from emp where job in ("ANALYST","MANAGER") MySQL
XQuery: //Emp[Job = ("ANALYST","MANAGER")]
NOT IN
[edit | edit source]任务: 选择所有工作职位不为 'ANALYST' 或 'MANAGER' 的员工。
SQL: select * from emp where job not in ("ANALYST","MANAGER") MySQL
这不起作用
XQuery: //Emp[Job !=("ANALYST","MANAGER")] XML 表格
这里的广义等于始终为真,因为每个人要么不是 ANALYST,要么不是 MANAGER。这可以工作。
XQuery: //Emp[not(Job =("ANALYST","MANAGER"))] XML 表格
不同的值
[edit | edit source]任务: 显示员工担任的不同职位。
MySQL: select distinct job from emp; MySQL
XQuery: distinct-values(//Emp/Job) XML
模式匹配
[edit | edit source]任务: 列出所有姓名以 "S" 开头的员工。
MySQL: select * from emp where ename like "S%"; MySQL
XQuery: //Emp[starts-with(Ename,"S")] XML 表格
任务: 列出所有姓名包含 "AR" 的员工。
MySQL: select * from emp where ename like "%AR%"; MySQL
XQuery: //Emp[contains(Ename,"AR")] XML 表格
参见 contains()
任务: 列出所有姓名包含 "ar" 的员工,不区分大小写。
MySQL: select * from emp where ename like "%ar%"; MySQL
SQL 中的 LIKE 不区分大小写,但 fn:contains() 区分大小写,因此需要转换大小写。
XQuery: //Emp[contains(upper-case(Ename),upper-case("ar"))] XML 表格
参见 upper-case()
更复杂的模式需要正则表达式。
MySQL: select * from emp where ename regexp "M.*R"; MySQL
XQuery: //Emp[matches(Ename,"M.*R")] XML 表格
参见 matches()
类似地,SQL 的 REGEXP 不区分大小写,而 XQuery matches() 中的附加标志控制匹配。
MySQL: select * from emp where ename regexp "m.*r"; MySQL
XQuery: //Emp[matches(Ename,"m.*r",'i')] XML 表格
('i' 使正则表达式匹配不区分大小写。)
表连接
[edit | edit source]简单的内连接
[edit | edit source]任务: 查找员工 'SMITH' 所在部门的名称。
SQL
select dept.dname
from emp, dept
where dept.deptno = emp.deptno
and ename='SMITH';
XPath : //Dept[DeptNo = //Emp[Ename='SMITH']/DeptNo]/Dname
也许 XQuery 中的 FLWOR 表达式更易读
let $dept := //Emp[Ename='SMITH']/DeptNo return //Dept[DeptNo = $dept ]/Dname
任务:查找所有会计部门员工的姓名
SQL
select emp.ename
from emp,dept
where dept.deptno = emp.deptno
and dname='Accounting';
XPath://Emp[DeptNo = //Dept[Dname='Accounting']/DeptNo]/Ename
XQuery
let $dept := //Dept[Dname='Accounting']/DeptNo return //Emp[DeptNo = $dept]/Ename
注意,在这个版本的 eXist 中,等式中操作数的顺序很重要 - 在以后的版本中会修复。
XQuery://Emp[Dname='Accounting'/DeptNo = //Emp/DeptNo]/Ename
任务:列出每个员工的姓名,以及他们所在部门的名称和位置。
SQL
select ename, dname,location from emp, dept where emp.deptno = dept.deptno;
如果必须从多个节点中选择元素,XPath 就无能为力,需要使用 XQuery
XQuery
此连接可以写成
for $emp in //Emp for $dept in //Dept where $dept/DeptNo= $emp/DeptNo return <Emp> {$emp/Ename} {$dept/(Dname|Location)} </Emp>
但更常见的是以子选择的形式编写
for $emp in //Emp let $dept := //Dept[DeptNo=$emp/DeptNo] return <Emp> {$emp/Ename} {$dept/(Dname|Location)} </Emp>
任务:列出所有分析师的姓名和部门。
SQL
select ename, dname from emp, dept where emp.deptno = dept.deptno and job="ANALYST";
XQuery
for $emp in //Emp[Job='ANALYST'] let $dept := //Dept[DeptNo= $emp/DeptNo] return <Emp> {$emp/Ename} {$dept/Dname} </Emp>
任务:列出部门以及每个部门的员工数量。
SQL
select dname, (select count(*) from emp where deptno = dept.deptno ) as headcount from dept;
XQuery
for $dept in //Dept let $headCount := count(//Emp[DeptNo=$dept/DeptNo]) return <Dept> {$dept/Dname} <HeadCount>{$headCount}</HeadCount> </Dept>
任务:按升序排列的薪级顺序列出员工姓名和薪级。
薪级由最低和最高工资定义。
SQL
select ename, grade from emp, salgrade where emp.sal between salgrade.losal and salgrade.hisal;
XQuery
for $emp in //Emp let $grade := //SalGrade[number($emp/Sal) > number(LoSal)][number($emp/Sal) < number(HiSal)]/Grade order by $grade return <Emp> {$emp/Ename} {$grade} </Emp>
员工与其经理之间的关系是递归关系。
任务:列出每个员工的姓名以及他们经理的姓名。
SQL
select e.ename, m.ename from emp e join emp m on e.mgr = m.empno
XQuery
for $emp in //Emp let $manager := //Emp[EmpNo = $emp/MgrNo] return <Emp> {$emp/Ename} <Manager>{string($manager/Ename)}</Manager> </Emp>
XQuery 的结果与 SQL 的结果不太一样。King 没有经理,因此缺席了 SQL 内连接。要在 XQuery 中产生相同的结果,我们需要过滤具有经理的员工
for $emp in //Emp[MgrNo] let $manager := //Emp[EmpNo = $emp/MgrNo] where $emp/MgrNo/text() return <Emp> {$emp/Ename} <Manager>{string($manager/Ename)}</Manager> </Emp>
或者,外连接会返回所有员工,包括 King
SQL
select e.ename, m.ename from emp e left join emp m on e.mgr = m.empno
经理关系定义了一个树状结构,King 是根节点,她的直属下属是她孩子,依此类推。XQuery 中的递归函数可以解决此任务。
XQuery
declare function local:hierarchy($emp) { <Emp name='{$emp/Ename}'> <Reports> {for $e in //Emp[MgrNo = $emp/EmpNo] return local:hierarchy($e) } </Reports> </Emp> }; local:hierarchy(//Emp[empty(MgrNo/text())])
为了导出,可以创建一个单个 XML 文件,其中员工嵌套在部门中。由于 Dept/Emp 关系恰好是一对多,因此可以在不引入冗余或数据丢失的情况下做到这一点。
XQuery
<Company> {for $dept in //Dept return <Department> {$dept/*} {for $emp in //Emp[DeptNo = $dept/DeptNo] return $emp } </Department> } </Company>
使用这种简单的方法,Emp 中的外部键 DeptNo 已被包含,但现在它已变得多余。except 运算符在这里很有用
<Company> {for $dept in //Dept return <Department> {$dept/*} {for $emp in //Emp[DeptNo = $dept/DeptNo] return <Employee> {$emp/* except $emp/DeptNo} </Employee> } </Department> } </Company>
注意,这假设没有要复制的属性。如果有,它们将使用 $emp/@*
复制
此层次数据可以在 XQuery 中直接查询。
几乎所有查询都保持不变(除了将元素名称更改为 Employee)。这是因为用于在 Emp.xml 文档中选择 Emp 的路径是 //Emp
,现在在合并的文档中是 //Employee
。如果使用完整的路径(/EmpList/Emp
),则需要将其替换为 /Company/Department/Employee
任务:查找员工“Smith”的部门名称。
XQuery://Employee[Ename='SMITH']/../Dname
XML
任务:查找会计部门员工的姓名。
XQuery://Department[Dname='Accounting']/Employee/Ename
XML
主要变化在于需要在员工和部门之间进行连接的查询,因为它们已经嵌套在一起,因此变成了向上(从员工到部门)或向下(从部门到员工)导航树。
列出员工及其所在部门位置的查询(使用单独的文档)为
for $emp in //Emp for $dept in //Dept where $dept/DeptNo=$emp/DeptNo return <Emp> {$emp/Ename} {$dept/(Dname|Location)} </Emp>
对于一个嵌套文档,它变成了
for $emp in //Employee return <Employee> {$emp/Ename} {$emp/../Location} </Employee>
列出部门和员工数量(在单独的表格中)为
for $dept in //Dept let $headCount := count(//Emp[DeptNo=$dept/DeptNo]) return <Dept> {$dept/Dname} <HeadCount>{$headCount}</HeadCount> </Dept>
它变成了
for $dept in //Department let $headCount := count($dept/Employee) return <Department> {$dept/Dname} <HeadCount>{$headCount}</HeadCount> </Department>
任务:显示经理的数量、平均工资(四舍五入)、最低工资和最高工资。
SQL:SELECT count(*), round(avg(sal)), min(sal), max(sal) FROM emp WHERE job='MANAGER'; MySQL
XQuery
(count(//Emp[Job='MANAGER']),round(avg(//Emp[Job='MANAGER']/Sal)),min(//Emp[Job='MANAGER']/Sal),max( //Emp[Job='MANAGER']/Sal))
最好将 XPath 表达式分解为员工子集
let $managers := //Emp[Job='MANAGER'] return (count($managers),round(avg($managers/Sal)),min($managers/Sal),max($managers/Sal))
最好为计算的各个值添加标签
let $managers := //Emp[Job='MANAGER'] return <Statistics> <Count>{count($managers)}</Count> <Average>{round(avg($managers/Sal))}</Average> <Min>{min($managers/Sal)}</Min> <Max>{max($managers/Sal)}</Max> </Statistics>
任务:显示每个职位的员工数量、平均工资(四舍五入)、最低工资和最高工资。
SQL:SELECT job, count(*), round(avg(sal)), min(sal), max(sal) FROM emp GROUP BY job; MySQL
在 XQuery 中,分组必须通过遍历各组来完成。每个组都由职位标识,我们可以使用 distinct-values 函数获取所有职位的集合(序列)
for $job in distinct-values(//Emp/Job) let $employees := //Emp[Job=$job] return <Statistics> <Job>{$job}</Job> <Count>{count($employees )}</Count> <Average>{round(avg($employees/Sal))}</Average> <Min>{min($employees/Sal)}</Min> <Max>{max($employees/Sal)}</Max> </Statistics>
任务:列出部门、部门员工姓名和工资,以及每个部门的总工资。
这将生成嵌套表格。
SQL: ?
XQuery
<Report> { for $dept in //Dept let $subtotal := sum(//Emp[DeptNo = $dept/DeptNo]/Sal) return <Department> {$dept/Dname} {for $emp in //Emp[DeptNo = $dept/DeptNo] return <Emp> {$emp/Ename} {$emp/Sal} </Emp> } <SubTotal>{$subtotal}</SubTotal> </Department> } <Total>{sum(//Emp/Sal)}</Total> </Report>
请注意,XQuery 语言的函数特性意味着每个总计必须显式计算,而不是像在命令式语言中那样累积计算。这样一来,公式就明确且独立,因此可以放在报告中的任何位置,例如放在开头而不是结尾。
<Report> <Total>{sum(//Emp/Sal)}</Total> { for $dept in //Dept let $subtotal := sum(//Emp[DeptNo = $dept/DeptNo]/Sal) return <Department> <SubTotal>{$subtotal}</SubTotal> {$dept/Dname} {for $emp in //Emp[DeptNo = $dept/DeptNo] return <Emp> {$emp/Ename} {$emp/Sal} </Emp> } </Department> } </Report>
任务:显示每个职位的员工数量、平均工资(四舍五入)、最低工资和最高工资,条件是每个组至少要有 2 名员工。
SQL
SELECT job, count(*), round(avg(sal)), min(sal), max(sal) FROM emp GROUP BY job HAVING count(*) > 1;
XQuery
for $job in distinct-values(//Emp/Job) let $employees := //Emp[Job=$job] where count($employees) > 1 return <Statistics> <Job>{$job}</Job> <Count>{count($employees )}</Count> <Average>{round(avg($employees /Sal))}</Average> <Min>{min($employees /Sal)}</Min> <Max>{max($employees /Sal)}</Max> </Statistics>
任务:列出所有在本世纪雇用的员工。
SQL:SELECT * from job where hiredate >= '2000-01-01' MySQL
XQuery://Emp[HireDate >= '2000-01-01']
实际上,由于缺乏定义 HireDate 为 xs:date 的模式,因此此比较是一个字符串比较。