<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          神奇的 SQL 之 CASE表達式,妙用多多 !

          共 5964字,需瀏覽 12分鐘

           ·

          2022-03-02 20:31

          前言


          歷史考試選擇題:黃花崗起義第一槍誰開的? A宋教仁 B孫中山 C黃興 D徐錫麟,考生選C。

          又看第二題:黃花崗起義第二槍誰開的? 考生傻了,就選了個B。

          接著看第三題:黃花崗起義中,第三槍誰開的? 考生瘋了,胡亂選了A。

          考試出來就去找出卷老師。老師拿出課本說:黃興連開三槍,揭開了黃花崗起義的序幕。考生:......


          CASE表達式 之概念

          相信大家都用過CASE表達式,尤其是做一些統(tǒng)計功能的時候,用的特別多,可真要說什么是 CASE表達式,我估計還真沒幾個人能清楚的表述出來。

          CASE表達式和 “2+1” 或者 “120/3” 這樣的表達式一樣,是一種進行運算的功能,正如CASE(情況)這個詞的含義一樣,用于區(qū)分情況,在有條件分歧的時候使用它。

          CASE表達式是從 SQL-92 標準開始被引入的,可能因為它是相對較新的技術(shù),所以盡管使用起來非常便利,但其真正的價值卻并不怎么為人所知。很多人不用它,或者用它的簡略版函數(shù),例如 DECODE(Oracle)、IF(MySQL)等。然而,CASE表達式也許是 SQL-92 標準里加入的最有用的特性,如果能用好它,那么 SQL 能解決的問題就會更廣泛,寫法也會更加漂亮,而且,因為 CASE表達式 是不依賴于具體數(shù)據(jù)庫的技術(shù),所以可以提高 SQL 代碼的可移植性。


          基本格式如下

          --?簡單?CASE表達式
          CASE?列(或表達式)
          ?????WHEN?<匹配值1>?THEN?<表達式>
          ?????WHEN?<匹配值2>?THEN?<表達式>
          ?????......
          ?????ELSE?<表達式>
          END

          --?搜索?CASE表達式
          CASE?WHEN?<判斷表達式>?THEN?<表達式>
          ?????WHEN?<判斷表達式>?THEN?<表達式>
          ?????WHEN?<判斷表達式>?THEN?<表達式>
          ?????......
          ?????ELSE?<表達式>
          END


          --?簡單?CASE表達式?示例
          CASE?sex
          ????WHEN?'1'?THEN?'男'
          ????WHEN?'2'?THEN?'女'
          ????ELSE?'其他'?
          END

          --?搜索CASE表達式?示例
          CASE?WHEN?sex?=?'1'?THEN?'男'
          ?????WHEN?sex?=?'2'?THEN?'女'
          ?????ELSE?'其他'?
          END

          CASE表達式 的 ELSE子句 可以省略,但推薦不要省略,省略了可能會出現(xiàn)我們意料之外的結(jié)果。END不能省,必須有。當(dāng) WHEN子句 為真時,CASE表達式 的真假值判斷就會中止,而剩余的 WHEN子句會被忽略。為了避免引起不必要的混亂,使用 WHEN子句 時要注意條件的排他性。
          簡單CASE表達式正如其名,寫法簡單,但能實現(xiàn)的功能比較有限。簡單CASE表達式能寫的條件,搜索CASE表達式也能寫,所以基本上采用搜索CASE表達式的寫法。

          CASE表達式 之妙用


          上面講了 CASE表達式 的理論知識,感覺不痛不癢,那么接下來我們進入實戰(zhàn)篇,結(jié)合一些場景來看看 CASE表達式 的妙用

          行轉(zhuǎn)列

          可能我們用的更多的是 IF(MySQL)或 DECODE(Oracle),但這兩者都不是標準的 SQL,更推薦大家用 CASE表達式,移植性更高
          假設(shè)我們有如下表,以及如下數(shù)據(jù)
          CREATE?TABLE?t_customer_credit?(
          ????id?INT(11)?UNSIGNED?NOT?NULL?AUTO_INCREMENT?COMMENT?'自增主鍵',
          ????login_name?VARCHAR(50)?NOT?NULL?COMMENT?'登錄名',
          ????credit_type?TINYINT(1)?NOT?NULL?COMMENT?'額度類型,1:自由資金,2:凍結(jié)資金,3:優(yōu)惠',
          ????amount?DECIMAL(22,6)?NOT?NULL?DEFAULT?'0.00000'?COMMENT?'額度值',
          ????create_by?VARCHAR(50)?NOT?NULL?COMMENT?'創(chuàng)建者',
          ????create_time?DATETIME?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?COMMENT?'創(chuàng)建時間',
          ????update_time?DATETIME?NOT?NULL?DEFAULT?CURRENT_TIMESTAMP?ON?UPDATE?CURRENT_TIMESTAMP?COMMENT?'創(chuàng)建時間',
          ????update_by?VARCHAR(50)?NOT?NULL?COMMENT?'修改者',
          ??PRIMARY?KEY?(id)
          );
          INSERT?INTO?`t_customer_credit`?VALUES?(1,?'zhangsan',?1,?550.000000,?'system',?'2019-7-7?11:30:09',?'2019-7-8?20:21:05',?'system');
          INSERT?INTO?`t_customer_credit`?VALUES?(2,?'zhangsan',?2,?0.000000,?'system',?'2019-7-7?11:30:09',?'2019-7-7?11:30:09',?'system');
          INSERT?INTO?`t_customer_credit`?VALUES?(3,?'zhangsan',?3,?0.000000,?'system',?'2019-7-7?11:30:09',?'2019-7-7?11:30:09',?'system');
          INSERT?INTO?`t_customer_credit`?VALUES?(4,?'lisi',?1,?0.000000,?'system',?'2019-7-7?11:30:09',?'2019-7-7?11:30:09',?'system');
          INSERT?INTO?`t_customer_credit`?VALUES?(5,?'lisi',?2,?0.000000,?'system',?'2019-7-7?11:30:09',?'2019-7-7?11:30:09',?'system');
          INSERT?INTO?`t_customer_credit`?VALUES?(6,?'lisi',?3,?0.000000,?'system',?'2019-7-7?11:30:09',?'2019-7-7?11:30:09',?'system');


          如果我們要一行顯示用戶的三個額度,而不是 3 條記錄顯示 3 個額度,我們應(yīng)該怎么做,方式有很多種,這里提供如下 3 種

          --?1、最容易想到的IF,不具備移植性,不推薦
          SELECT?login_name,
          ????MAX(IF(credit_type=1,?amount,?0))?freeAmount,
          ????MAX(IF(credit_type=2,?amount,?0))?freezeAmount,
          ????MAX(IF(credit_type=3,?amount,?0))?promotionAmount
          FROM?t_customer_credit?GROUP?BY?login_name;

          --?2、CASE表達式,標準的?SQL?規(guī)范,具備移植性,推薦使用
          SELECT?login_name,
          ????MAX(CASE?WHEN?credit_type?=?1?THEN?amount?ELSE?0?END)?freeAmount,
          ????MAX(CASE?WHEN?credit_type?=?2?THEN?amount?ELSE?0?END)?freezeAmount,
          ????MAX(CASE?WHEN?credit_type?=?3?THEN?amount?ELSE?0?END)?promotionAmount
          FROM?t_customer_credit?GROUP?BY?login_name;

          --?3、自連接,數(shù)據(jù)量大的情況下,結(jié)合索引,效率不錯,具備移植性
          SELECT
          ????a.login_name,a.amount?freeAmount,
          ????b.amount?freezeAmount,
          ????c.amount?promotionAmount
          FROM?(
          ????SELECT?login_name,?amount?FROM?t_customer_credit?WHERE?credit_type?=?1
          )a
          LEFT?JOIN?t_customer_credit?b?ON?a.login_name?=?b.login_name?AND?b.credit_type?=?2
          LEFT?JOIN?t_customer_credit?c?ON?a.login_name?=?c.login_name?AND?c.credit_type?=?3;


          無論是 IF 還是 CASE表達式,都結(jié)合了 GROUP BY 與聚合函數(shù),效率是個問題,而自連接是效率最高的,不管在不在 login_name 上加索引


          轉(zhuǎn)換統(tǒng)計

          將已有編號方式轉(zhuǎn)換為新的方式并統(tǒng)計,在進行非定制化統(tǒng)計時,我們經(jīng)常會遇到將已有編號方式轉(zhuǎn)換為另外一種便于分析的方式并進行統(tǒng)計的需求。假設(shè)我們有如下表

          DROP?TABLE?t_province_population;
          CREATE?TABLE?t_province_population?(
          ??id?tinyint(2)?unsigned?NOT?NULL?AUTO_INCREMENT?COMMENT?'自增主鍵',
          ??province_name?varchar(50)?NOT?NULL?COMMENT?'省份名',
          ??sex?tinyint(1)?NOT?NULL?COMMENT?'性別,1:男,2:女',
          ??population?int(11)?NOT?NULL?COMMENT?'人口數(shù)',
          ??PRIMARY?KEY?(id)
          );

          INSERT?INTO?t_province_population(province_name,sex,population)
          VALUES
          ("黑龍江",?1?,20),
          ("黑龍江",?2?,18),
          ("內(nèi)蒙古",?1?,7),
          ("內(nèi)蒙古",?2?,8),
          ("海南",?1?,20),
          ("海南",?2?,22),
          ("西藏",?1?,8),
          ("西藏",?2?,7),
          ("浙江",?1?,35),
          ("浙江",?2?,35),
          ("臺灣",?1?,26),
          ("臺灣",?2?,23),
          ("河南",?1?,40),
          ("河南",?2?,38),
          ("湖北",?1?,27),
          ("湖北",?2?,24);

          SELECT?*?FROM?t_province_population;


          我們需要按各個省所在的位置,統(tǒng)計出東南西北中,各個區(qū)域內(nèi)的人口數(shù)量

          東:浙江、臺灣,西:西藏,南:海南,北:黑龍江、內(nèi)蒙古,中:湖北、河南

          可能有人覺得這個表設(shè)計的不合理,應(yīng)該在設(shè)計之初就應(yīng)該多加一個區(qū)域字段(district)來標明各省所屬區(qū)域。最好的做法確實是這樣,但這得需要我們在設(shè)計之初的時候能考慮得到,或者有這樣的需求,假設(shè)我們設(shè)計之初沒有這樣的需求,而我們也沒考慮到,那么有沒有什么辦法來實現(xiàn)了?我們可以這樣來寫 SQL


          --?通用寫法,適用于多種數(shù)據(jù)庫
          SELECT?CASE?province_name
          ????WHEN?'浙江'?THEN?'東'
          ????WHEN?'臺灣'?THEN?'東'
          ????WHEN?'海南'?THEN?'南'
          ????WHEN?'西藏'?THEN?'西'
          ????WHEN?'黑龍江'?THEN?'北'
          ????WHEN?'內(nèi)蒙古'?THEN?'北'
          ????WHEN?'河南'?THEN?'中'
          ????WHEN?'湖北'?THEN?'種'
          ????ELSE?'其他'?END?district,
          ????SUM(population)?populations
          FROM?t_province_population
          GROUP?BY?CASE?province_name
          ????WHEN?'浙江'?THEN?'東'
          ????WHEN?'臺灣'?THEN?'東'
          ????WHEN?'海南'?THEN?'南'
          ????WHEN?'西藏'?THEN?'西'
          ????WHEN?'黑龍江'?THEN?'北'
          ????WHEN?'內(nèi)蒙古'?THEN?'北'
          ????WHEN?'河南'?THEN?'中'
          ????WHEN?'湖北'?THEN?'中'
          ????ELSE?'其他'?END;

          --?MySQL支持寫法,移植性差
          SELECT?CASE?province_name
          ????WHEN?'浙江'?THEN?'東'
          ????WHEN?'臺灣'?THEN?'東'
          ????WHEN?'海南'?THEN?'南'
          ????WHEN?'西藏'?THEN?'西'
          ????WHEN?'黑龍江'?THEN?'北'
          ????WHEN?'內(nèi)蒙古'?THEN?'北'
          ????WHEN?'河南'?THEN?'中'
          ????WHEN?'湖北'?THEN?'中'
          ????ELSE?'其他'?END?district,
          ????SUM(population)?populations
          FROM?t_province_population
          GROUP?BY?district;


          結(jié)果如下

          假設(shè)我們需要對各個省份做一個人口數(shù)級別的統(tǒng)計,統(tǒng)計出各個級別的數(shù)量

          level_1:population < 20,level_2:20 <= population < 50 ,level_3:50 <= population < 70?,level_4:>= 70;統(tǒng)計出 level_1 ~ level_4 的數(shù)量各有多少

          SQL 與執(zhí)行結(jié)果如下

          SELECT?
          ????CASE?WHEN?population?20?THEN?'level_1'
          ????????WHEN?population?>=?20?AND?population?50?THEN?'level_2'
          ????????WHEN?population?>=?50?AND?population?70?THEN?'level_3'
          ????????WHEN?population?>=?70?THEN?'level_4'
          ????????ELSE?NULL?
          ????END?pop_level,
          ????COUNT(*)?cnt
          FROM?(
          ????SELECT?province_name,SUM(population)?population?FROM?t_province_population?GROUP?BY?province_name
          )a
          GROUP?BY?
          ????CASE?WHEN?population?20?THEN?'level_1'
          ????????WHEN?population?>=?20?AND?population?50?THEN?'level_2'
          ????????WHEN?population?>=?50?AND?population?70?THEN?'level_3'
          ????????WHEN?population?>=?70?THEN?'level_4'
          ????????ELSE?NULL?
          ????END;


          這種轉(zhuǎn)換統(tǒng)計還是比較常用的,重點就是 GROUP BY 子句的寫法。


          條件分支

          SELECT 條件分支

          還是以上面的?t_province_population 為例,如果我們想要直觀的知道各個省份的男、女?dāng)?shù)量情況,類似如下

          我們要怎么寫 SQL?有如下兩種方法

          --?1、CASE表達式?集合?GROUP?BY
          SELECT?province_name,
          ????SUM(CASE?WHEN?sex?=?1?THEN?population?ELSE?0?END)?c,
          ????SUM(CASE?WHEN?sex?=?2?THEN?population?ELSE?0?END)?f_pops
          FROM?t_province_population
          GROUP?BY?province_name;

          --?2、自關(guān)聯(lián)
          SELECT?t.province_name,?t.population?m_pops,?a.population?f_pops
          FROM?t_province_population?t
          LEFT?JOIN?t_province_population?a
          ON?t.province_name?=?a.province_name
          WHERE?t.sex?=?1?AND?a.sex?=?2;


          其實就是行轉(zhuǎn)列,行轉(zhuǎn)列更容易懂

          UPDATE 條件分支

          我們有一張薪資表,如下

          CREATE?TABLE?t_user_salaries(
          ??id?int(11)?unsigned?NOT?NULL?AUTO_INCREMENT?COMMENT?'自增主鍵',
          ??name?varchar(50)?NOT?NULL?COMMENT?'姓名',
          ????sex?tinyint(1)?NOT?NULL?COMMENT?'性別,1:男,2:女',
          ??salary?int(11)?NOT?NULL?COMMENT?'薪資',
          ??PRIMARY?KEY?(id)
          );

          INSERT?INTO?t_user_salaries(name,?sex,salary)?VALUES
          ("張三",?1,?30000),
          ("李四",?1,?27000),
          ("王五",?1,?22000),
          ("菲菲",?2,?24000),
          ("趙六",?1,?29000);

          SELECT?*?FROM?t_user_salaries;


          假設(shè)現(xiàn)在需要根據(jù)以下條件對該表的數(shù)據(jù)進行更新:1、對當(dāng)前工資為 30000 元以上的員工,降薪 10%,2、對當(dāng)前工資為 25000 元以上且不滿 28000 元的員工,加薪 20%。調(diào)整之后的薪資如下所示

          乍一看,分別執(zhí)行下面兩個 UPDATE 操作好像就可以做到,但是我們執(zhí)行下看看結(jié)果

          --?條件1
          UPDATE?t_user_salaries
          SET?salary?=?salary?*?0.9
          WHERE?salary?>=?30000;

          --?條件2
          UPDATE?t_user_salaries
          SET?salary?=?salary?*?1.2
          WHERE?salary?>=?25000?AND?salary?28000;



          我們發(fā)現(xiàn)張三的薪資不降反升了!這是因為執(zhí)行 條件1的SQL后,張三的薪資又滿足條件2了,所以又更新了一遍,導(dǎo)致他的薪資變多了,有人可能會說,把條件1和條件2的SQL換下順序不就好了嗎,我們來試試

          --?條件2
          UPDATE?t_user_salaries
          SET?salary?=?salary?*?1.2
          WHERE?salary?>=?25000?AND?salary?28000;

          --?條件1
          UPDATE?t_user_salaries
          SET?salary?=?salary?*?0.9
          WHERE?salary?>=?30000;



          張三的薪資是降對了,可李四的薪資卻漲錯了!這是因為李四的薪資滿足條件2,升了 20% 之后又滿足條件1,又降了 10%。難道就沒有就沒有正確的方式了?我們來看看這個 SQL

          UPDATE?t_user_salaries?SET?salary?=?
          ????CASE?WHEN?salary?>=?30000?THEN?salary?*?0.9
          ????????????WHEN?salary?>=?25000?AND?salary?28000?THEN?salary?*?1.2
          ????????????ELSE?salary
          ????END;

          SELECT?*?FROM?t_user_salaries;


          完美不?特別完美,這個技巧的應(yīng)用范圍很廣,值得我們掌握

          CHECK 約束


          注意:CHECK 是標準的 SQL,但是 MySQL 卻沒有實現(xiàn)它,所以 CHECK 在 MySQL 中是不起作用的!


          回到我們的薪資表,假設(shè)某個公司有這樣一個無理的規(guī)定:女性員工的工資不得高于50000,我們?nèi)绻麑崿F(xiàn)它??方式有兩種:1、代碼層面控制 、2、數(shù)據(jù)庫表加約束。


          代碼層面控制就不多說了,這我們平時最能想到的,實際也是用的最多的;那從表約束,我們該如何實現(xiàn)了,像這樣嗎?

          --?創(chuàng)建表的時候增加約束
          CREATE?TABLE?t_user_salaries_check(
          ??name?varchar(50)?NOT?NULL?COMMENT?'姓名',
          ????sex?tinyint(1)?NOT?NULL?COMMENT?'性別,1:男,2:女',
          ??salary?int(11)?NOT?NULL?COMMENT?'薪資',
          ????CONSTRAINT?chk_sex_salary?CHECK?(sex=2?AND?salary?<=?50000)
          );

          --?若t_user_salaries_check已創(chuàng)建,則補充上約束
          ALTER?TABLE?t_user_salaries_check
          ADD?CONSTRAINT?chk_sex_salary?CHECK?(sex=2?AND?salary?<=?50000);


          這么實現(xiàn)你會發(fā)現(xiàn)公司的男同事都會提著刀來找你了,因為沒有他們的薪資,這個約束會導(dǎo)致錄入不了男性的薪資!因為我們的約束是:sex=2 AND salary < = 50000 表示 “是女性,并且薪資不能高于50000”,而不是:“如果是女性,薪資不高于50000”。正確的約束條件應(yīng)該這么寫

          --?創(chuàng)建表的時候增加約束
          CREATE?TABLE?t_user_salaries_check(
          ??name?varchar(50)?NOT?NULL?COMMENT?'姓名',
          ????sex?tinyint(1)?NOT?NULL?COMMENT?'性別,1:男,2:女',
          ??salary?int(11)?NOT?NULL?COMMENT?'薪資',
          ??PRIMARY?KEY?(id),
          ????CONSTRAINT?chk_sex_salary?CHECK(
          ????????CASE?WHEN?sex?=?2?THEN?
          ????????????????????????CASE?WHEN?salary?<=?50000?THEN?1?
          ????????????????????????????????ELSE?0?
          ????????????????????????END
          ????????????????ELSE?1?
          ????????END?=?1?)
          );

          --?若t_user_salaries_check已創(chuàng)建,則補充上約束
          ALTER?TABLE?t_user_salaries_check
          ADD?CONSTRAINT?chk_sex_salary?CHECK(
          ????CASE?WHEN?sex?=?2?THEN?
          ????????????????????????CASE?WHEN?salary?<=?50000?THEN?1?
          ????????????????????????????????ELSE?0?
          ????????????????????????END
          ????????????????ELSE?1?
          ????????END?=?1?
          );


          CASE表達式還有很多其他的用處,強大的不得了,而且高度靈活;用好它,能讓我們寫出更加契合的 SQL。


          總結(jié)


          1、CASE表達式 是支撐 SQL 聲明式編程的根基之一,也是靈活運用 SQL 時不可或缺的基礎(chǔ)技能。作為表達式,CASE 表達式在執(zhí)行時會被判定為一個固定值,因此它可以寫在聚合函數(shù)內(nèi)部;也正因為它是表達式,所以還可以寫在SELECE 子句、GROUP BY 子句、WHERE 子句、ORDER BY 子句里。簡單點說,在能寫列名和常量的地方,通常都可以寫 CASE 表達式


          2、寫 CASE表達式 的注意點

          ????a、各個分支返回的數(shù)據(jù)類型要一致

          ????b、養(yǎng)成寫 ELSE 的好習(xí)慣

          ????c、不要忘了寫 END


          參考

          《SQL基礎(chǔ)教程》《SQL進階教程》


          youzhibing2904

          https://www.cnblogs.com/youzhibing/p/11240536.html

          我是岳哥,最后給大家分享我寫的SQL兩件套:《SQL基礎(chǔ)知識第二版》《SQL高級知識第二版》的PDF電子版。里面有各個語法的解釋、大量的實例講解和批注等等,非常通俗易懂,方便大家跟著一起來實操。


          有需要的讀者可以下載學(xué)習(xí),在下面的公眾號「數(shù)據(jù)前線」(非本號)后臺回復(fù)關(guān)鍵字:SQL,就行

          數(shù)據(jù)前線

          后臺回復(fù)關(guān)鍵字:1024,獲取一份精心整理的技術(shù)干貨

          后臺回復(fù)關(guān)鍵字:進群,帶你進入高手如云的交流群。


          推薦閱讀

          瀏覽 112
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91嫩草久久久天美传媒 | 伊人天天操天天爱 | 免费AV网址大全 | 国产午夜在线一区二区三区 | 日韩毛片在线 |