常见SQL业务语句实现
in Projects with 0 comment

常见SQL业务语句实现

in Projects with 0 comment

单表

分页

PageHelper是早期MyBatis技术栈中最为常用的分页方式。(早先常见的面试题就有:如何实现数据库的分页?有哪几种方式?)

PageHelper有哪些使用方式呢?

//第一种,RowBounds方式的调用
List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));

//第二种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);

//第三种,Mapper接口方式的调用,推荐这种使用方式。
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);

//第四种,参数方法调用
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<User> selectByPageNumSize(
            @Param("user") User user,
            @Param("pageNum") int pageNum, 
            @Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代码中直接调用:
List<User> list = userMapper.selectByPageNumSize(user, 1, 10);

//第五种,参数对象
//如果 pageNum 和 pageSize 存在于 User 对象中,只要参数有值,也会被分页
//有如下 User 对象
public class User {
    //其他fields
    //下面两个参数名和 params 配置的名字一致
    private Integer pageNum;
    private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 处理后两个参数
public interface CountryMapper {
    List<User> selectByPageNumSize(User user);
}
//当 user 中的 pageNum!= null && pageSize!= null 时,会自动分页
List<User> list = userMapper.selectByPageNumSize(user);

//第六种,ISelect 接口方式
//jdk6,7用法,创建接口
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectGroupBy();
    }
});
//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());

//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectGroupBy();
    }
});
//对应的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());

//count查询,返回一个查询语句的count数
long total = PageHelper.count(new ISelect() {
    @Override
    public void doSelect() {
        userMapper.selectLike(user);
    }
});
//lambda
total = PageHelper.count(()->userMapper.selectLike(user));

分页实现

如何实现分页

我们知道如何使用PageHelper后,我们发现使用PageHelper.startPage(pageNum, pageSize, orderBy)方法后的第一个select是具备分页能力的,那它是如何做到的呢?

理解它的原理,有两个点:

第一,相对对于JDBC这种嵌入式的分页而言,PageHelper分页是独立的,能做到独立分页查询,那它必然是通过某个拦截点进行了拦截,这样它才能够进行解耦分离出分页。

第二,我们通过PageHelper.startPage(pageNum, pageSize, orderBy)方法后的第一个select是具备分页能力的,那它必然缓存了分页信息,同时结合线程知识,这里必然使用的是本地栈ThreadLocal,即每个线程有一个本地缓存。

所以结合这两点,聪明的你就会想到它大概是如何实现的,关键就是两点(拦截,ThreadLocal).

如何评价PageHelper的封装和使用?

就使用PageHelper本身而言,第二种和第三种方式较为中规中矩,它是早前绝大多数的使用方式。并不是说中规中矩就是不好的,很多场景下保守能让团队的绝大多数人接受便是好的方式。当更多的人接受Java8 lambda方式后,Page page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());这种方式显然对于他们来说更为优雅。

就本例对PageHelper的封装来看,通常而言针对PageHelper封装有两个方式,一种是封装PageUtils,另一种就是放在上层Controller中, 这个例子中使用第二种。它并不是非常完美的封装方式,但确实是在有限的需求内是一个完整的封装。需要记住一点,过多的设计和封装也没有必要,重点在于封装是伴随着需求的改变而适度调整而来的。这个例子中可能出现需求变更从而影响其封装的点,比如排序可能是组合排序,比如先按照A字段的升序再按照B字段的降序排序,比如SQLUtil等带来的Util冗余等。

就分页未来来看,PageHelper不是长远的趋势,真正长远的趋势应该是被ORM融合,并且考虑更多的适用场景提供各种常见实现方式的封装功能(即约定大于实现中的约定),同时结合builder参数构建和lambda方式写法等语言特性写法上做到优雅。所以你可以看到JPA,MyBatis-plus等中相关的封装和实现就是这种趋势。

多表

业务

用户管理:关联角色,关联部门,及通过部门过滤用户

image
image-1663233779341

角色管理

image-1663233864852

部门管理(树形结构)

image-1663233894280

需求

我们来看下角色(一对多,一个用户有多个角色)和部门(一对一,一个用户属于一个部门)是如何实现的。

项目

User

 public class User extends BaseEntity {

   // ...

   /**
    * 部门对象
    */
   private Dept dept;

   private List<Role> roles;

}

User和Role的关联类

public class UserRole {
  /**
   * 用户ID
   */
  private Long userId;

  /**
   * 角色ID
   */
  private Long roleId;

  public Long getUserId() {
      return userId;
  }

  public void setUserId(Long userId) {
      this.userId = userId;
  }

  public Long getRoleId() {
      return roleId;
  }

