mysql在子查询中使用自定义变量和条件语句实现函数效果的查询语句

mysql在子查询中使用自定义变量和条件语句实现函数效果的查询语句

    • 前言
    • 自定义变量和条件语句语法规则
      • 自定义变量规则
      • 条件语句
    • 例题简介
    • 思路解析
    • 方法1:同表多连接
    • 方法2:子查询(自定义变量)
      • 方法2详解
    • 总结

前言

mysql语法简单,但也正因如此,在检索稍微复杂的数据时,有时可能需要连续的子查询的嵌套以及表连接的组合应用,其逻辑上的循环嵌套远不如通过中间变量来存储易于理解。但一般情况下,我们倾向于通过一个分号来输出一次查询结果,而不是保存为视图进行二次查询。这样的情况下,对于不是特别复杂的检索逻辑我们可以尽量通过自定义变量来存储值,达到编程语言中的函数效果,而不是完全依靠where子句进行一箩筐的添加条件让机器思考,这样既易于编写又提升了速度。

自定义变量和条件语句语法规则

自定义变量规则

@变量名:=固定值 | 函数调用 | 条件语句		别名

mysql> SELECT @min_price:=MIN(price),@max_price:=MAX(price) FROM shop;		# 初始化变量

mysql> SELECT * FROM shop WHERE price=@min_price OR price=@max_price;	# 引用变量

条件语句

在非funtionprocedure的一般查询语法中,sql中的条件语句和高级编程语言中的条件语句用法上有所不同,sql中的if更像一个函数,而不是代表一个语句,但也有和编程语言中相差不大的条件语句,比如case。简单介绍一下3种在一般条件语句中的用法,不做更深的理解。不深究的原因可以看下例题思路解析部分。

IF( expr1 , True_expr , False_expr )	#	expr1为真返回True_expr, 假则返回False_expr

IFNULL( expr1 , Null_expr )	#	expr1的值不为空仍返回该值, 值为空则返回Null_expr

case when 条件1 then 结果1 
		when 条件2 then 结果2 
		else 结果N 
end

例题简介

Leetcode 180:编写一个 SQL 查询,查找所有至少连续出现三次的数字。

+----+-----+
| Id | Num |
+----+-----+
| 1  |  1  |
| 2  |  1  |
| 3  |  1  |
| 4  |  2  |
| 5  |  1  |
| 6  |  2  |
| 7  |  2  |
+----+-----+

思路解析

写代码写多了,遇到这种情况程序思维就觉得很简单,if+count的组合应该很容易就解决了。但是在sql中,一方面我对if,ifnull, case when等条件语句和函数不太了解,在帮助文档以及mysql应知应会这种工具书中都没有怎么提及,只能在csdn上搜索相关文档,另一方面,这类题目的逻辑并非非常复杂,我个人是不想为这种程度的逻辑去使用functionprocedure
一般的sql解法可能会对同一张表多次连接后加上较复杂的where子句,但我是有些不太习惯这种思维模式的,感觉过于暴力;另外一种解法是程序思维,使用自定义变量和条件语句来达到函数的效果。因此,我会列出两种方法,但对第二种方法更有倾向性。

方法1:同表多连接

无论如何,用的最多的可能还是相同表连接的查询方式来表示检索条件。

mysql> select distinct L1.Num as ConsecutiveNums
    -> from Logs L1, Logs L2, Logs L3
    -> where L1.Id = L2.Id - 1		# 条件语句
    -> and L2.Id = L3.Id - 1
    -> and L1.Num = L2.Num
    -> and L2.Num = L3.Num;

+-----------------+
| ConsecutiveNums |
+-----------------+
|               1 |
+-----------------+
1 row in set (0.01 sec)

这里有至少有一处可以优化,就是在表连接出可以使用外连接减少检索的行数,速度的确快了,但思路上基本没有变化。

mysql> select distinct L1.Num as ConsecutiveNums
    -> from Logs L1 left join Logs L2 on L1.Num = L2.Num
    -> left join Logs L3 on L2.Num = L3.Num
    -> where L1.Id = L2.Id - 1
    -> and L2.Id = L3.Id - 1;
    
