惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
C
Check Point Blog
博客园_首页
B
Blog
D
Docker
U
Unit 42
量子位
I
InfoQ
有赞技术团队
有赞技术团队
Martin Fowler
Martin Fowler
GbyAI
GbyAI
L
LangChain Blog
云风的 BLOG
云风的 BLOG
博客园 - Franky
美团技术团队
T
The Blog of Author Tim Ferriss
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
Vercel News
Vercel News
Recent Announcements
Recent Announcements
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
小众软件
小众软件
Google DeepMind News
Google DeepMind News

博客园 - 柠檬隐士

C#一行代码实现整数取反函数 Win8下使用Ctrl加空格来切换输入法 3L软件网站 - 音速启动 (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示 (C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标? (C#)Windows Shell 外壳编程系列7 - ContextMenu 注册文件右键菜单 .NET 异常处理 WebBrowser控件使用详解 悲壮的一天,记华为核心网面试经历 光驱发展史 50年变大了八万倍!超经典硬盘发展史 回首风雨路 笔记本二十年发展史 技术革命 十年芯片组发展回顾 鼠标37年发展史 浅谈键盘的发展史 声卡发展史 见证PC发展历史:细数内存的发展历程 从胡萝卜到4毫秒:液晶显示器发展的120年 从8Mhz到4Ghz+ CPU发展历程也精彩
SQL连接查询深度探险
柠檬隐士 · 2007-11-01 · via 博客园 - 柠檬隐士

测试环境:

Windows XP Profession

MySQL 5.0.45

Oracle 9i

DB2 UDB 9.1

测试的SQL脚本如下:此脚本适合MySQLDB2,如果要在Oracle上执行,需要做个替换BIGINTàINTEGERVARCHARàVARCHAR2

CREATETABLE CUSTOMERS (

   ID BIGINT NOTNULL,

   NAME VARCHAR(15) NOTNULL,

   AGE INT,

   PRIMARYKEY (ID)

);

CREATETABLE ORDERS (

   ID BIGINT NOTNULL,

   ORDER_NUMBER VARCHAR(15) NOTNULL,

   PRICE DOUBLE PRECISION,

   CUSTOMER_ID BIGINT,

   PRIMARYKEY (ID)

);

COMMIT;

/*

ALTERTABLE ORDERS ADDINDEX FK_CUSTOMER (CUSTOMER_ID), ADDCONSTRAINT FK_CUSTOMER FOREIGNKEY (CUSTOMER_ID) REFERENCES CUSTOMERS (ID);

*/

INSERTINTO CUSTOMERS(ID,NAME,AGE) VALUES(1,'TOM',21);

INSERTINTO CUSTOMERS(ID,NAME,AGE) VALUES(2,'MIKE',24);

INSERTINTO CUSTOMERS(ID,NAME,AGE) VALUES(3,'JACK',30);

INSERTINTO CUSTOMERS(ID,NAME,AGE) VALUES(4,'LINDA',25);

INSERTINTO CUSTOMERS(ID,NAME,AGE) VALUES(5,'TOM',NULL);

COMMIT;

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(1,'TOM_ORDER001',100,1);

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(2,'TOM_ORDER002',200,1);

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(3,'TOM_ORDER003',300,1);

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(4,'MIKE_ORDER001',100,2);

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(5,'JACK_ORDER001',200,3);

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(6,'LINDA_ORDER001',100,4);

INSERTINTO ORDERS(ID,ORDER_NUMBER,PRICE,CUSTOMER_ID) VALUES(7,'UNKNOWNORDER',200,NULL);

COMMIT;

两表的数据如下:

CUSTOMERS表数据:

ORDERS表数据

