1. 程式人生 > 資料庫 >MySQL核心知識概要(二)

MySQL核心知識概要(二)

DQL

select查詢

select column_name [[as] column_alias] from table_name [[as] table_alias];
select 常量...;
select 表示式;
select 函式;
#使用列別名時應使用as,否則可能會出現無法預料的情況:
select 'a' 'b';

條件查詢

select column_names from table_name where column_name operator value;
  • 算術運算子:+ - * / %
  • 比較運算子: =、 <> !=、 >、 <、 >=、 <=、 BETWEEN、 NOT BETWEEN、 IN、 NOT IN、 LIKE、 IS NULL、 IS NOT NULL
    字元比較時按照ASCII碼對應的值進行比較,比較時按照字元順序逐一比較
    BETWEEN AND會選取介於兩個值之間的資料範圍,可以是數值、文字或者日期,且是閉區間查詢;兩個臨界值不能調換位置,只能大於等於左邊小於等於右邊
    LIKE中%可以匹配一個到多個字元,_可以匹配任意一個字元
  • 邏輯查詢運算子: AND、 OR、 NOT
    IN列表的值型別必須一致或者相容,不支援萬用字元

關於NULL值的坑

  • 查詢運算子、LIKE、BETWEEN AND、IN、NOT IN對於NULL值查詢不起作用,無法查詢值為NULL的記錄
  • 查詢值為NULL的記錄需要使用IS NULL、IS NOT NULL
  • <=>(安全等於)既可以判斷NULL值又可以判斷普通數值,但可讀性較低不常用,不建議使用
  • 建立表時應儘量設定表字段不能為空,給欄位設定預設值

排序和分頁

select column_name from table_name order by column1 [asc | desc [, column2 [asc | desc]]];
#按照函式進行排序
select id 編號, birth 出生日期, year(birth) 出生年份, name 姓名 from student order by year(birth);

#offset表示偏移量也即跳過的行數,可以省略;count查詢的記錄數
select column_name from table_name limit [offset,] count;
#分頁查詢
select column_name from table_name limit (page-1)*pagesize, pagesize;
  • limit中不能使用表示式,兩個數值不能為負
  • 當排序使用的欄位出現相同的值時,如果沒有其他排序規則可能會出現相鄰的幾頁出現相同資料的情況應再指定一個排序規則(如主鍵排序)消除二義性

分組查詢

select column_name, 聚合函式,... from table_name [where...] group by... [having...];
#聚合函式:max、min、count、sum、avg

#where&group by & having & order by & limit
select column_name from table_name where... group by... having... order by... limite...;

分組中select後面的列只能是出現在group by後面的列或者使用聚合函式的列;sql_mode中包含了ONLY_FULL_GROUP_BY表示select後面的列必須符合前面兩點規範,刪除ONLY_FULL_GROUP_BY後select後面可以加任意列

where和having的區別

  • where在分組前對記錄進行篩選,而having是在分組結束後的結果中篩選
  • having後可以跟聚合函式,且不必與select後面的聚合函式相同

MySQL常用函式彙總

數值型函式

函式名稱 作用
abs(n) 求絕對值
sqrt(n) 求二次方根
mod(m, n) 求餘數
ceil(n)、ceiling(n) 返回不小於引數的最小整數,也即向上取整
floor(n) 向下取整,返回值轉化為BIGINT
rand() 生成一個0~1之間的隨機數,傳入整數引數用來產生重複序列
round(n) 對所傳引數進行四捨五入
sign(n) 返回引數的符號
pow(x, y)、power(x, y) 計算所傳引數的次方結果值
sin(n)、asin(n) 求正弦、反正弦
cos(n)、acos(n) 求餘弦、反餘弦
tan(n)、atan(n) 求正切、反正切
cot(n) 求餘切
select abs(5), abs(-2.5), abs(0);
select sqrt(25), sqrt(2), sqrt(-9);
select mod(6, 4), mod(10, 10), mod(5.5, 5);
select ceil(-2.1), ceil(2.1);
select floor(5), floor(5.5), floor(-5.5);
select rand(), rand(), rand(1), rand(1), rand(2);
select round(-5.6), round(-5.4), round(-5.5), round(3.3);
select sign(-5), sign(0), sign(3);
select pow(5, -2), pow(2, 3), pow(100, 0);

字串函式

