SQL 是一种声明式的语言,我们只需要描述想要的结果(WHAT),而不关心数据库如何实现(HOW);虽然 SQL 比较容易学习,但是有一些容易混淆和出错的概念。今天就来说说 SQL 中的空值陷阱和避坑方法。
本文使用的示例表结构如下:
示例数据:
INSERT INTO departments VALUES ( 10 , 'Administration' , 200 , 1700 ); INSERT INTO departments VALUES ( 20 , 'Marketing' , 201 , 1800 ); INSERT INTO departments VALUES ( 30 , 'Purchasing' , 114 , 1700 ); INSERT INTO departments VALUES ( 40 , 'Human Resources' , 203 , 2400 ); INSERT INTO departments VALUES ( 50 , 'Shipping' , 121 , 1500 ); INSERT INTO departments VALUES ( 60 , 'IT' , 103 , 1400 ); INSERT INTO departments VALUES ( 70 , 'Public Relations' , 204 , 2700 ); INSERT INTO departments VALUES ( 80 , 'Sales' , 145 , 2500 ); INSERT INTO departments VALUES ( 90 , 'Executive' , 100 , 1700 ); INSERT INTO departments VALUES ( 100 , 'Finance' , 108 , 1700 ); INSERT INTO departments VALUES ( 110 , 'Accounting' , 205 , 1700 ); INSERT INTO departments VALUES ( 120 , 'Treasury' , NULL , 1700 ); INSERT INTO departments VALUES ( 130 , 'Corporate Tax' , NULL , 1700 ); INSERT INTO departments VALUES ( 140 , 'Control And Credit' , NULL , 1700 ); INSERT INTO departments VALUES ( 150 , 'Shareholder Services' , NULL , 1700 ); INSERT INTO departments VALUES ( 160 , 'Benefits' , NULL , 1700 ); INSERT INTO departments VALUES ( 170 , 'Manufacturing' , NULL , 1700 ); INSERT INTO departments VALUES ( 180 , 'Construction' , NULL , 1700 ); INSERT INTO departments VALUES ( 190 , 'Contracting' , NULL , 1700 ); INSERT INTO departments VALUES ( 200 , 'Operations' , NULL , 1700 ); INSERT INTO departments VALUES ( 210 , 'IT Support' , NULL , 1700 ); INSERT INTO departments VALUES ( 220 , 'NOC' , NULL , 1700 ); INSERT INTO departments VALUES ( 230 , 'IT Helpdesk' , NULL , 1700 ); INSERT INTO departments VALUES ( 240 , 'Government Sales' , NULL , 1700 ); INSERT INTO departments VALUES ( 250 , 'Retail Sales' , NULL , 1700 ); INSERT INTO departments VALUES ( 260 , 'Recruiting' , NULL , 1700 ); INSERT INTO departments VALUES ( 270 , 'Payroll' , NULL , 1700 ); INSERT INTO jobs VALUES ( 'AD_PRES' , 'President' , 20080 , 40000 ); INSERT INTO jobs VALUES ( 'AD_VP' , 'Administration Vice President' , 15000 , 30000 ); INSERT INTO jobs VALUES ( 'AD_ASST' , 'Administration Assistant' , 3000 , 6000 ); INSERT INTO jobs VALUES ( 'FI_MGR' , 'Finance Manager' , 8200 , 16000 ); INSERT INTO jobs VALUES ( 'FI_ACCOUNT' , 'Accountant' , 4200 , 9000 ); INSERT INTO jobs VALUES ( 'AC_MGR' , 'Accounting Manager' , 8200 , 16000 ); INSERT INTO jobs VALUES ( 'AC_ACCOUNT' , 'Public Accountant' , 4200 , 9000 ); INSERT INTO jobs VALUES ( 'SA_MAN' , 'Sales Manager' , 10000 , 20080 ); INSERT INTO jobs VALUES ( 'SA_REP' , 'Sales Representative' , 6000 , 12008 ); INSERT INTO jobs VALUES ( 'PU_MAN' , 'Purchasing Manager' , 8000 , 15000 ); INSERT INTO jobs VALUES ( 'PU_CLERK' , 'Purchasing Clerk' , 2500 , 5500 ); INSERT INTO jobs VALUES ( 'ST_MAN' , 'Stock Manager' , 5500 , 8500 ); INSERT INTO jobs VALUES ( 'ST_CLERK' , 'Stock Clerk' , 2008 , 5000 ); INSERT INTO jobs VALUES ( 'SH_CLERK' , 'Shipping Clerk' , 2500 , 5500 ); INSERT INTO jobs VALUES ( 'IT_PROG' , 'Programmer' , 4000 , 10000 ); INSERT INTO jobs VALUES ( 'MK_MAN' , 'Marketing Manager' , 9000 , 15000 ); INSERT INTO jobs VALUES ( 'MK_REP' , 'Marketing Representative' , 4000 , 9000 ); INSERT INTO jobs VALUES ( 'HR_REP' , 'Human Resources Representative' , 4000 , 9000 ); INSERT INTO jobs VALUES ( 'PR_REP' , 'Public Relations Representative' , 4500 , 10500 ); INSERT INTO employees VALUES ( 100 , 'Steven' , 'King' , 'SKING' , '515.123.4567' , DATE('2003-06-17') , 'AD_PRES' , 24000 , NULL , NULL , 90 ); INSERT INTO employees VALUES ( 101 , 'Neena' , 'Kochhar' , 'NKOCHHAR' , '515.123.4568' , DATE('2005-09-21') , 'AD_VP' , 17000 , NULL , 100 , 90 ); INSERT INTO employees VALUES ( 102 , 'Lex' , 'De Haan' , 'LDEHAAN' , '515.123.4569' , DATE('2001-01-13') , 'AD_VP' , 17000 , NULL , 100 , 90 ); INSERT INTO employees VALUES ( 103 , 'Alexander' , 'Hunold' , 'AHUNOLD' , '590.423.4567' , DATE('2006-01-03') , 'IT_PROG' , 9000 , NULL , 102 , 60 ); INSERT INTO employees VALUES ( 104 , 'Bruce' , 'Ernst' , 'BERNST' , '590.423.4568' , DATE('2007-05-21') , 'IT_PROG' , 6000 , NULL , 103 , 60 ); INSERT INTO employees VALUES ( 105 , 'David' , 'Austin' , 'DAUSTIN' , '590.423.4569' , DATE('2005-06-25') , 'IT_PROG' , 4800 , NULL , 103 , 60 ); INSERT INTO employees VALUES ( 106 , 'Valli' , 'Pataballa' , 'VPATABAL' , '590.423.4560' , DATE('2006-02-05') , 'IT_PROG' , 4800 , NULL , 103 , 60 ); INSERT INTO employees VALUES ( 107 , 'Diana' , 'Lorentz' , 'DLORENTZ' , '590.423.5567' , DATE('2007-02-07') , 'IT_PROG' , 4200 , NULL , 103 , 60 ); INSERT INTO employees VALUES ( 108 , 'Nancy' , 'Greenberg' , 'NGREENBE' , '515.124.4569' , DATE('2002-08-17') , 'FI_MGR' , 12008 , NULL , 101 , 100 ); INSERT INTO employees VALUES ( 109 , 'Daniel' , 'Faviet' , 'DFAVIET' , '515.124.4169' , DATE('2002-08-16') , 'FI_ACCOUNT' , 9000 , NULL , 108 , 100 ); INSERT INTO employees VALUES ( 110 , 'John' , 'Chen' , 'JCHEN' , '515.124.4269' , DATE('2005-09-28') , 'FI_ACCOUNT' , 8200 , NULL , 108 , 100 ); INSERT INTO employees VALUES ( 111 , 'Ismael' , 'Sciarra' , 'ISCIARRA' , '515.124.4369' , DATE('2005-09-30') , 'FI_ACCOUNT' , 7700 , NULL , 108 , 100 ); INSERT INTO employees VALUES ( 112 , 'Jose Manuel' , 'Urman' , 'JMURMAN' , '515.124.4469' , DATE('2006-03-07') , 'FI_ACCOUNT' , 7800 , NULL , 108 , 100 ); INSERT INTO employees VALUES ( 113 , 'Luis' , 'Popp' , 'LPOPP' , '515.124.4567' , DATE('2007-12-07') , 'FI_ACCOUNT' , 6900 , NULL , 108 , 100 ); INSERT INTO employees VALUES ( 114 , 'Den' , 'Raphaely' , 'DRAPHEAL' , '515.127.4561' , DATE('2002-12-07') , 'PU_MAN' , 11000 , NULL , 100 , 30 ); INSERT INTO employees VALUES ( 115 , 'Alexander' , 'Khoo' , 'AKHOO' , '515.127.4562' , DATE('2003-05-18') , 'PU_CLERK' , 3100 , NULL , 114 , 30 ); INSERT INTO employees VALUES ( 116 , 'Shelli' , 'Baida' , 'SBAIDA' , '515.127.4563' , DATE('2005-12-24') , 'PU_CLERK' , 2900 , NULL , 114 , 30 ); INSERT INTO employees VALUES ( 117 , 'Sigal' , 'Tobias' , 'STOBIAS' , '515.127.4564' , DATE('2005-07-24') , 'PU_CLERK' , 2800 , NULL , 114 , 30 ); INSERT INTO employees VALUES ( 118 , 'Guy' , 'Himuro' , 'GHIMURO' , '515.127.4565' , DATE('2006-11-15') , 'PU_CLERK' , 2600 , NULL , 114 , 30 ); INSERT INTO employees VALUES ( 119 , 'Karen' , 'Colmenares' , 'KCOLMENA' , '515.127.4566' , DATE('2007-08-10') , 'PU_CLERK' , 2500 , NULL , 114 , 30 ); INSERT INTO employees VALUES ( 120 , 'Matthew' , 'Weiss' , 'MWEISS' , '650.123.1234' , DATE('2004-07-18') , 'ST_MAN' , 8000 , NULL , 100 , 50 ); INSERT INTO employees VALUES ( 121 , 'Adam' , 'Fripp' , 'AFRIPP' , '650.123.2234' , DATE('2005-04-10') , 'ST_MAN' , 8200 , NULL , 100 , 50 ); INSERT INTO employees VALUES ( 122 , 'Payam' , 'Kaufling' , 'PKAUFLIN' , '650.123.3234' , DATE('2003-05-01') , 'ST_MAN' , 7900 , NULL , 100 , 50 ); INSERT INTO employees VALUES ( 123 , 'Shanta' , 'Vollman' , 'SVOLLMAN' , '650.123.4234' , DATE('2005-10-10') , 'ST_MAN' , 6500 , NULL , 100 , 50 ); INSERT INTO employees VALUES ( 124 , 'Kevin' , 'Mourgos' , 'KMOURGOS' , '650.123.5234' , DATE('2007-11-16') , 'ST_MAN' , 5800 , NULL , 100 , 50 ); INSERT INTO employees VALUES ( 125 , 'Julia' , 'Nayer' , 'JNAYER' , '650.124.1214' , DATE('2005-07-16') , 'ST_CLERK' , 3200 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 126 , 'Irene' , 'Mikkilineni' , 'IMIKKILI' , '650.124.1224' , DATE('2006-09-28') , 'ST_CLERK' , 2700 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 127 , 'James' , 'Landry' , 'JLANDRY' , '650.124.1334' , DATE('2007-01-14') , 'ST_CLERK' , 2400 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 128 , 'Steven' , 'Markle' , 'SMARKLE' , '650.124.1434' , DATE('2008-03-08') , 'ST_CLERK' , 2200 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 129 , 'Laura' , 'Bissot' , 'LBISSOT' , '650.124.5234' , DATE('2005-08-20') , 'ST_CLERK' , 3300 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 130 , 'Mozhe' , 'Atkinson' , 'MATKINSO' , '650.124.6234' , DATE('2005-10-30') , 'ST_CLERK' , 2800 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 131 , 'James' , 'Marlow' , 'JAMRLOW' , '650.124.7234' , DATE('2005-02-16') , 'ST_CLERK' , 2500 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 132 , 'TJ' , 'Olson' , 'TJOLSON' , '650.124.8234' , DATE('2007-04-10') , 'ST_CLERK' , 2100 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 133 , 'Jason' , 'Mallin' , 'JMALLIN' , '650.127.1934' , DATE('2004-06-14') , 'ST_CLERK' , 3300 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 134 , 'Michael' , 'Rogers' , 'MROGERS' , '650.127.1834' , DATE('2006-08-26') , 'ST_CLERK' , 2900 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 135 , 'Ki' , 'Gee' , 'KGEE' , '650.127.1734' , DATE('2007-12-12') , 'ST_CLERK' , 2400 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 136 , 'Hazel' , 'Philtanker' , 'HPHILTAN' , '650.127.1634' , DATE('2008-02-06') , 'ST_CLERK' , 2200 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 137 , 'Renske' , 'Ladwig' , 'RLADWIG' , '650.121.1234' , DATE('2003-07-14') , 'ST_CLERK' , 3600 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 138 , 'Stephen' , 'Stiles' , 'SSTILES' , '650.121.2034' , DATE('2005-10-26') , 'ST_CLERK' , 3200 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 139 , 'John' , 'Seo' , 'JSEO' , '650.121.2019' , DATE('2006-02-12') , 'ST_CLERK' , 2700 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 140 , 'Joshua' , 'Patel' , 'JPATEL' , '650.121.1834' , DATE('2006-04-06') , 'ST_CLERK' , 2500 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 141 , 'Trenna' , 'Rajs' , 'TRAJS' , '650.121.8009' , DATE('2003-10-17') , 'ST_CLERK' , 3500 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 142 , 'Curtis' , 'Davies' , 'CDAVIES' , '650.121.2994' , DATE('2005-01-29') , 'ST_CLERK' , 3100 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 143 , 'Randall' , 'Matos' , 'RMATOS' , '650.121.2874' , DATE('2006-03-15') , 'ST_CLERK' , 2600 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 144 , 'Peter' , 'Vargas' , 'PVARGAS' , '650.121.2004' , DATE('2006-07-09') , 'ST_CLERK' , 2500 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 145 , 'John' , 'Russell' , 'JRUSSEL' , '011.44.1344.429268' , DATE('2004-10-01') , 'SA_MAN' , 14000 , .4 , 100 , 80 ); INSERT INTO employees VALUES ( 146 , 'Karen' , 'Partners' , 'KPARTNER' , '011.44.1344.467268' , DATE('2005-01-05') , 'SA_MAN' , 13500 , .3 , 100 , 80 ); INSERT INTO employees VALUES ( 147 , 'Alberto' , 'Errazuriz' , 'AERRAZUR' , '011.44.1344.429278' , DATE('2005-03-10') , 'SA_MAN' , 12000 , .3 , 100 , 80 ); INSERT INTO employees VALUES ( 148 , 'Gerald' , 'Cambrault' , 'GCAMBRAU' , '011.44.1344.619268' , DATE('2007-10-15') , 'SA_MAN' , 11000 , .3 , 100 , 80 ); INSERT INTO employees VALUES ( 149 , 'Eleni' , 'Zlotkey' , 'EZLOTKEY' , '011.44.1344.429018' , DATE('2008-01-29') , 'SA_MAN' , 10500 , .2 , 100 , 80 ); INSERT INTO employees VALUES ( 150 , 'Peter' , 'Tucker' , 'PTUCKER' , '011.44.1344.129268' , DATE('2005-01-30') , 'SA_REP' , 10000 , .3 , 145 , 80 ); INSERT INTO employees VALUES ( 151 , 'David' , 'Bernstein' , 'DBERNSTE' , '011.44.1344.345268' , DATE('2005-03-24') , 'SA_REP' , 9500 , .25 , 145 , 80 ); INSERT INTO employees VALUES ( 152 , 'Peter' , 'Hall' , 'PHALL' , '011.44.1344.478968' , DATE('2005-08-20') , 'SA_REP' , 9000 , .25 , 145 , 80 ); INSERT INTO employees VALUES ( 153 , 'Christopher' , 'Olsen' , 'COLSEN' , '011.44.1344.498718' , DATE('2006-03-30') , 'SA_REP' , 8000 , .2 , 145 , 80 ); INSERT INTO employees VALUES ( 154 , 'Nanette' , 'Cambrault' , 'NCAMBRAU' , '011.44.1344.987668' , DATE('2006-12-09') , 'SA_REP' , 7500 , .2 , 145 , 80 ); INSERT INTO employees VALUES ( 155 , 'Oliver' , 'Tuvault' , 'OTUVAULT' , '011.44.1344.486508' , DATE('2007-11-23') , 'SA_REP' , 7000 , .15 , 145 , 80 ); INSERT INTO employees VALUES ( 156 , 'Janette' , 'King' , 'JKING' , '011.44.1345.429268' , DATE('2004-01-30') , 'SA_REP' , 10000 , .35 , 146 , 80 ); INSERT INTO employees VALUES ( 157 , 'Patrick' , 'Sully' , 'PSULLY' , '011.44.1345.929268' , DATE('2004-03-04') , 'SA_REP' , 9500 , .35 , 146 , 80 ); INSERT INTO employees VALUES ( 158 , 'Allan' , 'McEwen' , 'AMCEWEN' , '011.44.1345.829268' , DATE('2004-08-01') , 'SA_REP' , 9000 , .35 , 146 , 80 ); INSERT INTO employees VALUES ( 159 , 'Lindsey' , 'Smith' , 'LSMITH' , '011.44.1345.729268' , DATE('2005-03-10') , 'SA_REP' , 8000 , .3 , 146 , 80 ); INSERT INTO employees VALUES ( 160 , 'Louise' , 'Doran' , 'LDORAN' , '011.44.1345.629268' , DATE('2005-12-15') , 'SA_REP' , 7500 , .3 , 146 , 80 ); INSERT INTO employees VALUES ( 161 , 'Sarath' , 'Sewall' , 'SSEWALL' , '011.44.1345.529268' , DATE('2006-11-03') , 'SA_REP' , 7000 , .25 , 146 , 80 ); INSERT INTO employees VALUES ( 162 , 'Clara' , 'Vishney' , 'CVISHNEY' , '011.44.1346.129268' , DATE('2005-11-11') , 'SA_REP' , 10500 , .25 , 147 , 80 ); INSERT INTO employees VALUES ( 163 , 'Danielle' , 'Greene' , 'DGREENE' , '011.44.1346.229268' , DATE('2007-03-19') , 'SA_REP' , 9500 , .15 , 147 , 80 ); INSERT INTO employees VALUES ( 164 , 'Mattea' , 'Marvins' , 'MMARVINS' , '011.44.1346.329268' , DATE('2008-01-24') , 'SA_REP' , 7200 , .10 , 147 , 80 ); INSERT INTO employees VALUES ( 165 , 'David' , 'Lee' , 'DLEE' , '011.44.1346.529268' , DATE('2008-02-23') , 'SA_REP' , 6800 , .1 , 147 , 80 ); INSERT INTO employees VALUES ( 166 , 'Sundar' , 'Ande' , 'SANDE' , '011.44.1346.629268' , DATE('2008-03-24') , 'SA_REP' , 6400 , .10 , 147 , 80 ); INSERT INTO employees VALUES ( 167 , 'Amit' , 'Banda' , 'ABANDA' , '011.44.1346.729268' , DATE('2008-04-21') , 'SA_REP' , 6200 , .10 , 147 , 80 ); INSERT INTO employees VALUES ( 168 , 'Lisa' , 'Ozer' , 'LOZER' , '011.44.1343.929268' , DATE('2005-03-11') , 'SA_REP' , 11500 , .25 , 148 , 80 ); INSERT INTO employees VALUES ( 169 , 'Harrison' , 'Bloom' , 'HBLOOM' , '011.44.1343.829268' , DATE('2006-03-23') , 'SA_REP' , 10000 , .20 , 148 , 80 ); INSERT INTO employees VALUES ( 170 , 'Tayler' , 'Fox' , 'TFOX' , '011.44.1343.729268' , DATE('2006-01-24') , 'SA_REP' , 9600 , .20 , 148 , 80 ); INSERT INTO employees VALUES ( 171 , 'William' , 'Smith' , 'WSMITH' , '011.44.1343.629268' , DATE('2007-02-23') , 'SA_REP' , 7400 , .15 , 148 , 80 ); INSERT INTO employees VALUES ( 172 , 'Elizabeth' , 'Bates' , 'EBATES' , '011.44.1343.529268' , DATE('2007-03-24') , 'SA_REP' , 7300 , .15 , 148 , 80 ); INSERT INTO employees VALUES ( 173 , 'Sundita' , 'Kumar' , 'SKUMAR' , '011.44.1343.329268' , DATE('2008-04-21') , 'SA_REP' , 6100 , .10 , 148 , 80 ); INSERT INTO employees VALUES ( 174 , 'Ellen' , 'Abel' , 'EABEL' , '011.44.1644.429267' , DATE('2004-05-11') , 'SA_REP' , 11000 , .30 , 149 , 80 ); INSERT INTO employees VALUES ( 175 , 'Alyssa' , 'Hutton' , 'AHUTTON' , '011.44.1644.429266' , DATE('2005-03-19') , 'SA_REP' , 8800 , .25 , 149 , 80 ); INSERT INTO employees VALUES ( 176 , 'Jonathon' , 'Taylor' , 'JTAYLOR' , '011.44.1644.429265' , DATE('2006-03-24') , 'SA_REP' , 8600 , .20 , 149 , 80 ); INSERT INTO employees VALUES ( 177 , 'Jack' , 'Livingston' , 'JLIVINGS' , '011.44.1644.429264' , DATE('2006-04-23') , 'SA_REP' , 8400 , .20 , 149 , 80 ); INSERT INTO employees VALUES ( 178 , 'Kimberely' , 'Grant' , 'KGRANT' , '011.44.1644.429263' , DATE('2007-05-24') , 'SA_REP' , 7000 , .15 , 149 , NULL ); INSERT INTO employees VALUES ( 179 , 'Charles' , 'Johnson' , 'CJOHNSON' , '011.44.1644.429262' , DATE('2008-01-04') , 'SA_REP' , 6200 , .10 , 149 , 80 ); INSERT INTO employees VALUES ( 180 , 'Winston' , 'Taylor' , 'WTAYLOR' , '650.507.9876' , DATE('2006-01-24') , 'SH_CLERK' , 3200 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 181 , 'Jean' , 'Fleaur' , 'JFLEAUR' , '650.507.9877' , DATE('2006-02-23') , 'SH_CLERK' , 3100 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 182 , 'Martha' , 'Sullivan' , 'MSULLIVA' , '650.507.9878' , DATE('2007-06-21') , 'SH_CLERK' , 2500 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 183 , 'Girard' , 'Geoni' , 'GGEONI' , '650.507.9879' , DATE('2008-02-03') , 'SH_CLERK' , 2800 , NULL , 120 , 50 ); INSERT INTO employees VALUES ( 184 , 'Nandita' , 'Sarchand' , 'NSARCHAN' , '650.509.1876' , DATE('2004-01-27') , 'SH_CLERK' , 4200 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 185 , 'Alexis' , 'Bull' , 'ABULL' , '650.509.2876' , DATE('2005-02-20') , 'SH_CLERK' , 4100 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 186 , 'Julia' , 'Dellinger' , 'JDELLING' , '650.509.3876' , DATE('2006-06-24') , 'SH_CLERK' , 3400 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 187 , 'Anthony' , 'Cabrio' , 'ACABRIO' , '650.509.4876' , DATE('2007-02-07') , 'SH_CLERK' , 3000 , NULL , 121 , 50 ); INSERT INTO employees VALUES ( 188 , 'Kelly' , 'Chung' , 'KCHUNG' , '650.505.1876' , DATE('2005-06-14') , 'SH_CLERK' , 3800 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 189 , 'Jennifer' , 'Dilly' , 'JDILLY' , '650.505.2876' , DATE('2005-08-13') , 'SH_CLERK' , 3600 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 190 , 'Timothy' , 'Gates' , 'TGATES' , '650.505.3876' , DATE('2006-07-11') , 'SH_CLERK' , 2900 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 191 , 'Randall' , 'Perkins' , 'RPERKINS' , '650.505.4876' , DATE('2007-12-19') , 'SH_CLERK' , 2500 , NULL , 122 , 50 ); INSERT INTO employees VALUES ( 192 , 'Sarah' , 'Bell' , 'SBELL' , '650.501.1876' , DATE('2004-02-04') , 'SH_CLERK' , 4000 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 193 , 'Britney' , 'Everett' , 'BEVERETT' , '650.501.2876' , DATE('2005-03-03') , 'SH_CLERK' , 3900 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 194 , 'Samuel' , 'McCain' , 'SMCCAIN' , '650.501.3876' , DATE('2006-07-01') , 'SH_CLERK' , 3200 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 195 , 'Vance' , 'Jones' , 'VJONES' , '650.501.4876' , DATE('2007-03-17') , 'SH_CLERK' , 2800 , NULL , 123 , 50 ); INSERT INTO employees VALUES ( 196 , 'Alana' , 'Walsh' , 'AWALSH' , '650.507.9811' , DATE('2006-04-24') , 'SH_CLERK' , 3100 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 197 , 'Kevin' , 'Feeney' , 'KFEENEY' , '650.507.9822' , DATE('2006-05-23') , 'SH_CLERK' , 3000 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 198 , 'Donald' , 'OConnell' , 'DOCONNEL' , '650.507.9833' , DATE('2007-06-21') , 'SH_CLERK' , 2600 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 199 , 'Douglas' , 'Grant' , 'DGRANT' , '650.507.9844' , DATE('2008-01-13') , 'SH_CLERK' , 2600 , NULL , 124 , 50 ); INSERT INTO employees VALUES ( 200 , 'Jennifer' , 'Whalen' , 'JWHALEN' , '515.123.4444' , DATE('2003-09-17') , 'AD_ASST' , 4400 , NULL , 101 , 10 ); INSERT INTO employees VALUES ( 201 , 'Michael' , 'Hartstein' , 'MHARTSTE' , '515.123.5555' , DATE('2004-02-17') , 'MK_MAN' , 13000 , NULL , 100 , 20 ); INSERT INTO employees VALUES ( 202 , 'Pat' , 'Fay' , 'PFAY' , '603.123.6666' , DATE('2005-08-17') , 'MK_REP' , 6000 , NULL , 201 , 20 ); INSERT INTO employees VALUES ( 203 , 'Susan' , 'Mavris' , 'SMAVRIS' , '515.123.7777' , DATE('2002-06-07') , 'HR_REP' , 6500 , NULL , 101 , 40 ); INSERT INTO employees VALUES ( 204 , 'Hermann' , 'Baer' , 'HBAER' , '515.123.8888' , DATE('2002-06-07') , 'PR_REP' , 10000 , NULL , 101 , 70 ); INSERT INTO employees VALUES ( 205 , 'Shelley' , 'Higgins' , 'SHIGGINS' , '515.123.8080' , DATE('2002-06-07') , 'AC_MGR' , 12008 , NULL , 101 , 110 ); INSERT INTO employees VALUES ( 206 , 'William' , 'Gietz' , 'WGIETZ' , '515.123.8181' , DATE('2002-06-07') , 'AC_ACCOUNT' , 8300 , NULL , 205 , 110 );
NULL 即是空
在数据库中,空值(NULL)是一个特殊的值,通常用于表示缺失值或者不适用的值。比如,填写问卷时不愿意透露某些信息会导致录入项的缺失,在公司的组织结构中总会有一个人(董事长/总经理)没有上级领导。
首先一点,空值与数字 0 并不相同。假如我问你:你的钱包里有多少钱?如果你知道里面没有钱,可以说是零;如果你不确定,那么就是未知,但不能说没有。当我们需要创建一个表来存储这个信息的时候,应该是 NULL;除非我们能够确定钱包里面没有钱或者有多少钱。
另外,空值与空字符串('')也不相同,原因和上面类似。但是 Oracle 是一个例外,我们会在下文具体讨论。
在大多数编程语言中,访问 null 值通常会导致错误;但是 SQL 不会出错,只是会影响到运算的结果而已。
三值逻辑
在大多数编程语言中,逻辑运算的结果只有两种情况,不是真(True)就是假(False)。但是对于 SQL 而言,逻辑运算还可能是未知(Unknown):
引入三值逻辑主要是为了支持 NULL,因为 NULL 代表的是未知数据。因此,SQL 中的逻辑运算与(AND)、或(OR)以及非(NOT)的结果如下:
对于 AND 运算符而言,真和未知的与运算有可能是真,也有可能是假;因此,最终的结果是未知。
SQL 中的 WHERE、HAVING 以及 CASE WHEN 子句只返回逻辑运算结果为真的数据,不返回结果为假或未知的数据。
空值比较
当我们使用比较运算符(=、<>、<、> 等)与 NULL 进行比较时,结果既不是真也不是假,而是未知;因为 NULL 表示未知,也就意味着可能是任何值。以下运算的结果都是未知:
NULL = 0 NULL <> 0 NULL <= 0 NULL = NULL NULL != NULL
NULL 与任何值都不相等,甚至两个 NULL 也不想等;因为我们不能说两个未知的值相同,也不能说它们不相同。
对于比较运算而言,NULL 和 NULL 不相同;但是某些 SQL 子句中的 NULL 值被看作相同的值,例如 GROUP BY。具体参考下文。
那么,如何判断一个值是否是 NULL 呢?为此,SQL 引入了两个谓词(WHERE 子句):IS NULL和IS NOT NULL。以下示例用于查找 manager 为空的员工:
-- 使用比较运算符判断空值 SELECT employee_id, first_name, last_name, manager_id FROM employees WHERE manager_id = NULL; employee_id|first_name|last_name|manager_id| -----------|----------|---------|----------| -- 使用 IS NULL 判断空值 SELECT employee_id, first_name, last_name, manager_id FROM employees WHERE manager_id IS NULL; employee_id|first_name|last_name|manager_id| -----------|----------|---------|----------| 100|Steven |King | |
其中,第一个查询使用比较运算符判断空值,不会返回任何结果;第二个查询使用 IS NULL 判断空值,返回了正确的结果。
除了标准的IS [NOT] NULL之外,还有一些数据库扩展的运算符可以用于空值比较:
-- MySQL SELECT employee_id, first_name, last_name, manager_id FROM employees WHERE manager_id <=> NULL; employee_id|first_name|last_name|manager_id| -----------|----------|---------|----------| 100|Steven |King | |
MySQL 中的<=>可以用于等值比较,支持两个 NULL 值。
以下查询的结果也不会返回任何结果:
SELECT employee_id, first_name, last_name, manager_id FROM employees WHERE (1 = NULL) OR (1 != NULL);
因为根据上面的三值逻辑,两个未知结果的 OR 运算最终还是未知。
前文我们说过,空字符串不是 NULL;但是 Oracle 中的空字符串被看作 NULL。例如:
-- Oracle SELECT 1 FROM dual WHERE '' IS NULL; VAL| ---| 1| -- 其他数据库 SELECT 1 AS val WHERE '' IS NULL; val| ---|
当然,我们如果使用等值(=)运算符判断空字符串与 NULL,结果仍然为空。
NOT IN 与空值
对于 WHERE 条件中的 IN 和 NOT IN 运算符,使用的是等值比较。所以如果 NOT IN 碰到了 NULL 值,永远不会返回任何结果。例如:
SELECT employee_id, first_name, last_name, manager_id FROM employees WHERE 1 NOT IN (NULL, 2);
因为上面的条件实际上等价于:
SELECT employee_id, first_name, last_name, manager_id FROM employees WHERE 1 != NULL AND 1 != 2;
1 不等于 NULL 的结果是未知,1 不等于 2 的结果是真,未知和真的 AND 运算结果还是未知。
如果使用 NOT IN,一定要确保括号中的值不会出现 NULL;或者尽量使用 NOT EXISTS。
函数与空值
一般来说,函数和表达式的参数中如果存在 NULL,其结果也是 NULL。当然也有一些例外,比如聚合函数。
以下查询返回的都是 NULL:
SELECT ABS(NULL), 1 + NULL FROM employees WHERE employee_id = 100; ABS(NULL)|1 + NULL| ---------|--------| [NULL]| [NULL]|
一个未知值的绝对值仍然未知,1 加上一个未知值结果还是未知。
但是一个常见的例外是字符串与 NULL 的连接:
-- Oracle SELECT CONCAT('Hello', NULL) FROM employees WHERE employee_id = 100; CONCAT('HELLO',NULL)| --------------------| Hello | -- MySQL SELECT CONCAT('Hello', NULL) FROM employees WHERE employee_id = 100; CONCAT('Hello', NULL)| ---------------------| [NULL]|
Oracle 将 NULL 看作空字符串,所以查询结果为“Hello”;MySQL 中 NULL 参数导致 CONCAT 函数结果为 NULL;SQLite 没有提供 CONCAT 函数。
另外,Oracle 中的 || 也将 NULL 看作空字符串;其他数据库 || 中的 NULL 将参数会产生 NULL 结果。
聚合函数(SUM、COUNT、AVG 等)通常会在进行计算之前删除 NULL 数据:
SELECT SUM(salary + commission_pct) sum1, SUM(salary) + SUM(commission_pct) sum2, COUNT(salary), COUNT(commission_pct) FROM employees; SUM1 |SUM2 |COUNT(SALARY)|COUNT(COMMISSION_PCT)| --------|--------|-------------|---------------------| 311507.8|691423.8| 107| 35|
第一个 SUM 函数返回的是 salary 和 commission_pct 都不为空的数据总和;第而个 SUM 函数返回的是 salary 不为空的数据总和加上 commission_pct 不为空的数据总和,所以比第一个数据大;COUNT 函数结果显示 salary 有 107 条记录不为空,commission_pct 只有 35 条记录不为空。
如果输入数据都是 NULL 值,除了 COUNT 函数之外的其他聚合函数返回 NULL:
SELECT COUNT(*), COUNT(commission_pct), AVG(commission_pct), SUM(commission_pct) FROM employees WHERE commission_pct IS NULL; COUNT(*)|COUNT(COMMISSION_PCT)|AVG(COMMISSION_PCT)|SUM(COMMISSION_PCT)| --------|---------------------|-------------------|-------------------| 72| 0| [NULL]| [NULL]|
COUNT(*) 总是返回数据的行数,不受空值的影响;COUNT(commission_pct) 返回了零;AVG 和 SUM 返回了 NULL。
DISTINCT、GROUP BY、UNION 与空值
SQL 中的分组操作将所有的 NULL 值分到同一个组,包括 DISTINCT、GROUP BY 以及窗口函数中的 PARTITION BY。例如:
SELECT DISTINCT commission_pct FROM employees; commission_pct| --------------| [NULL]| 0.40| 0.30| 0.20| 0.25| 0.15| 0.35| 0.10| SELECT commission_pct FROM employees GROUP BY commission_pct; commission_pct| --------------| [NULL]| 0.40| 0.30| 0.20| 0.25| 0.15| 0.35| 0.10|
从上面的示例可以看出,commission_pct 为空的数据有 72 条,但是分组之后只有一个 NULL 组。
除此之外,UNION 操作符也将所有的 NULL 看作相同值:
SELECT manager_id FROM employees WHERE manager_id IS NULL UNION SELECT manager_id FROM employees WHERE manager_id IS NULL; manager_id| ----------| [NULL]|
如果将 UNION 换成 UNION ALL,查询结果将会保留 2 个 NULL 值。
ORDER BY 与空值
SQL 标准没有定义 NULL 值的排序顺序,但是为 ORDER BY 定义了 NULLS FIRST 和 NULLS LAST 选项,用于明确指定空值排在其他数据之前或者之后。
不同数据库对此提供了不同的实现:
SELECT employee_id, manager_id FROM employees WHERE employee_id IN (100, 101, 102) ORDER BY manager_id; -- Oracle EMPLOYEE_ID|MANAGER_ID| -----------|----------| 101| 100| 102| 100| 100| [NULL]| -- MySQL employee_id|manager_id| -----------|----------| 100| [NULL]| 101| 100| 102| 100|
其中,Oracle 默认将 NULL 作为最大值,升序时排在最后;MySQL默认将 NULL 作为最小值,升序时排在最前。
另外,Oracle提供了扩展的 NULLS FIRST 和 NULLS LAST 选项:
-- Oracle SELECT employee_id, manager_id FROM employees WHERE employee_id IN (100, 101, 102) ORDER BY manager_id NULLS FIRST; employee_id|manager_id| -----------|----------| 100| [NULL]| 101| 100| 102| 100|
我们也可以使用 CASE 表达式实现类似的效果。以下示例与 NULLS LAST 作用相同,而且所有数据库都可以使用:
SELECT employee_id, manager_id FROM employees WHERE employee_id IN (100, 101, 102) ORDER BY CASE WHEN manager_id IS NULL THEN 1 ELSE 0 END, manager_id; employee_id|manager_id| -----------|----------| 101| 100| 102| 100| 100| [NULL]|
首先,CASE 表达式将 manager_id 为空的数据转换为 1,非空的数据转换为 0,所以空值排在其他数据之后;第二个排序字段 manager_id 确保了非空的数据从小到大排序。
空值处理函数
由于空值的特殊性,我们在分析数据时经常需要进行空值和其他值的转换。为此,SQL 提供了两个标准的空值函数:COALESCE 和 NULLIF。
COALESCE(exp1, exp2, …) 函数用于将 NULL 转换为其他值。当 exp1 不为空时返回 exp1,否则检查 exp2;如果 exp2 不为空时返回 exp2,依次类推。例如:
SELECT COALESCE(NULL, NULL, 3) FROM employees WHERE employee_id = 100; COALESCE(NULL, NULL, 3)| -----------------------| 3|
由于前面两个参数都是 NULL,COALESCE 最终返回了 3。
COALESCE 函数也可以使用 CASE 表达式改写如下:
CASE WHEN exp1 IS NOT NULL THEN exp1 WHEN exp2 IS NOT NULL THEN exp2 ... ELSE expN END
NULLIF(exp1, exp2) 函数用于将指定值转换为 NULL。当 exp1 等于 exp2 时,返回 NULL;否则,返回 exp1 。NULLIF 最常见的用途是防止除零错误,例如:
SELECT 1 / NULLIF(0, 0) -- 1 / 0 FROM employees WHERE employee_id = 100;
示例中的 NULLIF 将第一个零转换为 NULL,因此查询结果返回 NULL;如果直接使用 1 / 0,查询将会返回除零错误。MySQL 中的除零错误由 sql_mode 变量控制。
NULLIF 函数同样可以使用 CASE 表达式改写如下:
CASE WHEN exp1 = exp2 THEN NULL ELSE exp1 END
利用 CASE 表达式,我们还可以轻松实现多个值到 NULL 的转换:
CASE WHEN expr IN (value1, value2, …) THEN NULL ELSE expr END
COALESCE 和 NULLIF 实际上是 CASE 表达式的两种缩写形式。
除了标准 SQL 函数之外,数据库还提供了一些专用的函数:
- Oracle 中的 NVL(expr1, expr2) 相当于 2 个参数的 COALESCE。另外,NVL2(expr1, expr2, expr3) 如果第一个参数不为空,返回第二个参数的值;否则,返回第三个参数的值;
- MySQL 中的 IFNULL(expr1, expr2) 相当于 2 个参数的 COALESCE。另外,IF(expr1, expr2, expr3) 如果第一个参数为真(expr1 <> 0 并且 expr1 不为空),返回第二个参数的值;否则,返回第三个参数的值;
字段约束与空值
如果不允许字段中存在未知或者缺失的数据,可以使用字段的 NOT NULL 约束。
对于唯一约束(UNIQUE),多个 NULL 被看作是不同的值;因此,唯一约束字段中可以存在多个空值。不过 SQL Server 是个例外:
CREATE TABLE t_unique(id INT UNIQUE); INSERT INTO t_unique VALUES(1); INSERT INTO t_unique VALUES(NULL); INSERT INTO t_unique VALUES(NULL); SELECT * FROM t_unique; id| ------| [NULL]| [NULL]| 1| SELECT * FROM t_unique; id| ------| [NULL]|
如果是复合索引,情况略有不同:
CREATE TABLE t_unique2(c1 INT, c2 INT, UNIQUE(c1,c2)); INSERT INTO t_unique2 VALUES(1, 1); INSERT INTO t_unique2 VALUES(NULL, NULL); INSERT INTO t_unique2 VALUES(NULL, NULL); -- SQL Server 产生唯一键冲突错误 INSERT INTO t_unique2 VALUES(1, NULL); INSERT INTO t_unique2 VALUES(1, NULL); -- Oracle 和 SQL Server 产生唯一键冲突错误
其中,SQL Server 只允许有一个记录的全部索引字段为空;如果某个字段不为空,Oracle 和 SQL Server 只允许有一个记录的其他索引字段为空。
另外,检查约束(CHECK)对于 NULL 的处理与 WHERE 条件正好相反:只要数据的检查结果不是假都可以插入成功。例如:
CREATE TABLE t_check ( c1 INT CHECK (c1 >= 0), c2 INT CHECK (c2 >= 0), CHECK (c1 + c2 <= 100) ); INSERT INTO t_check VALUES (5, 5); INSERT INTO t_check VALUES (NULL, NULL); INSERT INTO t_check VALUES (200, NULL); SELECT * FROM t_check; c1 |c2 | ------|------| 5| 5| [NULL]|[NULL]| 200|[NULL]|
如果 c1 和 c2 都有值的话,都必须大于等于零并且和值小于等于 100;c1 和 c2 都可以为空;如果其中之一为空,另一个字段的值可以大于 100。