  public void setRoleId(Long roleId) {
      this.roleId = roleId;
  }

  @Override
  public String toString() {
      return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
              .append("userId", getUserId())
              .append("roleId", getRoleId())
              .toString();
  }
}

UserMapper中通过association关联dept,和通过collection关联role集合。


<resultMap type="tech.pdai.mybatis.xml.web.system.user.domain.User" id="UserResult">
   	<id     property="userId"       column="user_id"      />
   	<result property="deptId"       column="dept_id"      />
   	<result property="loginName"    column="login_name"   />
   	<result property="userName"     column="user_name"    />
   	<result property="email"        column="email"        />
   	<result property="phonenumber"  column="phonenumber"  />
   	<result property="sex"          column="sex"          />
   	<result property="avatar"       column="avatar"       />
   	<result property="status"       column="status"       />
   	<result property="delFlag"      column="del_flag"     />
   	<result property="createBy"     column="create_by"    />
   	<result property="createTime"   column="create_time"  />
   	<result property="updateBy"     column="update_by"    />
   	<result property="updateTime"   column="update_time"  />
   	<result property="remark"       column="remark"       />
   	<association property="dept"    column="dept_id" javaType="tech.pdai.mybatis.xml.web.system.dept.domain.Dept" resultMap="deptResult" />
   	<collection  property="roles"   javaType="java.util.List"        resultMap="RoleResult" />
   </resultMap>
 

<resultMap id="deptResult" type="tech.pdai.mybatis.xml.web.system.dept.domain.Dept">
   	<id     property="deptId"   column="dept_id"     />
   	<result property="parentId" column="parent_id"   />
   	<result property="deptName" column="dept_name"   />
   	<result property="orderNum" column="order_num"   />
   	<result property="leader"   column="leader"   />
   	<result property="status"   column="dept_status" />
   </resultMap>
   
   <resultMap id="RoleResult" type="tech.pdai.mybatis.xml.web.system.role.domain.Role">
   	<id     property="roleId"       column="role_id"        />
   	<result property="roleName"     column="role_name"      />
   	<result property="roleKey"      column="role_key"       />
   	<result property="roleSort"     column="role_sort"      />
   	<result property="dataScope"    column="data_scope"     />
   	<result property="status"       column="role_status"    />
   </resultMap>

<insert id="insertSelective" parameterType="com.example.newbie.entity.AdminUser">
        insert into tb_newbee_mall_admin_user
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="adminUserId != null">
                admin_user_id,
            </if>
            <if test="loginUserName != null">
                login_user_name,
            </if>
            <if test="loginPassword != null">
                login_password,
            </if>
            <if test="nickName != null">
                nick_name,
            </if>
            <if test="locked != null">
                locked,
            </if>
        </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="adminUserId != null">
                #{adminUserId,jdbcType=BIGINT},
            </if>
            <if test="loginUserName != null">
                #{loginUserName,jdbcType=VARCHAR},
            </if>
            <if test="loginPassword != null">
                #{loginPassword,jdbcType=VARCHAR},
            </if>
            <if test="nickName != null">
                #{nickName,jdbcType=VARCHAR},
            </if>
            <if test="locked != null">
                #{locked,jdbcType=TINYINT},
            </if>
        </trim>
    </insert>

<insert id="insertUser" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" useGeneratedKeys="true" keyProperty="userId">
 		insert into sys_user(
 			<if test="userId != null and userId != 0">user_id,</if>
 			<if test="deptId != null and deptId != 0">dept_id,</if>
 			<if test="loginName != null and loginName != ''">login_name,</if>
 			<if test="userName != null and userName != ''">user_name,</if>
 			<if test="email != null and email != ''">email,</if>
 			<if test="avatar != null and avatar != ''">avatar,</if>
 			<if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
 			<if test="sex != null and sex != ''">sex,</if>
 			<if test="status != null and status != ''">status,</if>
 			<if test="createBy != null and createBy != ''">create_by,</if>
 			<if test="remark != null and remark != ''">remark,</if>
 			create_time
 		)values(
 			<if test="userId != null and userId != ''">#{userId},</if>
 			<if test="deptId != null and deptId != ''">#{deptId},</if>
 			<if test="loginName != null and loginName != ''">#{loginName},</if>
 			<if test="userName != null and userName != ''">#{userName},</if>
 			<if test="email != null and email != ''">#{email},</if>
 			<if test="avatar != null and avatar != ''">#{avatar},</if>
 			<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
 			<if test="sex != null and sex != ''">#{sex},</if>
 			<if test="status != null and status != ''">#{status},</if>
 			<if test="createBy != null and createBy != ''">#{createBy},</if>
 			<if test="remark != null and remark != ''">#{remark},</if>
 			sysdate()
 		)
	</insert>

