【Mysql進階技巧(1)】 MySQL的多表關聯與自鏈接

自鏈接

測試數據準備

CREATE TABLE `t2` (
  `id` int(11) NOT NULL,
  `gid` char(1) DEFAULT NULL,
  `col1` int(11) DEFAULT NULL,
  `col2` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

insert into t2 values (1,'A',31,6), (2,'B',25,83), (3,'C',76,21), (4,'D',63,56), (5,'E',3,17), (6,'A',29,97), (7,'B',88,63), (8,'C',16,22), (9,'D',25,43), (10,'E',45,28), (11,'A',2,78), (12,'B',30,79), (13,'C',96,73), (14,'D',37,40), (15,'E',14,86), (16,'A',32,67), (17,'B',84,38), (18,'C',27,9), (19,'D',31,21), (20,'E',80,63), (21,'A',89,9), (22,'B',15,22), (23,'C',46,84), (24,'D',54,79), (25,'E',85,64), (26,'A',87,13), (27,'B',40,45), (28,'C',34,90), (29,'D',63,8), (30,'E',66,40), (31,'A',83,49), (32,'B',4,90), (33,'C',81,7), (34,'D',11,12), (35,'E',85,10), (36,'A',39,75), (37,'B',22,39), (38,'C',76,67), (39,'D',20,11), (40,'E',81,36);

經過自鏈接查詢每組col2最大的值;

-- 方法1:
select * from t2 as a where not exists (select 1 from t2 where gid=a.gid and col2>a.col2); 
-- 1. select 1 from t2 where gid=a.gid and col2>a.col2 : select就進入了隱式迭代,同組中比當前col2大的就輸出1;
-- 2. 而後not exists來判斷是否存在比當前col2大的,若是不存在就返回true;返回true就輸出當前col2這一列;
-- 3. 這裏的exists與not exists是判斷語句,返回的是true or false;

-- 方法2:
select * from (select * from t2 order by gid,col2 desc) as t group by gid;
-- t2按照gid和col2來降序排列,而後group分組,分組就取的是frist row,而frist row就是最大的值;
-- 乍看之下貌似不用自鏈接也能夠搞定,可是group by分組是不能放在order by以後的,不然就會報錯;

經過自鏈接查詢每組col2最大的三個值;

select * from t2 as a where 3 > (select count(*) from t2 where gid=a.gid and col2>a.col2) order by a.gid,a.col2 desc; 
-- 比當前col2大的值若是小於三條就輸出(注意必須是小於三條,若是等於三條就表明已經有了三條),而後輸出後排序;
上面兩條自鏈接sql都比較難理解,但只要換個角度,其實理解起來也很容易,首先在mysql中要把select翻譯爲輸出,而且要知足where之後才輸出;輸出之後再分組,分組之後才輪到排序,排序以後才輪到取幾個

JOIN

[inner] join,left join,right join;

經過join,mysql能夠作到集合中的求交集,並集,差集等需求,但比起相似redis等集合來講,效率差了不止一個級別了;

數據準備mysql

-- 建立數據表
CREATE TABLE IF NOT EXISTS tdb_goods(
  goods_id    SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
  goods_name  VARCHAR(150) NOT NULL,
  goods_cate  VARCHAR(40)  NOT NULL,
  brand_name  VARCHAR(40)  NOT NULL,
  goods_price DECIMAL(15,3) UNSIGNED NOT NULL DEFAULT 0,
  is_show     BOOLEAN NOT NULL DEFAULT 1,
  is_saleoff  BOOLEAN NOT NULL DEFAULT 0
)ENGINE=MyISAM AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='測試商品表';

-- 寫入記錄
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('R510VC 15.6英寸筆記本','筆記本','華碩','3399',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('Y400N 14.0英寸筆記本電腦','筆記本','聯想','4899',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('G150TH 15.6英寸遊戲本','遊戲本','雷神','8499',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('X550CC 15.6英寸筆記本','筆記本','華碩','2799',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('X240(20ALA0EYCD) 12.5英寸超極本','超級本','聯想','4999',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('U330P 13.3英寸超極本','超級本','聯想','4299',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('SVP13226SCB 13.3英寸觸控超極本','超級本','索尼','7999',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('iPad mini MD531CH/A 7.9英寸平板電腦','平板電腦','蘋果','1998',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('iPad Air MD788CH/A 9.7英寸平板電腦 (16G WiFi版)','平板電腦','蘋果','3388',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES(' iPad mini ME279CH/A 配備 Retina 顯示屏 7.9英寸平板電腦 (16G WiFi版)','平板電腦','蘋果','2788',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('IdeaCentre C340 20英寸一體電腦 ','臺式機','聯想','3499',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('Vostro 3800-R1206 臺式電腦','臺式機','戴爾','2899',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('iMac ME086CH/A 21.5英寸一體電腦','臺式機','蘋果','9188',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('AT7-7414LP 臺式電腦 (i5-3450四核 4G 500G 2G獨顯 DVD 鍵鼠 Linux )','臺式機','宏碁','3699',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('Z220SFF F4F06PA工做站','服務器/工做站','惠普','4288',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('PowerEdge T110 II服務器','服務器/工做站','戴爾','5388',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('Mac Pro MD878CH/A 專業級臺式電腦','服務器/工做站','蘋果','28888',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES(' HMZ-T3W 頭戴顯示設備','筆記本配件','索尼','6999',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('商務雙肩揹包','筆記本配件','索尼','99',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('X3250 M4機架式服務器 2583i14','服務器/工做站','IBM','6888',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('玄龍精英版 筆記本散熱器','筆記本配件','九州風神','',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES(' HMZ-T3W 頭戴顯示設備','筆記本配件','索尼','6999',DEFAULT,DEFAULT);
INSERT tdb_goods (goods_name,goods_cate,brand_name,goods_price,is_show,is_saleoff) VALUES('商務雙肩揹包','筆記本配件','索尼','99',DEFAULT,DEFAULT);

語法

table1
{[INNER|CROSS]JOIN|{LEFT|RIGHT}[OUTER]JOIN}
table2
ON conditional_expr
...
{[INNER|CROSS]JOIN|{LEFT|RIGHT}[OUTER]JOIN}
tablen
ON conditional_expr

JOIN 按照功能大體分爲以下三類:web

  • INNER JOIN(內鏈接,或等值鏈接,):取得兩個表中存在鏈接匹配關係的記錄。
  • LEFT JOIN(左鏈接):取得左表(table1)徹底記錄,而後再去匹配(table2),若是匹配不到以NULL的形式返回table2的字段值。
  • RIGHT JOIN(右鏈接):與 LEFT JOIN 相反。

關聯更新

-- ↓↓把單表更新的table換成了關聯在一塊兒的table
UPDATE tdb_goods AS g
JOIN tdb_goods_brands AS b ON g.brand_name = b.brand_name
JOIN tdb_goods_cates AS c ON g.goods_cate = c.cate_name
SET g.brand_name = b.brand_id,
 g.goods_cate = c.cate_id;

-- ↓↓因爲把原來的品牌名和分類名更換爲了id,因此相應的字段名稱和類型要有所改變
ALTER TABLE tdb_goods 
CHANGE brand_name brand_id TINYINT NOT NULL DEFAULT 0,
CHANGE goods_cate cate_id TINYINT NOT NULL DEFAULT 0;

DESC tdb_goods;

關聯查詢

-- 內鏈接
SELECT * FROM tdb_goods AS g 
JOIN tdb_goods_cates AS c ON g.cate_id = c.cate_id
JOIN tdb_goods_brands AS b ON g.brand_id = b.brand_id;

-- 左鏈接
SELECT * FROM tdb_goods AS g 
LEFT JOIN tdb_goods_cates AS c ON g.cate_id = c.cate_id
LEFT JOIN tdb_goods_brands AS b ON g.brand_id = b.brand_id;

關聯刪除

-- 查找出重複記錄;
SELECT goods_id,goods_name FROM tdb_goods GROUP BY goods_name HAVING count(goods_name) >= 2;

-- 進行刪除
DELETE t1 FROM tdb_goods AS t1 JOIN (SELECT goods_id,goods_name FROM tdb_goods GROUP BY goods_name HAVING count(goods_name) >= 2 ) AS t2  ON t1.goods_name = t2.goods_name  WHERE t1.goods_id > t2.goods_id;
-- 注意,單表的刪除在delete後面不用加上表名,但多表必定要加,不然會報語法錯誤;
-- 如何理解這條sql語句? 首先把t1和t2關聯一塊兒;而後把t1全刪除;也就是說把符合另一張表關聯條件的本表給刪掉;

Cross join [笛卡爾]

交叉鏈接,獲得的結果是兩個表的乘積redis

select * from tdb_goods,tdb_goods_brands; -- tdb_goods表23條數據,tdb_goods_brands有10條數據,笛卡爾之後就出現23*10條數據;

select * from tdb_goods as g join tdb_goods_brands as b;  -- join在沒有on條件的時候也是笛卡爾乘積;

Full join [union]

mysql裏面沒有Full join,只有union;若是要使用union的話,被union的表的結構要同樣才能並在一塊兒;sql

-- 先查出1101的評論,再查出1101所關注的人的評論;
SELECT * FROM `comment` WHERE user_id = 1101 UNION SELECT * FROM `comment` WHERE user_id IN (select follow_user_id from follow where user_id = 1101);

關於join的外鍵約束

以往作PHP+MYSQL的web應用時,從未用過外鍵,但即便如此,外鍵仍是頗有必要的,它能是多表之間的關聯更嚴格,可以達到一致性的需求;
若是不用外鍵約束的話,在多表關聯的應用場景中,咱們插入一條數據,該條數據只要知足語法規範既可插入,但若是使用了外鍵,該語句還要同時知足當前外鍵在關聯的表中是否存在;服務器

使用外鍵要知足如下條件:測試

  1. 兩張表必須都是InnoDB表,而且它們沒有臨時表。
  2. 外鍵列和參照列必須具備類似的數據類型.其中數字的長度及是否有符號位必須相同;而字符的長度則能夠不一樣
  3. 外鍵列和參照列必須建立索引.若是外鍵列不存在索引的話,mysql將自動建立索引

語法翻譯

[CONSTRAINT [symbol]] FOREIGN KEY
  [index_name] (index_col_name, ...)
  REFERENCES tbl_name (index_col_name,...)
  [ON DELETE reference_option]
  [ON UPDATE reference_option]
  reference_option:
      RESTRICT | CASCADE | SET NULL | NO ACTION

若是子表試圖建立一個在父表中不存在的外鍵值,InnoDB會拒絕任何INSERT或UPDATE操做。若是父表試圖UPDATE或者DELETE任何子表中存在或匹配的外鍵值,最終動做取決於外鍵約束定義中的ON UPDATE和ON DELETE選項。InnoDB支持5種不一樣的動做,若是沒有指定ON DELETE或者ON UPDATE,默認的動做爲RESTRICT:設計

  1. CASCADE: 從父表中刪除或更新對應的行,同時自動的刪除或更新子表中匹配的行。ON DELETE CANSCADE和ON UPDATE CANSCADE都被InnoDB所支持。
  2. SET NULL: 從父表中刪除或更新對應的行,同時將子表中的外鍵列設爲空。注意,這些在外鍵列沒有被設爲NOT NULL時纔有效。ON DELETE SET NULL和ON UPDATE SET NULL都被InnoDB所支持。
  3. NO ACTION: InnoDB拒絕刪除或者更新父表。
  4. RESTRICT: 拒絕刪除或者更新父表。指定RESTRICT(或者NO ACTION)和忽略ON DELETE或者ON UPDATE選項的效果是同樣的。
  5. SET DEFAULT: InnoDB目前不支持。
上文中的父表是指被參照的表;

外鍵約束使用最多的狀況無外乎:code

  1. 父表更新時子表也更新,父表刪除時若是子表有匹配的項,刪除失敗;(在外鍵定義中,咱們使用ON UPDATE CASCADE ON DELETE RESTRICT)
  2. 父表更新時子表也更新,父表刪除時子表匹配的項也刪除。(使用ON UPDATE CASCADE ON DELETE CASCADE)

InnoDB容許你使用ALTER TABLE在一個已經存在的表上增長一個新的外鍵:排序

ALTER TABLE tbl_name
    ADD [CONSTRAINT [symbol]] FOREIGN KEY
    [index_name] (index_col_name, ...)
    REFERENCES tbl_name (index_col_name,...)
    [ON DELETE reference_option]
    [ON UPDATE reference_option]

也支持

ALTER TABLE tbl_name DROP FOREIGN KEY fk_symbol;

Example

CREATE TABLE `test1` (
  `goods_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `goods_name` varchar(150) NOT NULL,
  `brand_id` smallint(5) unsigned NOT NULL DEFAULT '0',
  `goods_price` decimal(15,3) unsigned NOT NULL DEFAULT '0.000',
  PRIMARY KEY (`goods_id`),
  KEY `brand_id` (`brand_id`),
  FOREIGN KEY (`brand_id`) REFERENCES `test2` (`brand_id`)
-- test1的brand_id必須和test2同樣,包括類型,長度,是否有符號,才能建立外鍵;
-- 建立完以後FOREIGN KEY...這sql就會自動變爲:CONSTRAINT `test1_ibfk_1` FOREIGN KEY (`brand_id`) REFERENCES `test2` (`brand_id`)
-- 若是不加任何ON DELETE reference_option語句的話,默認就是NO ACTION,也就是說,父表test2更新和刪除,只准增長;
-- 若是加上ON DELETE CANSCADE和ON UPDATE CANSCADE的話,test2刪除和更新都會影響到test1;
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='索引測試test1';

CREATE TABLE `test2` (
  `brand_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
  `brand_name` varchar(40) NOT NULL,
  PRIMARY KEY (`brand_id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='索引測試test2';
mysql> insert into test1(goods_name,brand_id) values('i phone 1',6);
1452 - Cannot add or update a child row: a foreign key constraint fails (`test`.`test1`, CONSTRAINT `test1_ibfk_1` FOREIGN KEY (`brand_id`) REFERENCES `test2` (`brand_id`))
-- 沒有參照不能插入

mysql> delete from test2 where brand_id=1;
1451 - Cannot delete or update a parent row: a foreign key constraint fails (`test`.`test1`, CONSTRAINT `test1_ibfk_1` FOREIGN KEY (`brand_id`) REFERENCES `test2` (`brand_id`))
-- NO ACTION,凡是test1已經關聯上的test2的row都不能被刪除;保持數據的一致性;沒關聯上能夠被刪除,雖然是no action;

自鏈接+join 實現一級分類

表的設計是無限極的設計方式,但mysql搞不了遞歸,只能實現一級;

數據準備

CREATE TABLE tdb_goods_types(
    type_id   SMALLINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    type_name VARCHAR(20) NOT NULL,
    parent_id SMALLINT UNSIGNED NOT NULL DEFAULT 0
 )ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='無限極分類表';; 

 INSERT tdb_goods_types(type_name,parent_id) VALUES('家用電器',DEFAULT);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('電腦、辦公',DEFAULT);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('你們電',1);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('生活電器',1);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('平板電視',3);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('空調',3);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('電風扇',4);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('飲水機',4);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('電腦整機',2);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('電腦配件',2);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('筆記本',9);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('超級本',9);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('遊戲本',9);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('CPU',10);
 INSERT tdb_goods_types(type_name,parent_id) VALUES('主機',10);

實現分類

SELECT
  t1.type_id,
  t1.type_name AS parent_name,
  t2.type_name,
  t2.parent_id
FROM
  `tdb_goods_types` AS t1
JOIN tdb_goods_types AS t2 ON t1.type_id = t2.parent_id;
相關文章
相關標籤/搜索