一、交叉连接(CROSS JOIN

交叉连接(CROSS JOIN):有两种,显式的和隐式的,不带ON子句,返回的是两表的乘积,也叫笛卡尔积。

SELECT O.ID, O.ORDER_NUMBER, C.ID, C.NAME

FROM ORDERS O , CUSTOMERS C

WHERE O.ID=1;

语句2:显式的交叉连接,使用CROSS JOIN

SELECT O.ID,O.ORDER_NUMBER,C.ID,C.NAME

FROM ORDERS O CROSSJOIN CUSTOMERS C

WHERE O.ID=1;

语句1和语句2的结果是相同的,查询结果如下:

二、内连接(INNER JOIN

内连接(INNER JOIN):有两种,显式的和隐式的,返回连接表中符合连接条件和查询条件的数据行。(所谓的链接表就是数据库在做查询形成的中间表)。

例如:下面的语句3和语句4的结果是相同的。

语句3:隐式的内连接,没有INNER JOIN,形成的中间表为两个表的笛卡尔积。

SELECT O.ID,O.ORDER_NUMBER,C.ID,C.NAME

FROM CUSTOMERS C,ORDERS O

WHERE C.ID=O.CUSTOMER_ID;

INNER JOIN,形成的中间表为两个表经过ON条件过滤后的笛卡尔积。

SELECT O.ID,O.ORDER_NUMBER,C.ID,C.NAME

FROM CUSTOMERS C INNERJOIN ORDERS O ON C.ID=O.CUSTOMER_ID;

语句3和语句4的查询结果:

三、外连接(OUTER JOIN):外连不但返回符合连接和查询条件的数据行,还返回不符合条件的一些行。外连接分三类:左外连接(LEFT OUTER JOIN)、右外连接(RIGHT OUTER JOIN)和全外连接(FULL OUTER JOIN)。

三者的共同点是都返回符合连接条件和查询条件(即:内连接)的数据行。不同点如下:

左外连接还返回左表中不符合连接条件单符合查询条件的数据行。

右外连接还返回右表中不符合连接条件单符合查询条件的数据行。

全外连接还返回左表中不符合连接条件单符合查询条件的数据行,并且还返回右表中不符合连接条件单符合查询条件的数据行。全外连接实际是上左外连接和右外连接的数学合集(去掉重复),即“全外=左外 UNION 右外”。

说明:左表就是在“(LEFT OUTER JOIN)”关键字左边的表。右表当然就是右边的了。在三种类型的外连接中,OUTER 关键字是可省略的。

下面举例说明:

语句5:左外连接(LEFT OUTER JOIN

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O LEFTOUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;

语句6:右外连接(RIGHT OUTER JOIN

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O RIGHTOUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;

注意:WHERE条件放在ON后面查询的结果是不一样的。例如:

语句7WHERESELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O LEFTOUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID

WHERE O.ORDER_NUMBER<>'MIKE_ORDER001';

语句8:将语句7中的WHERE条件放到ON后面。

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O LEFTOUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID AND O.ORDER_NUMBER<>'MIKE_ORDER001';

从语句7和语句8查询的结果来看,显然是不相同的,语句8显示的结果是难以理解的。因此,推荐在写连接查询的时候,ON后面只跟连接条件,而对中间表限制的条件都写到WHERE子句中。

语句9:全外连接(FULL OUTER JOIN)。

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O FULL OUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O LEFTOUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID

UNION

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O RIGHTOUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;

四、联合连接(UNION JOIN):这是一种很少见的连接方式。OracleMySQL均不支持,其作用是:找出全外连接和内连接之间差异的所有行。这在数据分析中排错中比较常用。也可以利用数据库的集合操作来实现此功能。

语句11:联合查询(UNION JOIN)例句,还没有找到能执行的SQL环境。

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O UNIONJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID

语句12:语句11DB2下的等价实现。还不知道DB2是否支持语句11呢!

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O FULL OUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID

EXCEPT

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O INNERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;

语句13:语句11Oracle下的等价实现。

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O FULL OUTERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID

MINUS

SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME

FROM ORDERS O INNERJOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;

查询结果如下:

五、自然连接(NATURAL INNER JOIN):说真的,这种连接查询没有存在的价值,既然是SQL2标准中定义的,就给出个例子看看吧。自然连接无需指定连接列,SQL会检查两个表中是否相同名称的列,且假设他们在连接条件中使用,并且在连接条件中仅包含一个连接列。不允许使用ON语句,不允许指定显示列,显示列只能用*表示(ORACLE环境下测试的)。对于每种连接类型(除了交叉连接外),均可指定NATURAL。下面给出几个例子。

语句14

SELECT*

FROM ORDERS O NATURALINNERJOIN CUSTOMERS C;

语句15

SELECT*

FROM ORDERS O NATURALLEFTOUTERJOIN CUSTOMERS C;

语句16

SELECT*

FROM ORDERS O NATURALRIGHTOUTERJOIN CUSTOMERS C;

语句17

SELECT*

FROM ORDERS O NATURAL FULL OUTERJOIN CUSTOMERS C;

六、SQL查询的基本原理:两种情况介绍。

第一、   单表查询:根据WHERE条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的);然后根据SELECT的选择列选择相应的列进行返回最终结果。

第二、   两表连接查询:对两表求积(笛卡尔积)并用ON条件和连接连接类型进行过滤形成中间表;然后根据WHERE条件过滤中间表的记录,并根据SELECT指定的列返回查询结果。

第三、   多表连接查询:先对第一个和第二个表按照两表连接做查询,然后用查询结果和第三个表做连接查询,以此类推,直到所有的表都连接上为止,最终形成一个中间的结果表,然后根据WHERE条件过滤中间表的记录,并根据SELECT指定的列返回查询结果。

理解SQL查询的过程是进行SQL优化的理论依据。

七、ON后面的条件(ON条件)和WHERE条件的区别:

ON条件:是过滤两个链接表笛卡尔积形成中间表的约束条件。

WHERE条件:在有ON条件的SELECT语句中是过滤中间表的约束条件。在没有ON的单表查询中,是限制物理表或者中间查询结果返回记录的约束。在两表或多表连接中是限制连接形成最终中间表的返回结果的约束。

从这里可以看出,将WHERE条件移入ON后面是不恰当的。推荐的做法是:

ON只进行连接操作,WHERE只过滤中间表的记录。

八、总结

连接查询是SQL查询的核心,连接查询的连接类型选择依据实际需求。如果选择不当,非但不能提高查询效率,反而会带来一些逻辑错误或者性能低下。下面总结一下两表连接查询选择方式的依据:

1、 查两表关联列相等的数据用内连接。

2、 Col_LCol_R的子集时用右外连接。

3、 Col_RCol_L的子集时用左外连接。

4、 Col_RCol_L彼此有交集但彼此互不为子集时候用全外。

5、 求差操作的时候用联合查询。

多个表查询的时候,这些不同的连接类型可以写到一块。例如:

 SELECT T1.C1,T2.CX,T3.CY

 FROM TAB1 T1

       INNERJOIN TAB2 T2 ON (T1.C1=T2.C2)

       INNERJOIN TAB3 T3 ON (T1.C1=T2.C3)

       LEFTOUTERJOIN TAB4 ON(T2.C2=T3.C3);

 WHERE T1.X >T3.Y;

上面这个SQL查询是多表连接的一个示范。

----全文完。

说明:此测试环境的SQL脚本来自孙卫秦《精通Hibernate》一书中的客户订单模型。