面试1
2019-03-15 19:00 缩酒 阅读(170) 评论(0) 收藏 举报sql 内连接、外连接、自然连接等各种连接
1、内联接(典型的联接运算,使用像 = 或 <> 之类的比较运算符)。包括相等联接和自然联接。
内联接使用比较运算符根据每个表共有的列的值匹配两个表中的行。例如,检索 students和courses表中学生标识号相同的所有行。
2、外联接。外联接可以是左向外联接、右向外联接或完整外部联接。
在 FROM子句中指定外联接时,可以由下列几组关键字中的一组指定:
1)LEFT JOIN或LEFT OUTER JOIN
左向外联接的结果集包括 LEFT OUTER子句中指定的左表的所有行,而不仅仅是联接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果集行中右表的所有选择列表列均为空值。
2)RIGHT JOIN 或 RIGHT OUTER JOIN
右向外联接是左向外联接的反向联接。将返回右表的所有行。如果右表的某行在左表中没有匹配行,则将为左表返回空值。
3)FULL JOIN 或 FULL OUTER JOIN
完整外部联接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。
3、交叉联接
交叉联接返回左表中的所有行,左表中的每一行与右表中的所有行组合。交叉联接也称作笛卡尔积。
FROM 子句中的表或视图可通过内联接或完整外部联接按任意顺序指定;但是,用左或右向外联接指定表或视图时,表或视图的顺序很重要。有关使用左或右向外联接排列表的更多信息,请参见使用外联接。
例子:
-------------------------------------------------
a表 id name b表 id job parent_id
1 张3 1 23 1
2 李四 2 34 2
3 王武 3 34 4
a.id同parent_id 存在关系
--------------------------------------------------
1) 内连接
select a.*,b.* from a inner join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
2)左连接
select a.*,b.* from a left join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
3 王武 null
3) 右连接
select a.*,b.* from a right join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
null 3 34 4
4) 完全连接
select a.*,b.* from a full join b on a.id=b.parent_id
结果是
1 张3 1 23 1
2 李四 2 34 2
null 3 34 4
3 王武 null
--------------------------------------------------------------------------------------------
一、交叉连接(CROSS JOIN)
交叉连接(CROSS JOIN):有两种,显式的和隐式的,不带ON子句,返回的是两表的乘积,也叫笛卡尔积。
例如:下面的语句1和语句2的结果是相同的。
语句1:隐式的交叉连接,没有CROSS JOIN。
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 CROSS JOIN 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;
语句4:显示的内连接,一般称为内连接,有INNER JOIN,形成的中间表为两个表经过ON条件过滤后的笛卡尔积。
SELECT O.ID,O.ORDER_NUMBER,C.ID,C.NAME
FROM CUSTOMERS C INNER JOIN 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 LEFT OUTER JOIN 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 RIGHT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
注意:WHERE条件放在ON后面查询的结果是不一样的。例如:
语句7:WHERE条件独立。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O LEFT OUTER JOIN 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 LEFT OUTER JOIN 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 OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
注意:MySQL是不支持全外的连接的,这里给出的写法适合Oracle和DB2。但是可以通过左外和右外求合集来获取全外连接的查询结果。下图是上面SQL在Oracle下执行的结果:
语句10:左外和右外的合集,实际上查询结果和语句9是相同的。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O LEFT OUTER JOIN 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 RIGHT OUTER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
语句9和语句10的查询结果是相同的,如下:
四、联合连接(UNION JOIN):这是一种很少见的连接方式。Oracle、MySQL均不支持,其作用是:找出全外连接和内连接之间差异的所有行。这在数据分析中排错中比较常用。也可以利用数据库的集合操作来实现此功能。
语句11:联合查询(UNION JOIN)例句,还没有找到能执行的SQL环境。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O UNION JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID
语句12:语句11在DB2下的等价实现。还不知道DB2是否支持语句11呢!
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O FULL OUTER JOIN 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 INNER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
语句13:语句11在Oracle下的等价实现。
SELECT O.ID,O.ORDER_NUMBER,O.CUSTOMER_ID,C.ID,C.NAME
FROM ORDERS O FULL OUTER JOIN 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 INNER JOIN CUSTOMERS C ON C.ID=O.CUSTOMER_ID;
查询结果如下:
五、自然连接(NATURAL INNER JOIN):说真的,这种连接查询没有存在的价值,既然是SQL2标准中定义的,就给出个例子看看吧。自然连接无需指定连接列,SQL会检查两个表中是否相同名称的列,且假设他们在连接条件中使用,并且在连接条件中仅包含一个连接列。不允许使用ON语句,不允许指定显示列,显示列只能用*表示(ORACLE环境下测试的)。对于每种连接类型(除了交叉连接外),均可指定NATURAL。下面给出几个例子。
语句14:
SELECT *
FROM ORDERS O NATURAL INNER JOIN CUSTOMERS C;
语句15:
SELECT *
FROM ORDERS O NATURAL LEFT OUTER JOIN CUSTOMERS C;
语句16:
SELECT *
FROM ORDERS O NATURAL RIGHT OUTER JOIN CUSTOMERS C;
语句17:
SELECT *
FROM ORDERS O NATURAL FULL OUTER JOIN 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_L是Col_R的子集时用右外连接。
3、 Col_R是Col_L的子集时用左外连接。
4、 Col_R和Col_L彼此有交集但彼此互不为子集时候用全外。
5、 求差操作的时候用联合查询。
多个表查询的时候,这些不同的连接类型可以写到一块。例如:
SELECT T1.C1,T2.CX,T3.CY
FROM TAB1 T1
INNER JOIN TAB2 T2 ON (T1.C1=T2.C2)
INNER JOIN TAB3 T3 ON (T1.C1=T2.C3)
LEFT OUTER JOIN TAB4 ON(T2.C2=T3.C3);
WHERE T1.X >T3.Y;
上面这个SQL查询是多表连接的一个示范。
1 Java中创建对象的5种方式 2 3 作为Java开发者,我们每天创建很多对象,但我们通常使用依赖管理系统,比如Spring去创建对象。然而这里有很多创建对象的方法,我们会在这篇文章中学到。 4 5 Java中有5种创建对象的方式,下面给出它们的例子还有它们的字节码 6 使用new关键字 } → 调用了构造函数 7 使用Class类的newInstance方法 } → 调用了构造函数 8 使用Constructor类的newInstance方法 } → 调用了构造函数 9 使用clone方法 } → 没有调用构造函数 10 使用反序列化 } → 没有调用构造函数 11 12 如果你运行了末尾的的程序,你会发现方法1,2,3用构造函数创建对象,方法4,5没有调用构造函数。 13 1.使用new关键字 14 15 这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。 16 17 Employee emp1 = new Employee(); 18 0: new #19 // class org/programming/mitra/exercises/Employee 19 3: dup 20 4: invokespecial #21 // Method org/programming/mitra/exercises/Employee."":()V 21 22 2.使用Class类的newInstance方法 23 24 我们也可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。 25 26 我们可以通过下面方式调用newInstance方法创建对象: 27 复制代码 28 29 Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance(); 30 或者 31 32 Employee emp2 = Employee.class.newInstance(); 33 51: invokevirtual #70 // Method java/lang/Class.newInstance:()Ljava/lang/Object; 34 35 复制代码 36 3.使用Constructor类的newInstance方法 37 38 和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。 39 40 Constructor<Employee> constructor = Employee.class.getConstructor(); 41 Employee emp3 = constructor.newInstance(); 42 111: invokevirtual #80 // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object; 43 44 这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。想了解这两个newInstance方法的区别,请看这篇Creating objects through Reflection in Java with Example. 45 4.使用clone方法 46 47 无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。 48 49 要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。 50 51 Employee emp4 = (Employee) emp3.clone(); 52 162: invokevirtual #87 // Method org/programming/mitra/exercises/Employee.clone ()Ljava/lang/Object; 53 54 5.使用反序列化 55 56 当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。 57 为了反序列化一个对象,我们需要让我们的类实现Serializable接口 58 59 ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); 60 Employee emp5 = (Employee) in.readObject(); 61 261: invokevirtual #118 // Method java/io/ObjectInputStream.readObject:()Ljava/lang/Object; 62 63 我们从上面的字节码片段可以看到,除了第1个方法,其他4个方法全都转变为invokevirtual(创建对象的直接方法),第一个方法转变为两个调用,new和invokespecial(构造函数调用)。 64 例子 65 66 让我们看一看为下面这个Employee类创建对象: 67 复制代码 68 69 class Employee implements Cloneable, Serializable { 70 private static final long serialVersionUID = 1L; 71 private String name; 72 public Employee() { 73 System.out.println("Employee Constructor Called..."); 74 } 75 public String getName() { 76 return name; 77 } 78 public void setName(String name) { 79 this.name = name; 80 } 81 @Override 82 public int hashCode() { 83 final int prime = 31; 84 int result = 1; 85 result = prime * result + ((name == null) ? 0 : name.hashCode()); 86 return result; 87 } 88 @Override 89 public boolean equals(Object obj) { 90 if (this == obj) 91 return true; 92 if (obj == null) 93 return false; 94 if (getClass() != obj.getClass()) 95 return false; 96 Employee other = (Employee) obj; 97 if (name == null) { 98 if (other.name != null) 99 return false; 100 } else if (!name.equals(other.name)) 101 return false; 102 return true; 103 } 104 @Override 105 public String toString() { 106 return "Employee [name=" + name + "]"; 107 } 108 @Override 109 public Object clone() { 110 Object obj = null; 111 try { 112 obj = super.clone(); 113 } catch (CloneNotSupportedException e) { 114 e.printStackTrace(); 115 } 116 return obj; 117 } 118 } 119 120 复制代码 121 122 下面的Java程序中,我们将用5种方式创建Employee对象。你可以从GitHub找到这些代码。 123 复制代码 124 125 public class ObjectCreation { 126 public static void main(String... args) throws Exception { 127 // By using new keyword 128 Employee emp1 = new Employee(); 129 emp1.setName("Naresh"); 130 System.out.println(emp1 + ", hashcode : " + emp1.hashCode()); 131 // By using Class class's newInstance() method 132 Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee") 133 .newInstance(); 134 // Or we can simply do this 135 // Employee emp2 = Employee.class.newInstance(); 136 emp2.setName("Rishi"); 137 System.out.println(emp2 + ", hashcode : " + emp2.hashCode()); 138 // By using Constructor class's newInstance() method 139 Constructor<Employee> constructor = Employee.class.getConstructor(); 140 Employee emp3 = constructor.newInstance(); 141 emp3.setName("Yogesh"); 142 System.out.println(emp3 + ", hashcode : " + emp3.hashCode()); 143 // By using clone() method 144 Employee emp4 = (Employee) emp3.clone(); 145 emp4.setName("Atul"); 146 System.out.println(emp4 + ", hashcode : " + emp4.hashCode()); 147 // By using Deserialization 148 // Serialization 149 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.obj")); 150 out.writeObject(emp4); 151 out.close(); 152 //Deserialization 153 ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); 154 Employee emp5 = (Employee) in.readObject(); 155 in.close(); 156 emp5.setName("Akash"); 157 System.out.println(emp5 + ", hashcode : " + emp5.hashCode()); 158 } 159 } 160 161 复制代码 162 163 程序会输出: 164 复制代码 165 166 Employee Constructor Called... 167 Employee [name=Naresh], hashcode : -1968815046 168 Employee Constructor Called... 169 Employee [name=Rishi], hashcode : 78970652 170 Employee Constructor Called... 171 Employee [name=Yogesh], hashcode : -1641292792 172 Employee [name=Atul], hashcode : 2051657 173 Employee [name=Akash], hashcode : 63313419 174 175 复制代码
区别1:& 和 | 可用于bitwise operation,即二进制运算,而&&和 || 不可以。 区别2:在逻辑运算时,&& 和 || 叫做short-circuit logical operator, 意思是先判定左侧的逻辑值,如果可以决定结果则不再浪费时间去判定右侧的逻辑值。例如(2<3) || (a*5+b/3-c>5),因为(2<3)是true,无论右侧是true or false,结果都是true, 所以右侧将不再进行判定。而& 和 | 则总会对两侧进行判定,称为non-short-circuit logical operator.
默认支持一级缓存,二级缓存需要配置
上述运行结果可以看出,第一次发起查询用户ID为1的用户时,先去找缓存中是否有相应的用户信息,没有,才从数据库查询。得到用户信息后,将信息存储到一级缓存中。第二查询用户ID为1的用户信息时,根本没有执行查询语句,而是直接从缓存中查找,有,就直接获取用户信息。
再来看一个测试实例:
上述运行结果可以看出,第一次发起查询用户ID为1的用户时,依然先去找缓存中是否有相应的用户信息,没有,才从数据库查询。得到用户信息后,将信息存储到一级缓存中。这期间SqlSession执行了一次commit操作,清空了SqlSession中的一级缓存,这样做的目的是为了让缓存中的数据总是最新的数据,避免脏读。之后,再次进行第二查询用户ID为1的用户信息时,依然是先从缓存中查找,没有,所以执行了sql查询语句,从数据库中获取了最新的数据。另外,当SqlSession关闭时,也会清空一级缓存。
--------------------
当开启MyBatis的二级缓存之后,SqlSession1去查询用户ID为1的用户信息,查询到用户信息会将数据存储到二级缓存中。SqlSession2去查询用户ID为1的用户信息,先去缓存中找是否有相应数据,有则直接获取。
MyBatis的二级缓存是mapper范围级别的,所以除了在SQLMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。如下:
1.在核心配置文件SQLMapConfig.xml中的全局设置中开启二级缓存,将value设为true。如下:
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
2.在对应的Mapper.xml中开启二级缓存,即:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace命名空间,作用就是对sql进行分类化管理,理解sql隔离
注意:使用mapper代理方法开发,namespace有特殊重要的作用,namespace等于mapper接口地址
-->
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
<!-- 开启本mapper的namespace下的二缓存
type:指定cache接口的实现类的类型,mybatis默认使用PerpetualCache
要和ehcache整合,需要配置type为ehcache实现cache接口的类型
-->
<cache />
<!-- 下面的一些SQL语句暂时略 -->
</mapper>
---------------------

很明显,测试方法中在第一次查询结束后关闭了SqlSession。第二次查询的结果是从缓存中直接获取的数据。这就证明了二级缓存比一级缓存的范围大,只要是同一个mapper,都可以从二级缓存中得到数据。
需要注意的一点是,在一级缓存中也提到过,当我们对数据库进行增删改的时候,数据库数据就进行了修改,这时任何缓存中的数据都需要被清楚,以免造成数据库的查询出现脏读。所以我们需要在每一次的insert、update、delete操作后都进行缓存刷新(即清空),也就是在Statement配置中配置flushCache属性,如下:
<insertid="insertUser" parameterType="cn.itcast.mybatis.po.User"flushCache="true">
---------------------
浙公网安备 33010602011771号