朋友作一个项目,遇到一个多表联合查询的需求。mysql
须要一个sql,当A表中SYSTEM_ID值为123时,找到B表和C表的关联,当B表知足SYSTEM_ID值为123时,包含其中的ROLE_ID数据,显示C表中NAME数据。例如查询结果为:james、lucy.sql
T_TABLE_A表函数
ID | SYSTEM_ID |
---|---|
1 | 123 |
2 | 234 |
T_TABLE_B表code
ID | SYSTEM_ID | ROLE_ID |
---|---|---|
1 | 222 | 667 |
2 | 123 |
555 |
3 | 123 |
777 |
4 | 234 | 567 |
5 | 234 | 231 |
T_TABLE_C表资源
ID | NAME | ROLE_LIST |
---|---|---|
1 | james |
667,777 |
2 | lucy |
223,555 ,823 |
3 | tom | 253,231 |
4 | max | 123,712 |
5 | min | 123,567 |
最终提供的sql以下:字符串
select name from t_table_c c where exists ( select 1 from t_table_a a inner join t_table_b b on a.system_id = b.system_id where a.system_id = 123 and c.role_list like '%' || b.role_id || '%' )
上面的查询sql采用的exists子句的方式,采用链接的方式也能完成相同的功能,具体实现见文末附录Sql.table
从给的脱敏数据能够推测出各个表的功能。class
通常使用单独的表来存储用户和角色的关联信息,这里很巧妙的采用以逗号分隔的方式存储多个角色编号。采用这种方式能够减小表链接,但处理存储和查询的复杂度更高。select
单纯从数据上来看,因为采用的模糊查询,有可能出现错误的查询结果。权限
假设C表中存在如下数据。
ID | NAME | ROLE_LIST |
---|---|---|
6 | kim | 1777 ,2211 |
7 | kenv | 220,1555 ,800 |
从新执行sql,你会发现查询结果里面包含:kim和kenv. 形成这个结果的缘由是,模糊查询能够匹配ROLE_LIST列中部分数据,1777和1555分别包含777和555.
经过这个简单示例,能够发现存储多个值时确实包含一些局限性。咱们既想一列存储多个数值,又想消除歧义,那么怎么解决这个问题呢?
给存储的值添加一个前缀,也许是一个好办法,好比:r555,r777,r1777,r1555,前缀采用表明特定含义的单词或字母,加上实际的数值,能够构造出一个特殊的字符串。条件子语句:like '%r555'. 能够避免示例数据中的错误查询结果问题。
等等,好像仍是有点问题。若是字符串为:r55,r555,r1555,何解。
固然若是存在这种状况,咱们还有一个终极解决办法(适用于任何状况):前缀+值+后缀
前缀+值+后缀 列存储示例:[55],[555],[1555] 条件子语句:like '%[55]%',这里前缀='['、值='55'、后缀=']'
通常状况下,使用特定单词或字母做为特殊前缀加取值能够满级绝大多数状况,这种方式可读信更高。而须要严格意义上的避免误查询,能够采用:前缀+值+后缀。
--附录Sql create table t_table_a ( id int primary key, system_id int ); create table t_table_b ( id int primary key, system_id int, role_id int ); create table t_table_c ( id int primary key, name varchar2(20), role_list varchar2(200) ); insert into t_table_a values(1, 123); insert into t_table_a values(2, 234); insert into t_table_a values(3, 100); insert into t_table_b values(1, 222, 667); insert into t_table_b values(2, 123, 555); insert into t_table_b values(3, 123, 777); insert into t_table_b values(4, 234, 567); insert into t_table_b values(5, 234, 231); insert into t_table_c values(1, 'james', '667,777'); insert into t_table_c values(2, 'lucy', '223,555,823'); insert into t_table_c values(3, 'tom', '253,231'); insert into t_table_c values(4, 'max', '123,712'); insert into t_table_c values(5, 'min', '123,567'); insert into t_table_c values(6, 'kim', '1777,2211'); insert into t_table_c values(7, 'kenv', '220,1555,800'); select name from t_table_c c where exists ( select 1 from t_table_a a inner join t_table_b b on a.system_id = b.system_id where a.system_id = 123 and c.role_list like '%' || b.role_id || '%' ); select c.name, b.id from t_table_a a inner join t_table_b b on a.system_id = b.system_id inner join t_table_c c on c.role_list like '%' || b.role_id || '%' where a.system_id = 123;
On more thing.
这sql在Oracle中跑起来,没有什么问题。可是朋友拿去改造后在mysql中运行,出现的查询结果不正常。排查了很长一段时间,才找到缘由:在mysql中 '%' || 'S001' || '%' 显示打印为:1. 正确的方式是使用concat函数。因此若是你在mysql中运行,须要这样构造sql.
select c.name, b.id from t_table_a a inner join t_table_b b on a.system_id = b.system_id inner join t_table_c c on c.role_list like concat('%', b.role_id, '%') where a.system_id = 123;