一個多表聯合查詢引起的思考

朋友作一個項目,遇到一個多表聯合查詢的需求。mysql

  • A表SYSTEM_ID和B表SYSTEM_ID關聯;
  • C表中ROLE_LIST字段,存儲多個B表中的ROLE_ID值;

須要一個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

  • A表是權限(資源)表;
  • B表是角色權限(資源)表;
  • C表用戶表;

通常使用單獨的表來存儲用戶和角色的關聯信息,這裏很巧妙的採用以逗號分隔的方式存儲多個角色編號。採用這種方式能夠減小錶鏈接,但處理存儲和查詢的複雜度更高。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;
相關文章
相關標籤/搜索