函式名稱 作用
length(s) 計算字串長度,返回位元組長度
concat(s1, s2...) 合併字串函式,若有任何一個引數為NULL則返回值為NULL,若引數中含有任一二進位制字串,則結果為一個二進位制字串
insert(s, r) 替換字串函式
lower(s) 將字串中的字母轉換為小寫
upper(s) 將字串中的字母轉換為大寫
left(s, n) 從左側擷取字串,返回字串左邊的若干字元
right(s, n) 從右側擷取字串,返回字串右邊的若干字元
trim(s) 刪除字串左右兩側的空格
replace(s, r) 字串替換函式,返回替換後的新字串
substr(s, m, n)、substring() 擷取字串,返回從指定位置開始的指定長度的字串
reverse(s) 字串反轉函式
select length('hello'), length('java'), length('架構師');
select concat('hello ', 'world'), concat(null, 'what?');
select insert('hello world', 7, 5, 'jojo') r1, insert('hello world', -1, 4, '*') r2, insert('hello world', 4, 50, '*') r3;
select lower('HELLO 你好'), upper('hello world');
select left('hello world', 4), right('hello world', 4), right('abcde', -1);
select trim(' is there spaces in this string? ');
select replace('hello laogong', 'laogong', 'laowang');
select substr('apple', 3), substring('apple', -3), substr('banana', 2, 3), substring('strawberry', -2, 5), substr('peach' from 2), substring('pineapple' from 4 for 5);
select reverse('white'), reverse('black');

日期和時間函式

函式名稱 作用
curdate()、current_date() 返回當前系統日期
curtime()、current_time() 返回當前系統時間
now()、sysdate() 返回當前系統的日期和時間值
unix_timestamp() 返回一個以UNIX時間戳為基礎的無符號整數
from_unixtime() 將UNIX時間戳轉換為時間格式
month() 獲取指定日期中的月份
monthname() 獲取指定日期中月份英文名稱
dayname() 獲取指定日期對應的星期幾英文名
dayofweek() 獲取指定日期是一週中第幾天,返回值範圍1~7
week() 獲取指定日期是一年中的第幾周,範圍為052或153
dayofyear() 獲取指定日期是一年中的第幾天,範圍1~366
dayofmonth() 獲取指定日期是一月中的第幾天,範圍1~31
year() 獲取年份,範圍1970~2069
time_to_sec() 將時間轉換為秒
sec_to_time() 將秒轉換為時間
date_add()、adddate() 向日期新增指定的時間間隔
date_sub()、subdate() 向日期減去指定的時間間隔
addtime() 在原時間上新增指定的時間
subtime() 在原時間上減去指定的時間
datediff() 獲取兩個日期之間的間隔,引數一減去引數二
date_format() 格式化指定的日期
weekday() 獲取指定日期在一週內的對應工作日索引

聚合函式

函式名 作用
max 查詢指定列的最大值
min 查詢指定列的最小值
count 統計查詢結果的行數
sum 求和,返回指定列的總和
avg 求平均值

流程控制函式

if、ifnull、case

if(expression, value1, value2)

ifnull(value1, value2)

case <expression>
      when <value1> then <operation1>
      when <value2> then <operation2>
      ...
      else <operation>
end case;
case 
      when <condition1> then <operation1>
      when <condition2> then <operation2>
      ...
      else <operation>
end case;

其他函式

函式名 作用
version 資料庫版本
database 當前資料庫名稱
user 當前連線的使用者名稱
password 返回字串密碼形式(8以後版本已廢棄)
md5 返回字串的md5加密

連線查詢及原理

笛卡爾積

表示兩個集合A、B中元素任意組合產生的所有可能結果

//Java表示
for(Object aElement : aCollection) {
      for(Object bElement : bCollection) {
            System.out.println(aElement + ", " + bElement);
      }
}

#SQL語法
select columns from table1, table2 [, tablen...];
select columns from table1 join table2 [join tablen...];

內連線

select columns from table1 inner join table2 on join_condition;
select columns from table1 join table2 on ...;
select columns from table1, table2 where join_condition;

//Java表示
for(Object aElement : aCollection) {
      for(Object bElement : bCollection) {
            if(連線條件) {
                  System.out.println(aElement + ", " + bElement);
            }
      }
}

外連線

查詢結果 = 內連線結果 + 主表中有而內連線結果中沒有的記錄

  • 左外連線
  • 右外連線

Java實現連線查詢

package join;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

public class Test1 {

    public static class Table1 {
        int a;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public Table1(int a) {
            this.a = a;
        }

        @Override
        public String toString() {
            return "Table1{" +
                    "a=" + a +
                    '}';
        }

        public static Table1 build(int a) {
            return new Table1(a);
        }
    }

    public static class Table2 {
        int b;