+-----------------+
| ConsecutiveNums |
+-----------------+
|               1 |
+-----------------+
1 row in set (0.00 sec)

方法2:子查询(自定义变量)

这种解法是我希望在sql中实现的,但在我原来的理解中,可能需要定义functionor procudure,不过因为逻辑上又没那么复杂不想麻烦所以一直觉得没必要。但这位老兄的解法给我提供了一个类函数的查询模板让我思考。
可能因为mysql的直接堆叠的查询逻辑更容易看懂,我看到Leetcode上数据库练习下的评论既少又表示对这种编程思维不太理解。其实包括我自己第一次看到的时候也有点懵。但看懂了以后会觉得比原来的方法更有趣也更快。
先把代码附上,接下来从子查询开始说明这种逻辑如何在sql中更好的实现。

SELECT DISTINCT a.Num ConsecutiveNums FROM (		# 主句
SELECT t.Num ,										# 子表1
       @cnt:=IF(@pre=t.Num, @cnt+1, 1) cnt,
       @pre:=t.Num pre
  FROM Logs t, (SELECT @pre:=null, @cnt:=0) b) a		# 子表2
  WHERE a.cnt >= 3

作者:hy3300
链接:https://leetcode-cn.com/problems/two-sum/solution/bu-shi-yong-idqing-kuang-xia-tong-ji-by-hy3300/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

方法2详解

子查询理解如果从主句开始,比较难用语言说明子查询返回的表的结构组成,所以我从子表2开始说明,并依次扩充直到主句

  1. 子表2
    select @pre:=null, @cnt:=0
    生成包含两个字段的表,但这两个字段本身没有意义,其主要作用是通过建立表来初始化两个自定义变量用于迭代,cnt变量用于存储当前Num出现的次数,pre用于存储当前的Num进行下一条数据比较。

  2. 子表1

    SELECT t.Num ,
       @cnt:=IF(@pre=t.Num, @cnt+1, 1) cnt,
       @pre:=t.Num pre
    	FROM Logs t, 子表2 b
    
    	+------+------+------+
    	| Num  | cnt  | pre  |
    	+------+------+------+
    	|    1 |    1 |    1 |
    	|    1 |    2 |    1 |
    	|    1 |    3 |    1 |
    	|    2 |    1 |    2 |
    	|    1 |    1 |    1 |
    	|    2 |    1 |    2 |
    	|    2 |    2 |    2 |
    	+------+------+------+
    
    

将子表2替换命名为b,该句可以理解为从表Logs t中选取t.Num, cnt, pre 三个字段,依次返回三个值:Num,Num出现次数,保存本条数据中的Num值。因为没有where子句的限制,select会对每行的Num检索一次,并计算出相对应的cnt和pre值。这种结构类似于编程语言中的while-if,每一行相当于一次循环,cnt和pre变量会在每次循环中随条件而变化,同时这里如果需要控制次数可以使用LIMIT语句。
可以看到,子表1的效果就是函数封装的效果,相当有趣。

  1.  SELECT DISTINCT a.Num ConsecutiveNums
     FROM 子表1 a
     WHERE a.cnt >= 3
    
    当我们看到子表1的结果表之后,再来看主句就相当容易理解了,无外乎是一个简单的查询语句。

总结

以往我使用子查询,通常会将子查询作为计算字段来使用,比如select c1, (select c2 from t2) as c2 from t1select * from t1 where t1.c1 = (select c2 from t2 where id=1),而在本例中,子查询起到了类似过滤的作用,即利用变量重新构建了一个新表,这种方法大大增加了子查询的易用性和可行性,未来在遇到比一般查询稍复杂一些,但逻辑上又不会过于繁杂的情况,可以尝试在子查询中使用自定义函数和条件语句,构建一个具有函数效果的sql查询语句。

你可能感兴趣的:(mysql在子查询中使用自定义变量和条件语句实现函数效果的查询语句)