代码改变世界

面试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.

 

 默认支持一级缓存,二级缓存需要配置

上述运行结果可以看出,第一次发起查询用户ID1的用户时,先去找缓存中是否有相应的用户信息,没有,才从数据库查询。得到用户信息后,将信息存储到一级缓存中。第二查询用户ID1的用户信息时,根本没有执行查询语句,而是直接从缓存中查找,有,就直接获取用户信息。

再来看一个测试实例:

上述运行结果可以看出,第一次发起查询用户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">
---------------------