        public int getB() {
            return b;
        }

        public void setB(int b) {
            this.b = b;
        }

        public Table2(int b) {
            this.b = b;
        }

        public static Table2 build(int b) {
            return new Table2(b);
        }

        @Override
        public String toString() {
            return "Table2{" +
                    "b=" + b +
                    '}';
        }
    }

    public static class Record<R1, R2> {
        R1 r1;
        R2 r2;

        public R1 getR1() {
            return r1;
        }

        public void setR1(R1 r1) {
            this.r1 = r1;
        }

        public R2 getR2() {
            return r2;
        }

        public void setR2(R2 r2) {
            this.r2 = r2;
        }

        public Record(R1 r1, R2 r2) {
            this.r1 = r1;
            this.r2 = r2;
        }

        @Override
        public String toString() {
            return "Record{" +
                    "r1=" + r1 +
                    ", r2=" + r2 +
                    '}';
        }

        public static <R1, R2> Record<R1, R2> build(R1 r1, R2 r2) {
            return new Record<>(r1, r2);
        }
    }

    public static enum JoinType {
        innerJoin, leftJoin
    }

    public static interface Filter<R1, R2> {
        boolean accept(R1 r1, R2 r2);
    }

    public static <R1, R2> List<Record<R1, R2>> join(List<R1> table1, List<R2> table2, JoinType joinType, Filter<R1, R2> onFilter, Filter<R1, R2> whereFilter) {
        if(Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) {
            return new ArrayList<>();
        }

        List<Record<R1, R2>> result = new CopyOnWriteArrayList<>();

        //笛卡爾積
        for(R1 r1 : table1) {
            List<Record<R1, R2>> onceJoinResult = joinOn(r1, table2, onFilter);
            result.addAll(onceJoinResult);
        }

        if(joinType == JoinType.leftJoin) {
            List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList());
            List<Record<R1, R2>> leftAppendList = new ArrayList<>();
            for (R1 r1 : table1) {
                if (!r1Record.contains(r1)) {
                    leftAppendList.add(Record.build(r1, null));
                }
            }
            result.addAll(leftAppendList);
        }
        if (Objects.nonNull(whereFilter)) {
            for (Record<R1, R2> record : result) {
                if (!whereFilter.accept(record.r1, record.r2)) {
                    result.remove(record);
                }
            }
        }
        return result;
    }

    public static <R1, R2> List<Record<R1, R2>> joinOn(R1 r1, List<R2> table2, Filter<R1, R2> onFilter) {
        List<Record<R1, R2>> result = new ArrayList<>();
        for (R2 r2 : table2) {
            if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) {
                result.add(Record.build(r1, r2));
            }
        }
        return result;
    }

    @Test
    public void innerJoin () {
        List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
        List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));

        join(table1, table2, JoinType.innerJoin, null, null).forEach(System.out::println);
        System.out.println("------------------");
        join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
    }

    @Test
    public void leftJoin() {
        List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
        List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));

        join(table1, table2, JoinType.leftJoin, null, null).forEach(System.out::println);
        System.out.println("------------------");
        join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10, null).forEach(System.out::println);
    }

}

上面程式碼的執行結果與MySQL查詢結果存在順序上的不一致,原因:

上面java程式碼中兩個表的連線查詢使用了巢狀迴圈,外迴圈每執行一次,內迴圈的表都會全部遍歷一次,如果放到mysql中,就相當於內表(被驅動表)全部掃描了一次(一次全表io讀取操作),主表(外迴圈)如果有n條資料,那麼從表就需要全表掃描n次,表的資料是儲存在磁碟中,每次全表掃描都需要做io操作,io操作是最耗時間的,如果mysql按照上面的java方式實現,那效率肯定很低。

MySQL對於連線查詢的優化:

msql內部使用了一個記憶體快取空間,就叫他 join_buffer 吧,先把外迴圈的資料放到 join_buffer 中,然後對從表進行遍歷,從表中取一條資料和 join_buffer 的資料進行比較, 然後從表中再取第2條和 join_buffer 資料進行比較,直到從表遍歷完成,使用這方方式來減少 從表的io掃描次數,當 join_buffer 足夠大的時候,大到可以存放主表所有資料,那麼從表只需 要全表掃描一次(即只需要一次全表io讀取操作)。MySQL中這種方式叫做Block Nested Loop。

package join;

import org.junit.Test;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;

public class Test2 {

    public static int joinBufferSize = 10000;
    public static List<?> joinBufferList = new ArrayList<>();

    public static class Table1 {
        int a;