<select id="findCarouselList" parameterType="Map" resultMap="BaseResultMap">
        select
        <include refid="Base_Column_List"/>
        from tb_newbee_mall_carousel
        where is_deleted = 0
        order by carousel_rank desc
        <if test="start!=null and limit!=null">
            limit #{start},#{limit}
        </if>
    </select>

<sql id="selectUserVo">
  <!-- 四张表联查 -->
        select  u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_time, u.remark,
       		    d.dept_id, d.parent_id, d.dept_name, d.order_num, d.leader, d.status as dept_status,
       		    r.role_id, r.role_name, r.role_key, r.role_sort, r.data_scope, r.status as role_status
		from sys_user u
			 left join sys_dept d on u.dept_id = d.dept_id
			 left join sys_user_role ur on u.user_id = ur.user_id
			 left join sys_role r on r.role_id = ur.role_id
    </sql>
  
<select id="selectUserList" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" resultMap="UserResult">
		select u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.remark, d.dept_name, d.leader from sys_user u
		left join sys_dept d on u.dept_id = d.dept_id
		where u.del_flag = '0'
		<if test="loginName != null and loginName != ''">
			AND u.login_name like concat('%', #{loginName}, '%')
		</if>
		<if test="status != null and status != ''">
			AND u.status = #{status}
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<if test="params.beginTime != null and params.beginTime != ''"><!-- 开始时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
		</if>
		<if test="params.endTime != null and params.endTime != ''"><!-- 结束时间检索 -->
			AND date_format(u.create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
		</if>
		<if test="deptId != null and deptId != 0">
			AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE FIND_IN_SET (#{deptId},ancestors) ))
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>
  

<select id="selectAllocatedList" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" resultMap="UserResult">
	    select distinct u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.status, u.create_time
	    from sys_user u
			 left join sys_dept d on u.dept_id = d.dept_id
			 left join sys_user_role ur on u.user_id = ur.user_id
			 left join sys_role r on r.role_id = ur.role_id
	    where u.del_flag = '0' and r.role_id = #{roleId}
	    <if test="loginName != null and loginName != ''">
			AND u.login_name like concat('%', #{loginName}, '%')
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>
 

<select id="selectUnallocatedList" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User" resultMap="UserResult">
	    select distinct u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.avatar, u.phonenumber, u.status, u.create_time
	    from sys_user u
			 left join sys_dept d on u.dept_id = d.dept_id
			 left join sys_user_role ur on u.user_id = ur.user_id
			 left join sys_role r on r.role_id = ur.role_id
	    where u.del_flag = '0' and (r.role_id != #{roleId} or r.role_id IS NULL)
	    and u.user_id not in (select u.user_id from sys_user u inner join sys_user_role ur on u.user_id = ur.user_id and ur.role_id = #{roleId})
	    <if test="loginName != null and loginName != ''">
			AND u.login_name like concat('%', #{loginName}, '%')
		</if>
		<if test="phonenumber != null and phonenumber != ''">
			AND u.phonenumber like concat('%', #{phonenumber}, '%')
		</if>
		<!-- 数据范围过滤 -->
		${params.dataScope}
	</select>
	
	<select id="selectUserByLoginName" parameterType="String" resultMap="UserResult">
	    <include refid="selectUserVo"/>
		where u.login_name = #{userName}
	</select>
 

	<select id="selectUserByPhoneNumber" parameterType="String" resultMap="UserResult">
		<include refid="selectUserVo"/>
		where u.phonenumber = #{phonenumber}
	</select>
	
	<select id="selectUserByEmail" parameterType="String" resultMap="UserResult">
	    <include refid="selectUserVo"/>
		where u.email = #{email}
	</select>
	
	<select id="checkLoginNameUnique" parameterType="String" resultType="int">
		select count(1) from sys_user where login_name=#{loginName}
	</select>
	
	<select id="checkPhoneUnique" parameterType="String" resultMap="UserResult">
		select user_id, phonenumber from sys_user where phonenumber=#{phonenumber}
	</select>
	
	<select id="checkEmailUnique" parameterType="String" resultMap="UserResult">
		select user_id, email from sys_user where email=#{email}
	</select>
	
	<select id="selectUserById" parameterType="Long" resultMap="UserResult">
		<include refid="selectUserVo"/>
		where u.user_id = #{userId}
	</select>

<delete id="deleteUserById" parameterType="Long">
  	delete from sys_user where user_id = #{userId}
  </delete>
  
  <delete id="deleteUserByIds" parameterType="Long">
  	update sys_user set del_flag = '2' where user_id in
  	<foreach collection="array" item="userId" open="(" separator="," close=")">
  		#{userId}
      </foreach> 
  </delete>




<update id="updateUser" parameterType="tech.pdai.mybatis.xml.web.system.user.domain.User">
  	update sys_user
  	<set>
  		<if test="deptId != null and deptId != 0">dept_id = #{deptId},</if>
  		<if test="loginName != null and loginName != ''">login_name = #{loginName},</if>
  		<if test="userName != null and userName != ''">user_name = #{userName},</if>
  		<if test="email != null and email != ''">email = #{email},</if>
  		<if test="phonenumber != null and phonenumber != ''">phonenumber = #{phonenumber},</if>
  		<if test="sex != null and sex != ''">sex = #{sex},</if>
  		<if test="avatar != null and avatar != ''">avatar = #{avatar},</if>
  		<if test="status != null and status != ''">status = #{status},</if>
  		<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
  		<if test="remark != null">remark = #{remark},</if>
  		update_time = sysdate()
  	</set>
  	where user_id = #{userId}
  </update>
<update id="updateByPrimaryKeySelective" parameterType="com.example.newbie.entity.AdminUser">
        update tb_newbee_mall_admin_user
        <set>
            <if test="loginUserName != null">
                login_user_name = #{loginUserName,jdbcType=VARCHAR},
            </if>
            <if test="loginPassword != null">
                login_password = #{loginPassword,jdbcType=VARCHAR},
            </if>
            <if test="nickName != null">
                nick_name = #{nickName,jdbcType=VARCHAR},
            </if>
            <if test="locked != null">
                locked = #{locked,jdbcType=TINYINT},
            </if>
        </set>
        where admin_user_id = #{adminUserId,jdbcType=BIGINT}
    </update>
 <update id="deleteBatch">
        update tb_newbee_mall_carousel
        set is_deleted=1,update_time=now() where carousel_id in
        <foreach item="id" collection="array" open="(" separator="," close=")">
            #{id}
        </foreach>
    </update>

注意

User和Role之间是一对多关系,所以是独立的表,在操作用户时需要同步删除User和Role的关联表UserRole

/**
* 通过用户ID删除用户
*
* @param userId 用户ID
* @return 结果
*/
@Override
public int deleteUserById(Long userId) {
  // 删除用户与角色关联
  userRoleMapper.deleteUserRoleByUserId(userId);
  return userMapper.deleteUserById(userId);
}

代码生成

Mybatis-Plus

之前的是最优雅的实现么

第一,从代码风格来看,源作者应该是吸收了早先SSM传统CRM后台框架的经验演进过来的,这个后面我会有两篇文章来帮你构建十年前项目是如何开发的认知;因为不是从头写的(考虑到代价, 沿用了原有的一些配置和Util等),所以必然不会是最新最优雅的方式,后续几点具体说明。

第二,过多的自定义的Util,而现有的项目项目中,Util一定不要自己造轮子,原则是优先使用Guava,commons等开源工具类;还有一些新的hutool等工具库是可以满足绝大部分需求的,如果没有再添加。于此同时,如果项目规模稍微大一点和要求,还可以使用外观模式进行小量的封装,比如你同时使用了Guava,Common库中StringUtils,那么可以增加一个MyStringUtils,将常用方法融入这个类,对项目中所有类来说只使用MyStringUtils。此外这里的例子还有一些标注为@Component等模块作为util,造成了代码的耦合度增加。

第三,没有完全注解化,还有一部分是xml;使用约定大于配置一定是最优雅的必要条件,因为大部分starters是支持的,比如这里的mybatis-springboot-starter中已经提供了mybatis的配置,无需再使用xml方式的配置了。

第四,Mapper和Service层抽象的缺失

第五,更为优雅的Lamada语法使用,比如MyBatis-Plus WrapperQuery的lambda查询方式。

/**
* 名字为王姓并且(年龄小于40或者邮箱不为空)
* sql:name like '王%' and (age < 40 or email is not null)
*/
@Test
public void selectLambda2() {
  LambdaQueryWrapper<User> lambdaQuery = Wrappers.<User>lambdaQuery();
  lambdaQuery.likeLeft(User::getName, "王")
          .lt(User::getAge, 40)
          .isNotNull(User::getEmail);
  List<Object> userList = userMapper.selectObjs(lambdaQuery);
  userList.forEach(System.out::println);
}

@Test
public void selectLambda3() {
  List<User> userList = new LambdaQueryChainWrapper<User>(userMapper)
          .like(User::getName, "雨")
          .ge(User::getAge, 20)
          .list();
  userList.forEach(System.out::println);
}

可以看下MyBatis-Plus官方测试用例 WrapperTest.java

第六,删除Fastjson等漏洞较多的使用库,改用Jackson;

解题思路

思维

1.顺序思维

复杂时, 也可以反过来先写出子查询, 用辅助表辅助做题.

先select出所有所求的结果, 再join所需的条件

2.链条思维

各种条件都存在顺序关系, 需要理顺它们, 然后采取从里往外或者从外往内的方式去解题.

在考虑当前条件a时, 还必须考虑条件a的前提条件b,包括需要排除的条件.

3. 行转列思维

SQL中的数据统统都是通过select在列中取的,所以如果发现需求的字段并非原表中的列, 则果断考虑使用条件语句进行行转列.

4. 思考特殊情况

易错点

窗口函数作为条件必须放在外层查询中, 而不是当前查询的where语句中.

模型

查询

一对一和一对多关系可以直接多表查, 如下所示, 但是多对多关系需要用连接查询.

select
  SCORE.SNO,
  CNO,
  DEGREE
from STUDENT, SCORE
where STUDENT.SNO = SCORE.SNO;

两表联查

left join/right join :
inner join:
group by

group by cola

可以查询到分组后的cola, 相关的聚合函数, 与cola具有一对一关系的列, 除此之外, 其他字段都查询不到.

case…when

类似Java中的if…else

select name,course,score,
    (case when score>=90 then '优秀'
    when  score>=80 then '良好'
    when score>=70 then '中等'
    when score>=60 then '及格'
    ELSE '不及格' end)level
    from score 

经常和聚合函数配合使用.

注意点:
case when 和sum()和count()使用时,用法不同(意义不同)

  1. sum()配合使用,满足条件返回1,表示进行+1 else 0表示+0
  2. count()配合使用,返回的值只要不是null,就进行一次计数,所以使用count()必须注意:若满足条件想进行一次计数,不满足条件不计数,应写成:
    count(case when ....then 1 else null end)count(case when ...then 1 end) 没有else,表示不满足条件,返回null
描述查询语句中各部分的执行顺序
  1. limit m,n 从索引m处开始, 筛选n条件数据–实现分页
  2. limit m, 从第一条数据开始, 筛选前m条
具体的执行顺序
  1. from join 产生虚拟表V1 —产生笛卡尔积—两表完全连接的结果叫做笛卡尔积, 会产生m*n条数据.
  2. on 根据连接条件进行筛选, 基于v1, 产生虚拟表v2
  3. left/right join 若是外连接,将主表中的所有数据进行补充到v2表中
  4. where 根据查询条件,从v2中进行数据筛选,产生虚拟表v3
  5. group by 对v3分组,产生虚拟表v4

select count(xx) ....group by bj_id

select count(xx) ... group by d.id

  1. having 对分组后的结果再次筛选 ,产生虚拟表v5
  2. select 选择最终要显示的字段,产生虚拟表v6
  3. distinct 对结果集进行去重,产生虚拟表v7
  4. order by 对结果根据某字段进行升序/降序排列
  5. limit 选择需要的数据

alt

步骤
  1. 判断主表: A的B, C, D; 则A为主表.
  2. 判断on筛选条件: 找到共同的字段.
  3. 判断查询方式: 内连接—一一对应/外连接—需要读取别的表信息, 关键字“所有”.
  4. 判断分组条件: 找出分组依据–需要聚合函数的表的id.
  5. 如果嵌套查询(有更多限制信息),则写出相应sql语句, 利用having进行再次筛选.
  6. 表中唯一存在不需要别名.

三表联查

多表联查,既有内联又有外联的语法:

select...from A join B on A.xx=B.xx left join C on A.xx=C.xx, 内联之后结果集和C外连接

不要写成:

select..from A join B left join C on A.xx=B.xx and A.xx=C.xx

若三表全都内联语法:

select..from A join B join C on A.xx=B.xx and A.xx=C.xx

不要写成:

select..from A join B on A.XX=B.xx join C on A.xx=c.XX

语法通过,结果也是正确的

步骤
  1. 题目特征: 查询信息横跨多个表
  2. 判断主表: A的B, C, D; 则A为主表.
  3. 判断on筛选条件: 找到共同字段
  4. 判断连接方式: 既有内连接又有外连接–存在为空的情况, 存在为空表和主表外连接/全部内连接.
  5. 判断分组条件: 找出分组依据–需要聚合函数的表的id.
  6. 如果嵌套查询(有更多限制信息),则写出相应sql语句, 利用having进行再次筛选.
  7. 表中唯一存在不需要别名.