MySQL技巧:無限級自定義分類

本文案例以MySQL5.7作為數(shù)據(jù)庫環(huán)境。

開發(fā)過程中難免會遇到一些奇葩的卻又不得不實現(xiàn)的需求刚操。

比如在我們的業(yè)務(wù)系統(tǒng)中用戶需要自己設(shè)置分類勇哗,并為商品設(shè)置分類,且滿足以下需求:

  • 分類可以是任意層次級別的
  • 商品可以將層級中的任一級作為分類(如某商品分類為B01月褥,B01分類層次為A01>B01>C01>D01...)
  • 在應(yīng)用程序中,需要顯示商品的完整分類層次(如分類為B01瓢喉,需要顯示A01>B01宁赤,而不是只顯示B01)
  • 在應(yīng)用程序中,能夠根據(jù)分類的上級分類來查詢栓票,不僅僅按此分類查詢(比如分類為C01决左,層次為A01>B01>C01,使用A01走贪、B01佛猛、C01均要求能夠查到此分類C01)

仔細考慮下種種要求,還是稍微有點復(fù)雜的坠狡,設(shè)計不同最終的性能也會有差異挚躯,而且差異還不小。

如下表所示擦秽,每個商品的分類層次可以不一致码荔,層次也沒有上限漩勤。

商品 分類
商品1 A01>B01>C02
商品2 A01
商品3 A02>B02>C01>D03
商品4 A03>B03

此時,這種需求肯定不能用省>市>區(qū)(縣)這種固定級別的表的數(shù)據(jù)結(jié)構(gòu)來設(shè)計缩搅,我們可以采用鏈表(可以理解為指針)的方式來設(shè)計表結(jié)構(gòu)越败,當(dāng)然省>市>區(qū)(縣)的需求也可以用這種鏈表方案。

# 自定義分類表
mysql> create table t_category(
    -> id bigint(20) primary key,
    -> name varchar(20) comment '分類名稱',
    -> upper_id bigint(20) comment '上級分類id'
    -> );
# 準(zhǔn)備分類數(shù)據(jù)
INSERT INTO `t_category` (`id`, `name`) VALUES ('10001', 'A01');
INSERT INTO `t_category` (`id`, `name`) VALUES ('10002', 'A02');
INSERT INTO `t_category` (`id`, `name`) VALUES ('10003', 'A03');
INSERT INTO `t_category` (`id`, `name`) VALUES ('20001', 'B01');
INSERT INTO `t_category` (`id`, `name`) VALUES ('20002', 'B02');
INSERT INTO `t_category` (`id`, `name`) VALUES ('20003', 'B03');
INSERT INTO `t_category` (`id`, `name`) VALUES ('30001', 'C01');
INSERT INTO `t_category` (`id`, `name`) VALUES ('30002', 'C02');
INSERT INTO `t_category` (`id`, `name`) VALUES ('30003', 'C03');
INSERT INTO `t_category` (`id`, `name`) VALUES ('40001', 'D01');
INSERT INTO `t_category` (`id`, `name`) VALUES ('40002', 'D02');
INSERT INTO `t_category` (`id`, `name`) VALUES ('40003', 'D03');
# 創(chuàng)建商品表
mysql> create table t_goods(
    -> id bigint(20) primary key,
    -> name varchar(50),
    -> goods_category varchar(1000)
    -> );
# 準(zhǔn)備商品數(shù)據(jù)(將分類層級保存到商品分類字段)
INSERT INTO `t_goods` (`id`, `name`, `goods_category`) VALUES ('1', '商品1', '10001/20001/30002');
INSERT INTO `t_goods` (`id`, `name`, `goods_category`) VALUES ('2', '商品2', '10001');
INSERT INTO `t_goods` (`id`, `name`, `goods_category`) VALUES ('3', '商品3', '10002/20002/30001/40003');
INSERT INTO `t_goods` (`id`, `name`, `goods_category`) VALUES ('4', '商品4', '10003/20003');

以上方案是我感覺比較好的設(shè)計硼瓣,避免了循環(huán)遞歸查詢(尤其要避免應(yīng)用程序與數(shù)據(jù)庫多次交互)究飞。

最后演示下將商品分類中的id通過sql轉(zhuǎn)成name,其它的沒難度不再演示了堂鲤,這個問題的關(guān)鍵在于數(shù)據(jù)結(jié)構(gòu)的設(shè)計和分類數(shù)據(jù)的保存亿傅。當(dāng)然,有些工作也可以放在應(yīng)用程序完成瘟栖。

# 準(zhǔn)備用于輔助的t_sequence
mysql> create table t_sequence(
    -> id bigint primary key auto_increment
    -> );
# 準(zhǔn)備1-10(如有需要可更多)
mysql> insert into t_sequence values (),(),(),(),(),(),(),(),(),();
# 利用cross join將分類信息轉(zhuǎn)成多行
mysql> select a.name,s.id as sequence,substring_index(substring_index(a.goods_category,'/',s.id),'/',-1) as category_id from t_sequence as s
    -> cross join (
    -> select `name`,goods_category,(length(goods_category)-length(replace(goods_category,'/','')) + 1) as len from t_goods
    -> ) as a
    -> on s.id <= a.len;