        public int getA() {
            return a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public Table1(int a) {
            this.a = a;
        }

        @Override
        public String toString() {
            return "Table1{" +
                    "a=" + a +
                    '}';
        }

        public static Table1 build(int a) {
            return new Table1(a);
        }
    }

    public static class Table2 {
        int b;

        public int getB() {
            return b;
        }

        public void setB(int b) {
            this.b = b;
        }

        public Table2(int b) {
            this.b = b;
        }

        public static Table2 build(int b) {
            return new Table2(b);
        }

        @Override
        public String toString() {
            return "Table2{" +
                    "b=" + b +
                    '}';
        }
    }

    public static class Record<R1, R2> {
        R1 r1;
        R2 r2;

        public R1 getR1() {
            return r1;
        }

        public void setR1(R1 r1) {
            this.r1 = r1;
        }

        public R2 getR2() {
            return r2;
        }

        public void setR2(R2 r2) {
            this.r2 = r2;
        }

        public Record(R1 r1, R2 r2) {
            this.r1 = r1;
            this.r2 = r2;
        }

        @Override
        public String toString() {
            return "Record{" +
                    "r1=" + r1 +
                    ", r2=" + r2 +
                    '}';
        }

        public static <R1, R2> Record<R1, R2> build(R1 r1, R2 r2) {
            return new Record<>(r1, r2);
        }
    }

    public static enum JoinType {
        innerJoin, leftJoin
    }

    public static interface Filter<R1, R2> {
        boolean accept(R1 r1, R2 r2);
    }

    public static <R1, R2> List<Record<R1, R2>> join(List<R1> table1, List<R2> table2,
                                                     JoinType joinType, Filter<R1, R2> onFilter, Filter<R1, R2> whereFilter) {
        if(Objects.isNull(table1) || Objects.isNull(table2) || joinType == null) {
            return new ArrayList<>();
        }

        List<Record<R1, R2>> result = new CopyOnWriteArrayList<>();

        int table1Size = table1.size();
        int fromIndex = 0, toIndex = joinBufferSize;
        toIndex = Integer.min(table1Size, toIndex);
        while (fromIndex < table1Size && toIndex <= table1Size) {
            joinBufferList = table1.subList(fromIndex, toIndex);
            fromIndex = toIndex;
            toIndex += joinBufferSize;
            toIndex = Integer.min(table1Size, toIndex);

            List<Record<R1, R2>> blockNestedLoopResult = blockNestedLoop((List<R1>) joinBufferList, table2, onFilter);
            result.addAll(blockNestedLoopResult);
        }

        if (joinType == JoinType.leftJoin) {
            List<R1> r1Record = result.stream().map(Record::getR1).collect(Collectors.toList());
            List<Record<R1, R2>> leftAppendList = new ArrayList<>();
            for (R1 r1 : table1) {
                if (!r1Record.contains(r1)) {
                    leftAppendList.add(Record.build(r1, null));
                }
            }
            result.addAll(leftAppendList);
        }
        if (Objects.nonNull(whereFilter)) {
            for (Record<R1, R2> record : result) {
                if (!whereFilter.accept(record.r1, record.r2)) {
                    result.remove(record);
                }
            }
        }
        return result;
    }

    public static <R1, R2> List<Record<R1, R2>> blockNestedLoop(List<R1> joinBufferList, List<R2> table2, Filter<R1, R2> onFilter) {
        List<Record<R1, R2>> result = new ArrayList<>();
        for (R2 r2 : table2) {
            for (R1 r1 : joinBufferList) {
                if (Objects.nonNull(onFilter) ? onFilter.accept(r1, r2) : true) {
                    result.add(Record.build(r1, r2));
                }
            }
        }
        return result;
    }

    @Test
    public void innerJoin() {
        List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
        List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));

        join(table1, table2, JoinType.innerJoin, null, null).forEach(System.out::println);
        System.out.println("-----------------------");
        join(table1, table2, JoinType.innerJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
    }

    @Test
    public void leftJoin() {
        List<Table1> table1 = Arrays.asList(Table1.build(1), Table1.build(2), Table1.build(3));
        List<Table2> table2 = Arrays.asList(Table2.build(3), Table2.build(4), Table2.build(5));

        join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a == r2.b, null).forEach(System.out::println);
        System.out.println("-----------------------");
        join(table1, table2, JoinType.leftJoin, (r1, r2) -> r1.a > 10, null).forEach(System.out::println);
    }
}

以上內容源自微信:itsoku的《MySQL筆記》,本隨筆僅供學習交流