-
Notifications
You must be signed in to change notification settings - Fork 0
/
null.qmd
242 lines (164 loc) · 8.78 KB
/
null.qmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# 결측 데이터
데이터베이스가 어떻게 결측 정보를 표현하는지 살펴보고,
결측 정보를 다룰 때, 3개 값을 가진 로직(three-valued logic) 데이터베이스 사용하여
결측 정보를 올바르게 처리하는 쿼리를 작성한다.
현실 세계 데이터는 결코 완전하지 않고 구멍은 항상 있다.
`null`로 불리는 특별한 값을 사용하여 데이터베이스는 구멍을 표현한다.
`null`는 0, `False`, 혹은 빈 문자열도 아니다."아무것도 없음(nothing here)"을 의미하는 특별한 값이다.
`null`을 다루는 것은 약간 특별한 기교와 신중한 생각을 요구한다.
기본적으로 SQLite는 출력에서 NULL 값을 표시하지 않는다.
`.nullvalue` 명령은 SQLite가 NULL에 대해 지정한 값을 표시하도록 한다.
NULL을 더 쉽게 볼 수 있도록 `-null-` 값을 사용한다.
```sql
.nullvalue -null-
```
시작으로 `Visited` 테이블을 살펴보자. 혹은 더 정확히 말하면 날짜가 `null`이다.
시작으로 `Visited` 테이블을 살펴보자.
레코드가 8개 있지만 #752은 날짜가 없다.
즉, 그 날짜는 `null`이다:
```sql
SELECT * FROM Visited;
```
| id | site | dated |
| ----- | ------ | ---------- |
| 619 | DR-1 | 1927-02-08 |
| 622 | DR-1 | 1927-02-10 |
| 734 | DR-3 | 1930-01-07 |
| 735 | DR-3 | 1930-01-12 |
| 751 | DR-3 | 1930-02-26 |
| 752 | DR-3 | \-null- |
| 837 | MSK-4 | 1932-01-14 |
| 844 | DR-1 | 1932-03-22 |
`Null` 다른 값과는 다르게 동작한다.
만약 1930년 이전 레코드를 선택한다면,
```sql
SELECT * FROM Visited WHERE dated < '1930-01-01';
```
| id | site | dated |
| ----- | ------ | ---------- |
| 619 | DR-1 | 1927-02-08 |
| 622 | DR-1 | 1927-02-10 |
결과 2개를 얻게 되고, 만약 1930년 동안 혹은 이후 레코드를 선택한다면,
```sql
SELECT * FROM Visited WHERE dated >= '1930-01-01';
```
| id | site | dated |
| ----- | ------ | ---------- |
| 734 | DR-3 | 1930-01-07 |
| 735 | DR-3 | 1930-01-12 |
| 751 | DR-3 | 1930-02-26 |
| 837 | MSK-4 | 1932-01-14 |
| 844 | DR-1 | 1932-03-22 |
결과를 5개 얻게 되지만, 레코드 #752은 결과값 어디에도 존재하지 않는다.
이유는 `null<'1930-00-00'` 평가결과가 참도 거짓도 아니기 때문이다.
`null` 이 의미하는 것은 "알수가 없다"는 것이다.
그리고 만약 비교 평가식의 왼쪽편 값을 알지 못한다면, 비교도 참인지 거짓인지 알수가 없다.
데이터베이스는 "알 수 없음"을 `null`로 표현하기 때문에, `null<'1930-00-00'`의 값도 사실 `null`이다.
`null>='1930-00-00'`도 또한 `null`인데 왜냐하면 질문에 답을 할 수 없기 때문이다.
그리고, `WHERE`절에 레코드는 테스트가 참인 것만 있기 때문에 레코드 #752은 어느 결과값에도 포함되지 않게 된다.
비교 연산 뿐만 아니라 다른 연산들도 null과 함께 이러한 방식으로 동작한다.
`1+null`도 `null`이고,
`5*null`도 `null`이고,
`log(null)`도 `null`이 된다.
특히, 무언가를 `=` 과 `!=` 으로 `null`과 비교하는 것도 `null`이 된다.
```sql
SELECT * FROM Visited WHERE dated = NULL;
```
출력을 생성하지 않으며, 다음도 마찬가지로 출력을 생성하지 않는다.
```sql
SELECT * FROM Visited WHERE dated != NULL;
```
`null` 인지 아닌지를 점검하기 위해서, 특별한 테스트 `IS NULL`을 사용해야 한다.
| id | site | dated |
| ----- | ------ | ---------- |
| 752 | DR-3 | \-null- |
혹은, 역으로는 `IS NOT NULL`을 사용한다.
```sql
SELECT * FROM Visited WHERE dated IS NOT NULL;
```
| id | site | dated |
| ----- | ------ | ---------- |
| 619 | DR-1 | 1927-02-08 |
| 622 | DR-1 | 1927-02-10 |
| 734 | DR-3 | 1930-01-07 |
| 735 | DR-3 | 1930-01-12 |
| 751 | DR-3 | 1930-02-26 |
| 837 | MSK-4 | 1932-01-14 |
| 844 | DR-1 | 1932-03-22 |
`null` 값은 나타나는 곳마다 문제를 일으킬 수 있다.
예를 들어, Lake에 의해 측정되지 않은 모든 염도 측정을 찾고 싶다고 가정해보자.
쿼리를 다음과 같이 작성하는 것이 자연스럽다.
```sql
SELECT * FROM Survey WHERE quant = 'sal' AND person != 'lake';
```
| taken | person | quant | reading |
| ----- | ------ | ---------- | ------- |
| 619 | dyer | sal | 0\.13 |
| 622 | dyer | sal | 0\.09 |
| 752 | roe | sal | 41\.6 |
| 837 | roe | sal | 22\.5 |
하지만, 상기 쿼리 필터는 누가 측정을 했는지 모르는 레코드는 빠뜨린다.
다시 한번, 이유는 `person`이 `null`일 때, `!=`비교는 `null`값을 만들어서
레코드가 결과값에 있지 않게 된다. 만약 이런 레코드도 유지하려고 한다면,
명시적으로 검사를 추가할 필요가 있다.
```sql
SELECT * FROM Survey WHERE quant = 'sal' AND (person != 'lake' OR person IS NULL);
```
| taken | person | quant | reading |
| ----- | ------ | ---------- | ------- |
| 619 | dyer | sal | 0\.13 |
| 622 | dyer | sal | 0\.09 |
| 735 | \-null- | sal | 0\.06 |
| 752 | roe | sal | 41\.6 |
| 837 | roe | sal | 22\.5 |
여전히 이러한 접근법이 맞는 것인지 아닌 것인지 판단할 필요가 있다.
만약 절대적으로 결과에 Lake가 측정한 어떠한 값도 포함하지 않는다고 확신한다면,
누가 작업을 한 것인지 모르는 모든 레코드를 제외할 필요가 있다.
산술 또는 부울 연산자와 달리, `min`, `max` 또는 `avg`와 같은 여러 값을 결합하는 집계 함수는 `null` 값을 *무시한다*. 대부분의 경우, 이는 바람직한 결과이다.
예를 들어, 알려지지 않은 값들이 평균을 낼 때 `null`값은 데이터에 영향을 주지 않는다.
집계 함수는 다음 장에서 더 자세히 다룰 것이다.
## 연습문제
### 알려진 날짜별로 정렬
날짜가 알려지지 않은 (즉 `null`) 항목은 빼고, 날짜 순으로 `Visited` 테이블에 있는 레코드를 정렬한
```sql
SELECT * FROM Visited WHERE dated IS NOT NULL ORDER BY dated ASC;
```
| id | site | dated |
| ----- | ------ | ---------- |
| 619 | DR-1 | 1927-02-08 |
| 622 | DR-1 | 1927-02-10 |
| 734 | DR-3 | 1930-01-07 |
| 735 | DR-3 | 1930-01-12 |
| 751 | DR-3 | 1930-02-26 |
| 837 | MSK-4 | 1932-01-14 |
| 844 | DR-1 | 1932-03-22 |
### 집합에서 NULL
다음 쿼리가 어떤 결과를 생성할 것으로 예상하는가?
```sql
SELECT * FROM Visited WHERE dated IN ('1927-02-08', NULL);
```
실제로 어떤 결과를 생성하는가?
---
위 쿼리는 '1927-02-08' 또는 NULL인 'dated' 행을 반환할 것으로 예상할 수 있다.
하지만 실제로는 '1927-02-08'인 행만 반환한다.
이는 다음과 같은 더 간단한 쿼리에서 얻을 수 있는 결과와 동일하다:
```sql
SELECT * FROM Visited WHERE dated IN ('1927-02-08');
```
이런 결과가 나타나는 이유는 `IN` 연산자가 *값들*의 집합과 작동하는데,
NULL은 정의상 값이 아니며 따라서 단순히 무시되기 때문이다.
실제로 NULL을 포함시키고자 한다면,
쿼리를 다음과 같이 IS NULL 조건을 사용하여 다시 작성해야 한다:
```sql
SELECT * FROM Visited WHERE dated = '1927-02-08' OR dated IS NULL;
```
이 쿼리는 'dated'가 '1927-02-08'이거나 NULL인 행을 모두 반환한다.
### 표식값 장단점
일부 데이터베이스 설계자들은 `null` 대신 표식값(sentienl value)을 사용하여 누락된 데이터를 표시하는 것을 선호한다. 예를 들어, 누락된 날짜에 "0000-00-00"을 사용하거나, 염도나 방사능 측정치가 누락된 경우 -1.0을 사용한다(실제 측정치는 음수가 될 수 없으므로).
이러한 접근방법이 단순화시킨 것은 무엇인가?
어떤 부담이나 위험을 도입하는가?
---
표식값은 누락된 데이터의 존재를 명확히 표현한다.
`null`보다 직관적으로 이해될 수 있고, 특정 값으로 데이터를 표시하면 null 값을 다룰 때 발생할 수 있는 복잡성(예: IS NULL 조건)을 피할 수 있다.
표식값은 실제 데이터와 혼동될 위험이 있다.
예를 들어, "0000-00-00"이나 -1.0이 실제 측정치로 잘못 해석될 수 있다. 센티넬 값은 데이터 분석 시 추가적인 검증 단계를 필요로 한다. 예를 들어, 평균을 계산할 때 -1.0과 같은 표식값을 제외해야 한다. 센티넬 값의 사용은 데이터베이스 설계 및 유지 관리를 더 복잡하게 만들 수 있다. 모든 사용자 및 개발자가 표식값의 의미를 정확히 이해하고 있어야 한다.