+---------+----------+-------------+
| name    | sequence | category_id |
+---------+----------+-------------+
| 商品1   |        1 | 10001       |
| 商品1   |        2 | 20001       |
| 商品1   |        3 | 30002       |
| 商品2   |        1 | 10001       |
| 商品3   |        1 | 10002       |
| 商品3   |        2 | 20002       |
| 商品3   |        3 | 30001       |
| 商品3   |        4 | 40003       |
| 商品4   |        1 | 10003       |
| 商品4   |        2 | 20003       |
+---------+----------+-------------+
# 進一步完善將分類id轉(zhuǎn)成name葵擎,這里又使用了一次join,可以看到離最終結(jié)果越來越近了
mysql> select b.name as goods,b.sequence,c.name as category from t_category as c
    -> join (
    -> select a.name,s.id as sequence,substring_index(substring_index(a.goods_category,'/',s.id),'/',-1) as category_id from t_sequence as s
    -> cross join (
    -> select `name`,goods_category,(length(goods_category)-length(replace(goods_category,'/','')) + 1) as len from t_goods
    -> ) as a on s.id <= a.len
    -> ) as b on b.category_id = c.id;
+---------+----------+----------+
| goods   | sequence | category |
+---------+----------+----------+
| 商品1   |        1 | A01      |
| 商品1   |        2 | B01      |
| 商品1   |        3 | C02      |
| 商品2   |        1 | A01      |
| 商品3   |        1 | A02      |
| 商品3   |        2 | B02      |
| 商品3   |        3 | C01      |
| 商品3   |        4 | D03      |
| 商品4   |        1 | A03      |
| 商品4   |        2 | B03      |
+---------+----------+----------+
# 最后剛好MySQL中有g(shù)roup_concat()這個函數(shù)可以解決我們的問題半哟,另外我們把上面的商品名稱換成id酬滤,因為名稱是可能重復(fù)的(上面的示例只是為了看得明顯)
mysql> select d.id,d.`name`,group_concat(category separator '>') as category from
    -> (
    -> select b.id,b.`name`,b.sequence,c.name as category from t_category as c
    -> join (
    -> select a.id,a.name,s.id as sequence,substring_index(substring_index(a.goods_category,'/',s.id),'/',-1) as category_id from t_sequence as s
    -> cross join (
    -> select id,`name`,goods_category,(length(goods_category)-length(replace(goods_category,'/','')) + 1) as len from t_goods
    -> ) as a on s.id <= a.len
    -> ) as b on b.category_id = c.id
    -> ) as d group by id,`name`;
+----+---------+-----------------+
| id | name    | category        |
+----+---------+-----------------+
|  1 | 商品1   | A01>B01>C02     |
|  2 | 商品2   | A01             |
|  3 | 商品3   | A02>B02>C01>D03 |
|  4 | 商品4   | A03>B03         |
+----+---------+-----------------+

大功告成,再理一下思路
Step 1:cross join連接t_sequence與t_goods將分類層級轉(zhuǎn)成多行寓涨,并用substring_index()函數(shù)拆分各層級分類
Step 2:inner join連接第一步的結(jié)果與t_category
Step 3:利用group_concat函數(shù)將多行轉(zhuǎn)一行

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盯串,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子戒良,更是在濱河造成了極大的恐慌体捏,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糯崎,死亡現(xiàn)場離奇詭異译打,居然都是意外死亡,警方通過查閱死者的電腦和手機拇颅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乔询,“玉大人樟插,你說我怎么就攤上這事「偷螅” “怎么了黄锤?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長食拜。 經(jīng)常有香客問我鸵熟,道長,這世上最難降的妖魔是什么负甸? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任流强,我火速辦了婚禮痹届,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘打月。我一直安慰自己队腐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布奏篙。 她就那樣靜靜地躺著柴淘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秘通。 梳的紋絲不亂的頭發(fā)上为严,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天,我揣著相機與錄音肺稀,去河邊找鬼第股。 笑死,一個胖子當(dāng)著我的面吹牛盹靴,可吹牛的內(nèi)容都是我干的炸茧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼稿静,長吁一口氣:“原來是場噩夢啊……” “哼梭冠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起改备,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤控漠,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后悬钳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盐捷,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年默勾,在試婚紗的時候發(fā)現(xiàn)自己被綠了碉渡。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡母剥,死狀恐怖滞诺,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情环疼,我是刑警寧澤习霹,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站炫隶,受9級特大地震影響淋叶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜伪阶,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一煞檩、第九天 我趴在偏房一處隱蔽的房頂上張望处嫌。 院中可真熱鬧,春花似錦形娇、人聲如沸锰霜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽癣缅。三九已至,卻和暖如春哄酝,著一層夾襖步出監(jiān)牢的瞬間友存,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工陶衅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屡立,地道東北人。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓搀军,卻偏偏與公主長得像膨俐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子罩句,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容