https://github.com/hibuz/db-techniques
๐Next-Level Database Techniques For Developers
https://github.com/hibuz/db-techniques
Last synced: 4 months ago
JSON representation
๐Next-Level Database Techniques For Developers
- Host: GitHub
- URL: https://github.com/hibuz/db-techniques
- Owner: hibuz
- Created: 2022-12-07T13:12:28.000Z (almost 3 years ago)
- Default Branch: main
- Last Pushed: 2024-05-11T06:13:22.000Z (over 1 year ago)
- Last Synced: 2025-03-04T23:13:19.774Z (8 months ago)
- Homepage:
- Size: 127 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# ๐ Next-Level Database Techniques For Developers
> Learn secret and lesser-known SQL features and approaches to become a database wizard.## 1. Data Manipulation
์ผ๋ถ ์ ์ ์ธ ์ปจํ ์ธ ๋ง ์๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด๋ ์กด์ฌํ์ง๋ง ๋๋ถ๋ถ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ํ์๋ก ํฉ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค์ INSERT, UPDATE ๋๋ DELETE ์ฟผ๋ฆฌ ์ฌ์ฉ์ SQL์์ ๊ฐ์ฅ ๋ณต์กํ์ง ์์ ๊ธฐ๋ฅ์ธ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง๋ง, ์ฌ์ ํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ์ ํฌ์ธํธ๊ฐ ๋ ์ ์์ต๋๋ค. ๋์คํฌ๊ฐ 1์ด์ ์ํํ ์ ์๋ ์ฐ๊ธฐ ์์ ์ ์๋ ๋งค์ฐ ์ ํ์ ์ด๋ผ๋ ์ ์ ํญ์ ๊ธฐ์ตํ์ญ์์ค. ์ด๋น ์์ ์๋ฅผ ์ค์ผ ์ ์๋ค๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฑ๋ฅ์ ํจ์ฌ ํฅ์๋ฉ๋๋ค. 1์ฅ์์๋ ๋ค๋ฅธ ํ ์ด๋ธ์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ ์ ๋ฐ์ดํธํ๊ฑฐ๋ ์ค๋ณต ํ์ ์ญ์ ํ๊ฑฐ๋ ์ ๊ธ ๊ฒฝํฉ์ ์ ๊ฑฐํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ๋น ๋ฅด๊ฒ ๋ง๋๋ ๋ฐฉ๋ฒ์ ์๋ ค์ค๋๋ค. ๋ง์ง๋ง ํ์ ์ข ์ข ์ฑ๋ฅ ๋ฌธ์ ๋ผ๋ ๊ฒ์ ์์๊ธฐ ๋๋ฌธ์ ์์ธํ ํ์ตํด์ผ ํฉ๋๋ค.### 1.1 Prevent Lock Contention For Updates On Hot Rows
> ์ฆ์ ์ ๋ฐ์ดํธ๊ฐ ํ์ํ ํ์ ๋ํ ์ ๊ธ ๊ฒฝํฉ ๋ฐฉ์งํ๊ธฐ
```sql
-- MySQLINSERT INTO tweet_statistics (
tweet_id, fanout, likes_count
) VALUES (
1475870220422107137, FLOOR(RAND() * 10), 1
) ON DUPLICATE KEY UPDATE likes_count =
likes_count + VALUES(likes_count);-- PostgreSQL
INSERT INTO tweet_statistics (
tweet_id, fanout, likes_count
) VALUES (
1475870220422107137, FLOOR(RANDOM() * 10), 1
) ON CONFLICT (tweet_id, fanout) DO UPDATE SET likes_count =
tweet_statistics.likes_count + excluded.likes_count;
```
ํธ์ํฐ์ ํธ์ ์นด์ดํฐ๊ฐ ์ง์์ ์ธ ์ ๋ฐ์ดํธ๋ฅผ ํ์๋ก ํ ๋, ํธ๋ํฝ ๊ธ์ฆ ๋๋ ์ธ๊ธฐ ํธ์์ ๊ฒฝ์ฐ ์นด์ดํฐ๊ฐ 1์ด ์์ ์์์ด ์ ๋ฐ์ดํธ๋ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋์์ฑ ์ ์ด๋ก ์ธํด ํ ๋ฒ์ ํ๋์ ํธ๋์ญ์ (์ฟผ๋ฆฌ)๋ง ํ์ ์ ๊ธ ์ ์์ผ๋ฏ๋ก ์ ๋ฐ์ดํธ๊ฐ ์๋ก ๊ฐ์ญํ๊ธฐ ์์ํฉ๋๋ค.
๋ชจ๋ ์ ๋ฐ์ดํธ๋ ๋ ๋ฆฝ๋ ํ์ ๋ํด ๋ณ๋ ฌ ์คํ ๋์ ์ฐจ๋ก๋ก ์คํ๋ฉ๋๋ค. ๋จ์ผ ํ์ ์ ๋ฐ์ดํธํ๋ ๋์ ์ฆ๋ถ์ ์๋ฅผ ๋ค์ด ํน๋ณํ ์นด์ดํฐ ํ ์ด๋ธ์ 100 ๊ฐ์ ๋ค๋ฅธ ํ์ผ๋ก ํฌ ์์๋ฉ๋๋ค. ์ด์ scaling factor๋ ์นด์ดํฐ๊ฐ ๊ธฐ๋ก๋๋ ์ถ๊ฐ ํ์ ์์ ๋ฐ๋ผ ์ฆ๊ฐํฉ๋๋ค. ์ด๋ฌํ ๊ฐ์ ๋์ค์ ๋จ์ผ ๊ฐ์ผ๋ก ์ง๊ณ๋๊ณ ์ ๊ธ ๊ฒฝํฉ์ด ๋ฐ์ํ์ ์๋ ์ด์ ์ ์ฅ๋ฉ๋๋ค.### 1.2 Updates Based On A Select Query
> ์ ๋ ํธ ์ฟผ๋ฆฌ์ ๊ธฐ๋ฐํ ์ ๋ฐ์ดํธ
```sql
-- MySQL
UPDATE products
JOIN categories USING(category_id)
SET price = price_base - price_base * categories.discount;-- PostgreSQL
UPDATE products
SET price = price_base - price_base * categories.discount
FROM categories
WHERE products.category_id = categories.category_id;
```
ํ๋์ ํ ์ด๋ธ์ด ๋ ์์ ์ผ๋ก ์ ๋ฐ์ดํธ ๋๊ธฐ๋ ํ์ง๋ง ๋ค๋ฅธ ํ ์ด๋ธ์ ์ ์ฅ๋ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๊ฐ์ด ์ ๋ฐ์ดํธ ๋๊ธฐ๋ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๋๋์ ์ธ ํ์ฌ๊ธฐ๊ฐ ๋์ ๋ชจ๋ ์ ํ์ ํ ์ธํ๋ ๊ฒฝ์ฐ, ๊ฐ ์ํ์ ์นดํ ๊ณ ๋ฆฌ๋ณ ํ ์ธ์จ์ ์ ์ฉ ๋ฐ์ต๋๋ค. ๋ชจ๋ ์นดํ ๊ณ ๋ฆฌ์ ๋ํด ์ ๋ฐ์ดํธ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๋จ์ํ ๋ฐฉ๋ฒ ๋์ ์ํ์ ์นดํ ๊ณ ๋ฆฌ์ ์กฐ์ธํ์ฌ ์ ๋ฐ์ดํธํ ์ ์์ต๋๋ค. ์์ฉํ๋ก๊ทธ๋จ์ ์๋ ์กฐ์ธ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ํด ๋ณด๋ค ํจ์จ์ ์ธ ์กฐ์ธ์ผ๋ก ๋์ฒด๋ฉ๋๋ค.> **:bulb:์ฐธ๊ณ **
> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ฌธ ์น ์ฌ์ดํธ์ธ SQLFordevs.com: [UPDATE from a SELECT](https://sqlfordevs.com/update-from-select) ์์ ์ด ์ฃผ์ ์ ๊ด๋ จ๋ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.### 1.3 Return The Values Of Modified Rows
> ๋ณ๊ฒฝ๋ ํ์ ๋ฐํ ๊ฐ ์ฌ์ฉ
```sql
-- PostgreSQL:
DELETE FROM sessions
WHERE ip = '127.0.0.1'
RETURNING id, user_agent, last_access;
```
๋๋ถ๋ถ์ ์ ์ง๋ณด์ ์์ ์ ํน์ ํ์ ์ฐพ์ ์ฒ๋ฆฌ(์: ์ด๋ฉ์ผ ์ ์ก ๋๋ ์ผ๋ถ ํต๊ณ ๊ณ์ฐ)ํ๊ณ ์ฒ๋ฆฌ๋ ํ์ผ๋ก ํ์ํ๋ ๊ฒ์ ๊ธฐ๋ฐ์ผ๋ก ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ํ ๋ด์ ํ๋๊ทธ๋ ๋ ์ด์ ํ์ํ์ง ์์ผ๋ฏ๋ก ์ ๋ฐ์ดํธ๋๊ฑฐ๋ ์ญ์ ๋ฉ๋๋ค. RETURNING ๊ธฐ๋ฅ์ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ์กฐ์ ๋ฐ ๋ฐ์ดํฐ ์ ํ์ ํ ๋จ๊ณ๋ก ์ํํด์ ์ฒ๋ฆฌ ์ํฌ ํ๋ก์ฐ๋ฅผ ๋จ์ํ ํ ์ ์์ต๋๋ค. ์ด ๊ธฐ๋ฅ์ DELETE, INSERT ๋ฐ UPDATE ์ฟผ๋ฆฌ์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ด ์๋ฃ๋ ํ ๋ฐ์ดํฐ(์๋ฅผ๋ค๋ฉด ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ๊ฐ ์คํ๋๊ณ ์์ฑ๋ ๊ฐ์ด ์ฌ์ฉ ๊ฐ๋ฅํ Inserted ๋๋ Updated ๋ฐ์ดํฐ)๋ฅผ ๋ฐํํฉ๋๋ค.> **:bulb:์ฐธ๊ณ **
> ์ด ๊ธฐ๋ฅ์ PostgreSQL์์๋ง ๊ฐ๋ฅํฉ๋๋ค.### 1.4 Delete Duplicate Rows
> ์ค๋ณต ํ ์ญ์ ```sql
-- MySQL
WITH duplicates AS (
SELECT id, ROW_NUMBER() OVER(
PARTITION BY firstname, lastname, email
ORDER BY age DESC
) AS rownum
FROM contacts
)
DELETE contacts
FROM contacts
JOIN duplicates USING(id)
WHERE duplicates.rownum > 1;-- PostgreSQL
WITH duplicates AS (
SELECT id, ROW_NUMBER() OVER(
PARTITION BY firstname, lastname, email
ORDER BY age DESC
) AS rownum
FROM contacts
)
DELETE FROM contacts
USING duplicates
WHERE contacts.id = duplicates.id AND duplicates.rownum > 1;
```
์๊ฐ์ด ์ง๋๋ฉด ๋๋ถ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ์ด ์ค๋ณต๋์ด ์ฌ์ฉ์ ๊ฒฝํ์ด ๋๋น ์ง๊ณ ์คํ ๋ฆฌ์ง ์๊ตฌ ์ฌํญ์ด ์ฆ๊ฐํ๋ฉฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฑ๋ฅ์ด ์ ํ๋ฉ๋๋ค. CTE(Common Table Expression)์ ์ฌ์ฉํ์ฌ ์ค๋ณต ํ์ ์ค์๋๋ณ๋ก ์๋ณํ๊ณ ์ ๋ ฌํ์ฌ ๋ณด๊ดํ ์ ์์ต๋๋ค. ๋จ์ผ ์ญ์ ์ฟผ๋ฆฌ๋ ์ดํ์ ๋ณด๊ดํ ํน์ ๊ฐ์๋ฅผ ์ ์ธํ ๋ชจ๋ ์ค๋ณต ํญ๋ชฉ์ ์ญ์ ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ๋ณต์กํ ๋ก์ง์ ํ๋์ ๋จ์ํ SQL ์ฟผ๋ฆฌ๋ก ์ํํฉ๋๋ค.> **:bulb:์ฐธ๊ณ **
> * CTE๋ ์๋ธ์ฟผ๋ฆฌ๋ก ์ฐ์ด๋ ํ์ํ ์ด๋ธ(derived table)๊ณผ ๋น์ทํ ๊ฐ๋ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค.
> CTE ๊ตฌ๋ฌธ์ด WITH๋ก ์์ํด์ WITH ์ ์ด๋ผ๊ณ ๋ ํ๊ณ MySQL 8.0์ด์๋ถํฐ ์ง์ํฉ๋๋ค.
> CTE๋ ๊ถํ์ด ํ์ ์๊ณ ํ๋์ ์ฟผ๋ฆฌ๋ฌธ์ด ๋๋ ๋๊น์ง๋ง ์ง์๋๋ ์ผํ์ฑ ํ ์ด๋ธ์ด๋ผ๋ ์ ์ด VIEW์๋ ๋ค๋ฆ ๋๋ค.
> * ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ฌธ ์น ์ฌ์ดํธ์ธ SQLFordevs.com: [Delete Duplicate Rows](https://sqlfordevs.com/delete-duplicate-rows) ์์ ์ด ์ฃผ์ ์ ๊ด๋ จ๋ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.### 1.5 Table Maintenance After Bulk Modifications
> ๋ฒํฌ ์์ ํ ํ ์ด๋ธ ์ ์ง๊ด๋ฆฌ```sql
-- MySQL
ANALYZE TABLE users;-- PostgreSQL
ANALYZE SKIP_LOCKED users;
```
๋ฐ์ดํฐ๋ฒ ์ด์ค๋ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๊ฐ์ฅ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ๊ณ์ฐํ๊ธฐ ์ํด ๋๋ต์ ์ธ ํ ์, ๊ฐ์ ๋ฐ์ดํฐ ๋ถํฌ ๋ฑ ํ ์ด๋ธ์ ๋ํ ์ต์ ํต๊ณ๊ฐ ํ์ํฉ๋๋ค. ๋ฐ์ดํฐ์ ์ํฅ์ ๋ฏธ์น๋ ํ์ด ์์ฑ, ์ ๋ฐ์ดํธ ๋๋ ์ญ์ ๋ ๋๋ง๋ค ์๋์ผ๋ก ๋ณ๊ฒฝ๋๋ ์ธ๋ฑ์ค์ ๋ฌ๋ฆฌ ๋ชจ๋ ๋ณ๊ฒฝ์๋ง๋ค ํต๊ณ๋ ๊ณ์ฐ๋์ง ์์ต๋๋ค. ์ฌ ๊ณ์ฐ์ ํ ์ด๋ธ์ ๋ํ ๋ณ๊ฒฝ ์๊ณ ๊ฐ์ ์ด๊ณผํ ๋๋ง ํธ๋ฆฌ๊ฑฐ๋ฉ๋๋ค. ํ ์ด๋ธ์ ํฐ ๋ถ๋ถ์ ๋ณ๊ฒฝํ ๋๋ง๋ค ์ํฅ์ ๋ฐ๋ ํ์ ์๋ ์ฌ์ ํ ํต๊ณ ์ฌ๊ณ์ฐ ์๊ณ๊ฐ๋ณด๋ค ๋ฎ์ง๋ง ํต๊ณ๊ฐ ๋ถ์ ํํด์ง ์ ๋์ ์๋ฏธ๊ฐ ์์ ์ ์์ต๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ํ ์ด๋ธ์ ๋ํ ์๋ชป๋ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ต์์ ์ฟผ๋ฆฌ ๊ณํ์ ์์ธกํ๊ธฐ ๋๋ฌธ์ ์ผ๋ถ ์ฟผ๋ฆฌ๋ ๋งค์ฐ ๋๋ ค์ง ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ค์ํ ๋ณ๊ฒฝ์ด ์์ ๋ ํต๊ณ ์ฌ๊ณ์ฐ์ ํธ๋ฆฌ๊ฑฐํ๊ธฐ ์ํด ANALYZE TABLE์ ํ๋ฉด ์ฟผ๋ฆฌ ์๋๋ฅผ ๋์ผ ์ ์์ต๋๋ค.## 2. Querying Data
์์ฑํ๊ณ ์คํํ๋ ๋๋ถ๋ถ์ SQL ์ฟผ๋ฆฌ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ๋ ์ฟผ๋ฆฌ์ ๋๋ค. ๋ฐ์ดํฐ๋ฅผ ํ์ํ์ง ์์ผ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ธ๋ชจ์๊ฒ ๋๋ฏ๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ณธ์ด ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ณด๋ค ์ ๊ตํ ์ฟผ๋ฆฌ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ์ฌ ๋ง์ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ์ ๊ฑฐํ ์ ์๋ ๊ฐ์ฅ ์ข์ ๊ธฐํ์ด๊ธฐ๋ ํฉ๋๋ค. ๋๋ถ๋ถ์ ์ฌ๋ก๋ฅผ ๋ณด๋ฉด ์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋ณด๋ด์ง ์๊ณ ๋ฐ์ดํฐ๊ฐ ์๋ ๊ณณ์์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ ๋ ์ฑ๋ฅ์ ํฅ์์ํต๋๋ค. ์ด ์ฑํฐ๋ SQL ๋ด์ for-each ๋ฃจํ, ๋ช ๊ฐ์ง null ์ฒ๋ฆฌ ํธ๋ฆญ, ํ์ด์ง ์ง์ ์ค์์ ๊ฐ์ ์์ธ์ ์ธ ๊ธฐ๋ฅ์ ๋ณด์ฌ์ค ๊ฒ์ ๋๋ค. CTE๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ ์ ์ (refinement) ํ์ ๋งค์ฐ ์์ธํ ์ฝ๊ณ ๋์ ์ดํดํ๋ค๋ฉด ์์ฃผ ์ฌ์ฉํ ์ ์์ต๋๋ค.### 2.1 Reduce The Amount Of Group By Columns
> Group By ์ปฌ๋ผ ๊ฐฏ์ ์ค์ด๊ธฐ```sql
SELECT actors.firstname, actors.lastname, COUNT(*) as count
FROM actors
JOIN actors_movies USING(actor_id)
GROUP BY actors.id
```
์ผ๋ถ ์ด์ ๊ทธ๋ฃนํํ ๋๋ ๋ชจ๋ SELECT ์ด์ GROUP BY์ ์ถ๊ฐํด์ผ ํ๋ค๋ ๊ฒ์ ์ค๋์ ์ ๋ฐฐ์ ์ ๊ฒ์ ๋๋ค. ๊ทธ๋ฌ๋ PK๋ก GROUP BY ํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค๊ฐ ์๋์ผ๋ก ์ถ๊ฐํ๋ฏ๋ก ๋์ผํ ํ ์ด๋ธ์ ๋ชจ๋ ์ด์ ์๋ตํ ์ ์์ต๋๋ค. ๊ฒฐ๊ณผ์ ์ผ๋ก ์ฟผ๋ฆฌ๊ฐ ์งง์์ ธ์ ์ฝ๊ณ ์ดํดํ๊ธฐ๊ฐ ๋ ์ฌ์์ง๋๋ค.### 2.2 Fill Tables With Large Amounts Of Test Data
> ๋๋์ ํ ์คํธ ๋ฐ์ดํฐ ๋ง๋ค๊ธฐ```sql
-- MySQL
SET cte_max_recursion_depth = 4294967295;
INSERT INTO contacts (firstname, lastname)
WITH RECURSIVE counter(n) AS(
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM counter WHERE n < 100000
)
SELECT CONCAT('firstname-', counter.n), CONCAT('lastname-', counter.n)
FROM counter-- PostgreSQL
INSERT INTO contacts (firstname, lastname)
SELECT CONCAT('firstname-', i), CONCAT('lastname-', i)
FROM generate_series(1, 100000) as i;
```
๋๋ก๋ ์ฑ๋ฅ ํ ์คํธ๋ฅผ ์ํด ํ ์คํธ์ฉ ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์ค๋นํด์ผ ํฉ๋๋ค. ์ด ๋ฐ์ดํฐ๋ ๋ณดํต fake ๋ฐ์ดํฐ ์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐ๊ฑฐ๋ ๋ง์ ์ฝ๋๋ก ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ํ์ค์ ์ผ๋ก ๋ง๋ค๊ธฐ๋ ํฉ๋๋ค. ํ์ง๋ง ๋ง์ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์ฒ์ฒํ ํ๋์ฉ insertํ๋ ๋์ ์ด๋ฌํ ์ฟผ๋ฆฌ๋ก ๋ง์ ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ์ฌ ์ธ๋ฑ์ค์ ํจ์จ์ฑ์ ํ ์คํธ ํ ์ ์์ต๋๋ค.> **:warning:์ฃผ์**
> ์๋ฏธ ์๋ ๋ฒค์น๋งํฌ๋ฅผ ์ํด์๋ ํ์ค์ ์ธ ๋ฐ์ดํฐ์ ๊ฐ ๋ถํฌ๊ฐ ํ์ํฉ๋๋ค. ๊ทธ๋ฌ๋ ๋น์ ์ ์ฟผ๋ฆฌ์ ์ํฅ์ ๋ฐ์ง ์๋ ๋ ๋ง์ ํ ์คํธ ๋ฐ์ดํฐ ์์ฑ์ ์ํด ์ด ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์์ต๋๋ค.### 2.3 Simplified Inequality Checks With Nullable Columns
> Nullable ์ปฌ๋ผ์ ๋ํ ๊ฐ๋จํ ๋ถ๋ฑ์ ๊ฒ์ฌ```sql
-- MySQL
SELECT * FROM example WHERE NOT(column <> 'value');-- PostgreSQL
SELECT * FROM example WHERE column IS DISTINCT FROM 'value';
```
Nullable ์ปฌ๋ผ์ ๋ํด ํน์ ๊ฐ๊ณผ ๊ฐ์ง ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ํ๋ ๊ฒ์ ๋ณต์กํฉ๋๋ค. ๋ง์ด ์ฌ์ฉํ๋ col != 'value' ์กฐ๊ฑด์ ์ null ๊ฐ์ ๋ํด์๋ ์ ์ฉ๋์ง ์์ ๊ฒฐ๊ณผ์ ํฌํจ๋์ง ์์ต๋๋ค. ๊ทธ๋์ ํญ์ ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ผ๋ ค๊ณ ๋ ๋ณต์กํ (col IS NULL OR col!= 'value') ์กฐ๊ฑด์ ์ ์ฌ์ฉํฉ๋๋ค. ๋คํํ๋ ๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ชจ๋ null ์ฒ๋ฆฌ๋ฅผ ํฌํจํ๋ ๋ถ๋ฑ์ ๊ฒ์ฌ ๊ธฐ๋ฅ์ ์ง์ํฉ๋๋ค.### 2.4 Prevent Division By Zero Errors
> 0์ผ๋ก ๋๋๊ธฐ ์๋ฌ ๋ฐฉ์ง```sql
SELECT visitors_today / NULLIF(visitors_yesterday, 0)
FROM logs_aggregated;
```
๋ฐ์ดํฐ๋ฒ ์ด์ค ํต๊ณ ์์ ์ ์ด๋ ต์ง ์๊ณ ์๋ง ์๋ฐฑ ๋ฒ์ ์ํํ์ ๊ฒ์ ๋๋ค. ๊ทธ๋ฌ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ๋ ์ํํ ๊ฐ์ ์ด ๋ ์ด์ ์ ํจํ์ง ์์ ๋ช ๋ฌ ํ์ ์ด๋ฌํ ์ฟผ๋ฆฌ์์ ์๋ฌ๊ฐ ๋ฐ์ํ์ ์ ์์ต๋๋ค. ์๋ง๋ ์ฌ์ดํธ๊ฐ ๋ค์ด๋์ด ํน์ ํ ๋ ์ ๋ฐฉ๋ฌธ์๊ฐ ์์๊ฑฐ๋, ์ด์ ์ฒ์์ผ๋ก ์จ๋ผ์ธ ์คํ ์ด ํ๋งค ๊ฑด์๊ฐ ์์ ์๋ ์์ต๋๋ค. ๊ทธ๋ ์๋ ๋ฐ์ดํฐ๊ฐ ์์ด SUM(visitors_yesterday)์ผ๋ก ๋๋์ ๊ณ์ฐ ์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ์ผ๋ถ ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํ์ฌ ํญ์ 0์ผ๋ก ๋๋์ง ์๋๋ก ํด์ผ ํฉ๋๋ค. ๋๋๋ ์๋ฅผ 0์์ Null ๊ฐ์ผ๋ก ๋ณํํ๋ฉด ํด๋น ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋ฉ๋๋ค.### 2.5 Sorting Order With Nullable Columns
> Nullable ์ปฌ๋ผ์ ์ ๋ ฌ์์```sql
-- MySQL: NULL๊ฐ ์ฒ์ ๋ฐฐ์น (๊ธฐ๋ณธ)
SELECT * FROM customers ORDER BY country ASC;
SELECT * FROM customers ORDER BY country IS NOT NULL, country ASC;
-- MYSQL: NULL๊ฐ ๋ง์ง๋ง ๋ฐฐ์น
SELECT * FROM customers ORDER BY country IS NULL, country ASC;-- PostgreSQL: NULL๊ฐ ์ฒ์ ๋ฐฐ์น
SELECT * FROM customers ORDER BY country ASC NULLS FIRST;
-- PostgreSQL: NULL๊ฐ ๋ง์ง๋ง ๋ฐฐ์น (๊ธฐ๋ณธ)
SELECT * FROM customers ORDER BY country ASC;
SELECT * FROM customers ORDER BY country ASC NULLS LAST;
```
MySQL๊ณผ PostgreSQL์ nullable ์ปฌ๋ผ์ NULL ๊ฐ์ ์์ ํ ๋ค๋ฅด๊ฒ ์ ๋ ฌํฉ๋๋ค. ์ค๋ฆ์ฐจ์ ์ ๋ ฌ ์ MySQL์์๋ NULL ๊ฐ์ด ์ฒ์์ ๋์ค๋ ๋ฐ๋ฉด PostgreSQL์์๋ ๋งจ๋ค์์ ์กฐํ๋ฉ๋๋ค. NULL ๊ฐ์ ๋ํ ์ ๋ ฌ ์์ ๊ท์น์ด SQL ํ์ค์์ ๋๋ฝ๋์ด DB๊ตฌํ์ ๋ฐ๋ผ ๊ธฐ๋ณธ๊ฐ์ด ๋ค๋ฅผ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ ํ๋ฆฌ์ผ์ ์์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ธฐ ์ํด ์ํฉ์ ๋ง๋ ์ ์ด๊ฐ ํ์ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ค๋ฆ์ฐจ์, ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ง์ ๋ ์ปฌ๋ผ๋ค์ ํน์ ํ์ ๊ฒ์ํ ๋ NULL๊ฐ์ ๋ณด๋๋ฐ ๊ด์ฌ์ด ์์ผ๋ฏ๋ก ๋ง์ง๋ง์ ์์ด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ปฌ๋ผ์ ๋น๊ฐ์ด ์ต์ ๋ฐ์ดํฐ๋ก ์๋ฏธ๊ฐ ๋ถ์ฌ๋ ๊ฒฝ์ฐ ํด๋น ์ ๋ณด๊ฐ ๊ฐ์ฅ ๋จผ์ ํ์๋์ด์ผ ์ข์ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.> **:bulb:์ฐธ๊ณ **
> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ฌธ ์น ์ฌ์ดํธ์ธ SQLFordevs.com: [ORDER BY with nullable columns](https://sqlfordevs.com/order-by-with-null) ์์ ์ด ์ฃผ์ ์ ๊ด๋ จ๋ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.### 2.6 Deterministic Ordering for Pagination
> ํ์ด์ง์ ๊ฒฐ์ ์ ์์```sql
SELECT *
FROM users
ORDER BY firstname ASC, lastname ASC, user_id ASC
LIMIT {contents ๊ฐ์} OFFSET {page number}
```
MySQL๊ณผ PostgreSQL์ nullable ์ปฌ๋ผ์ NULL ๊ฐ์ ์์ ํ ๋ค๋ฅด๊ฒ ์ ๋ ฌํฉ๋๋ค. ์ค๋ฆ์ฐจ์ ์ ๋ ฌ ์ MySQL์์๋ NULL ๊ฐ์ด ์ฒ์์ ๋์ค๋ ๋ฐ๋ฉด PostgreSQL์์๋ ๋งจ๋ค์์ ์กฐํ๋ฉ๋๋ค. NULL ๊ฐ์ ๋ํ ์ ๋ ฌ ์์ ๊ท์น์ด SQL ํ์ค์์ ๋๋ฝ๋์ด DB๊ตฌํ์ ๋ฐ๋ผ ๊ธฐ๋ณธ๊ฐ์ด ๋ค๋ฅผ ์ ์์ต๋๋ค. ๋ฐ๋ผ์ ์ ํ๋ฆฌ์ผ์ ์์๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๊ธฐ ์ํด ์ํฉ์ ๋ง๋ ์ ์ด๊ฐ ํ์ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ค๋ฆ์ฐจ์, ๋ด๋ฆผ์ฐจ์์ผ๋ก ์ง์ ๋ ์ปฌ๋ผ๋ค์ ํน์ ํ์ ๊ฒ์ํ ๋ NULL๊ฐ์ ๋ณด๋๋ฐ ๊ด์ฌ์ด ์์ผ๋ฏ๋ก ๋ง์ง๋ง์ ์์ด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ปฌ๋ผ์ ๋น๊ฐ์ด ์ต์ ๋ฐ์ดํฐ๋ก ์๋ฏธ๊ฐ ๋ถ์ฌ๋ ๊ฒฝ์ฐ ํด๋น ์ ๋ณด๊ฐ ๊ฐ์ฅ ๋จผ์ ํ์๋์ด์ผ ์ข์ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.> **:bulb:์ฐธ๊ณ **
> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ฌธ ์น ์ฌ์ดํธ์ธ SQLFordevs.com: [ORDER BY with nullable columns](https://sqlfordevs.com/order-by-with-null) ์์ ์ด ์ฃผ์ ์ ๊ด๋ จ๋ ์์ธํ ๋ด์ฉ์ ํ์ธํ ์ ์์ต๋๋ค.### 2.7 More Efficient Pagination Than LIMIT OFFSET
> LIMIT OFFSET ๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ ์ฌ์ฉ์ ์ฃผ์์
```sql
-- MySQL, PostgreSQL
SELECT *
FROM users
WHERE (firstname, lastname, id) > ('John', 'Doe', 3150)
ORDER BY firstname ASC, lastname ASC, user_id ASC
LIMIT 30
```
์ผ๋ถ ํ์ ๊ฑด๋๋ฐ๋ LIMIT OFFSET ํค์๋๋ฅผ ์ฌ์ฉํ๋ ๊ฐ์ฅ ๋จ์ํ ํ์ด์ง๋ค์ด์ ์ ๊ทผ ๋ฐฉ์์ ๋๋ก๋ ์ฌ๊ฐํ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. ํ์ด์ง๋ค์ด์ ์ ๊ณผ์ ์ค ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋ ๊ฒฝ์ฐ, ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋๊ฑฐ๋ ์ค๋ณต๋ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค. ๋ฐ์ดํฐ์ ์ค๋ณต ์ ๋ฌด๊ฐ ์ค์ํ์ง ์์ ๊ด๋ฆฌ์ ๊ฒ์ํ ๊ฐ์ ๊ฐ๋จํ ๊ฒฝ์ฐ๋ผ๋ฉด ๋ฌธ์ ๊ฐ ์์ง๋ง, ์กฐํ๋ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ค๋ณต์์ด ์ถ๊ฐ์ฒ๋ฆฌ๊ฐ ํ์ํ ์ํฉ์ด๋ผ๋ฉด ์ปค์๊ธฐ๋ฐ ํ์ด์ง๋ค์ด์ ์ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ณด๋ค ์์ ํฉ๋๋ค. ์ด ๊ฒฝ์ฐ์๋ ์ ๋ ฌํ ์ปฌ๋ผ์ ์ค๋ณต๋ ๊ฐ์ด ์กด์ฌํ๋ฉด ์๋๊ณ , ์์ฐจ์ ์ด์ด์ผ ํฉ๋๋ค. ๋ฐ๋ผ์ ๋ชจ๋ ํ์ด ๊ณ ์ ํ๋๋ก ๋ ๋ง์ ์ด์ ์ถ๊ฐํ์ฌ ์ ๋ ฌํ ์ปฌ๋ผ์ ์ค๋ณต๋ ๊ฐ์ด ์กด์ฌํ๋ฉด ์๋๋ก ํ์ฌ ํญ์ ์์๋ฅผ ๊ฒฐ์ ์ ์ผ๋ก ๋ง๋๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. timstamp ๊ฐ์ ์์ฐจ์ ์ด๊ณ ๊ณ ์ ํ ๊ธฐ๋ณธ ํค๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ๊ฐ์ฅ ๊ฐ๋จํ ์ ๊ทผ ๋ฐฉ์์ ๋๋ค.### 2.8 Database-Backed Locks With Safety Guarantees
> ์์ ํ DB๊ธฐ๋ฐ ์ ๊ธ ์ฅ์น
```sql
START TRANSACTION;SELECT balance FROM account WHERE account_id = 7 FOR UPDATE;
-- ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํ ๋ ์ด์ค ์ปจ๋์ free update
UPDATE account SET balance = 540 WHERE account_id = 7;COMMIT;
```
๊ฑฐ์ ๋ชจ๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ ์ด์ค ์ปจ๋์ ์ด ๋ฐ์ํ๊ธฐ ์ฝ์ต๋๋ค. ์ฆ, DB์์ ๊ฐ์ด ์ ํ๋๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ผ๋ถ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ฉฐ ๊ฐ์ด ์ ๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ ๋ฉ๋๋ค. ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์๋ก์ด ๊ฐ์ผ๋ก ์ ๋ฐ์ดํธ ํ๊ธฐ ์ํด ์ ํ๋ฆฌ์ผ์ด์ ์ DB์์ ๊ฐ์ ์กฐํํฉ๋๋ค. ๊ทธ๋ฌ๋ ๊ณ์ฐ ์ค์ ๋ฐ์ํ๋ ์ ๋ฐ์ดํธ๋ก๋ถํฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณดํธ๋์ง ์์ต๋๋ค. ์ผ๋ถ ์ค์ํ ๋ถ๋ถ์ ๋ ์ด์ค ์ปจ๋์ ์ ๋๋นํ ์ ๊ธ ์๋ฃจ์ ์ผ๋ก ๋ณดํธ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ์ถฉ๋์ด ๋ฐ์ํ๋ ์ ํ๋ฆฌ์ผ์ด์ ๋ ผ๋ฆฌ๋ ๋ง์ง๋ง์ ๋ฝ ํด์ ๋ฅผ ๋์น ์ ์์ผ๋ฏ๋ก ์ ๊ธ ์๊ณ ๋ฆฌ์ฆ์ ๊ตฌ์ถํ๊ธฐ๊ฐ ์ด๋ ต์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํ ์๋ ์๊ฐ ๊ธฐ๋ฐ ์ ๊ธ ํด์ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ ํ โโ๋ฝ์ ๋๋ฌด ์ค๋ซ๋์ ์ ์งํ๊ฒ ๋ฉ๋๋ค.
๋ฐ์ดํฐ ์์ ์ฟผ๋ฆฌ๋ฅผ(์: UPDATE)๋ฅผ ์คํํ๋ฉด DB๋ ์ ํ์ฑ์ ๋ณด์ฅํ๊ธฐ ์ํด ํธ๋์ญ์ ์ด ๋๋ ๋ ๊น์ง ์ํฅ์ ๋ฐ๋ ๋ชจ๋ ํ์ ์์ฒด์ ์ผ๋ก ์ ๊ธ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ๊ธ ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ ๋์ SELECT ์ฟผ๋ฆฌ์ FOR UPDATE๋ฅผ ์ฌ์ฉํ์ฌ ๋ ์ด์ค ์ปจ๋์ ์ ๋ํด ์ฝ๊ธฐ ๋ฐ์ดํฐ๋ฅผ ์ ๊ทธ๋ ๋ฐฉ์์ผ๋ก DB์ ํ์ ํ ์ ์์ต๋๋ค. ์ด๋ฌํ ์ ๊ธ์ ํธ๋์ญ์ ์ด ์๋ฃ๋๊ฑฐ๋ ํด๋ฆฌ์ด์ธํธ์ ์ฐ๊ฒฐ์ด ๋์ด์ง๋ฉด ์๋์ผ๋ก ํด์ ๋ฉ๋๋ค.> **:warning:์ฃผ์**
> ๊ฐ์ ์ ๋ฐ์ดํธ ํ๋ ๋ชจ๋ ๋ก์ง์ ๋ฝ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ๋ ์ด์ค ์ปจ๋์ ์ ์์น ์๋ ๊ฒฐ๊ณผ๋ก ๋๋๊ฒ ๋ฉ๋๋ค. ์ด๋์์๋ ์ฌ์ฉํด์ผ ํ๊ณ ์์ฃผ ์์ ๋ถ๋ถ๋ง์ผ๋ก๋ ๋ ์ด์ค ์ปจ๋์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์ต๋๋ค.> **:bulb:์ฐธ๊ณ **
> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ฌธ ์น ์ฌ์ดํธ์ธ SQLFordevs.com: [Transactional Locking to Prevent Race Conditions](https://sqlfordevs.com/transaction-locking-prevent-race-condition) ์์ ์ด ์ฃผ์ ์ ๊ด๋ จ๋ ๊ด๋ฒ์ํ ๊ธ์ ์์ฑํ์ต๋๋ค.### 2.9 Refinement Of Data With Common Table Expressions
> CTE๋ฅผ ์ฌ์ฉํ ๋ฐ์ดํฐ ๊ตฌ์ฒดํ
```sql
WITH most_popular_products AS (
SELECT products.*, COUNT(*) as sales
FROM products
JOIN users_orders_products USING(product_id)
JOIN users_orders USING(order_id)
WHERE users_orders.created_at BETWEEN '2022-01-01' AND '2022-06-30'
GROUP BY products.product_id
ORDER BY COUNT(*) DESC
LIMIT 10
), applicable_users (
SELECT DISTINCT users.*
FROM users
JOIN users_raffle USING(user_id)
WHERE users_raffle.correct_answers > 8
), applicable_users_bought_most_popular_product AS (
SELECT applicable_users.user_id, most_popular_products.product_id
FROM applicable_users
JOIN users_orders USING(order_id)
JOIN users_orders_products USING(product_id)
JOIN most_popular_products USING(product_id)
) raffle AS (
SELECT product_id, user_id, RANK() OVER(
PARTITION BY product_id
ORDER BY RANDOM()
) AS winner_order
FROM applicable_users_bought_most_popular_product
)
SELECT product_id, user_id FROM raffle WHERE winner_order = 1;
```
๋จ์ผ ์ฟผ๋ฆฌ ํ๋๋ก ์ํํ๊ธฐ ์ด๋ ค์ด ๋ณต์กํ ๊ท์น์ด ์๋ DB์์ ํ์ ๊ฐ์ ธ์์ผ ํ๋ ๊ฒฝ์ฐ CTE(Common Table Expressions)๋ฅผ ์ฌ์ฉํ์ฌ ์ด๋ฅผ ๋ถํ ํ ์ ์์ต๋๋ค. ๋ชจ๋ ๋จ๊ณ์์ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ์ฒดํํ๊ณ ๋์ค์ ๊ตฌ์ฒดํํ์ฌ ์ต์ข ์ ์ผ๋ก ์ํ๋ ๊ฒฐ๊ณผ๋ฅผ ์ป์ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด ์ค์ฒฉ๋ ์๋ธ์ฟผ๋ฆฌ๊ฐ ๋ง๊ฑฐ๋ ์กฐ์ธ์ด ์์ญ ๊ฐ์ด๋ฉด CTE๋ ์ ํต์ ์ธ ์ ๊ทผ ๋ฐฉ์๋ณด๋ค ๊ฐ๋ ์ฑ์ด ์ข๊ณ ๋ฐ๋ณต ๋จ๊ณ๋ฅผ ๊ณ ๋ฆฝ์์ผ ๋ณ๋๋ก ๋๋ฒ๊น ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ฑ๋ฅ ์ธก๋ฉด์์ ๋ ๊ฐ์ง ์ ๊ทผ๋ฐฉ์์ ๋ชจ๋ ๋์ผํฉ๋๋ค. DB๋ ์ด๋ฅผ ๋ด๋ถ์ ์ผ๋ก ์ค์ฒฉ๋ ์๋ธ์ฟผ๋ฆฌ๋ก ๋ณํํ๊ฑฐ๋ ์ฌ๋ฌ ๋ฒ ์ฌ์ฉ๋๋ ๋จ์ผ ๋จ๊ณ๋ฅผ ์บ์ฑํ์ฌ ๋ณด๋ค ํจ์จ์ ์ธ ์ ๊ทผ ๋ฐฉ์์ ์ฐพ์ ์ ์์ต๋๋ค.### 2.10 First Row Of Many Similar Ones
> ๋ง์ ์ ์ฌํ ํญ๋ชฉ ์ค ์ฒซ ๋ฒ์งธ ํ
```sql
-- PostgreSQL
SELECT DISTINCT ON (customer_id) *
FROM orders
WHERE EXTRACT (YEAR FROM created_at) = 2022
ORDER BY customer_id ASC, price DESC;
```
๋๋ก๋ ์๋ง์ ํ(์๋ฅผ๋ค๋ฉด ๋ชจ๋ ๊ณ ๊ฐ)์ด ์๊ณ ๊ทธ์ค์ ํ๋๋ง ์ํ๋ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. ์ด์ ์ ์ค๋ช ํ ๋๋ก for-each-loop์ ๊ฐ์ lateral ์กฐ์ธ์ ๊ณ ์ํ๊ฑฐ๋ PostgreSQL์ DISTINCT ON invention์ ์ฌ์ฉํ ์ ์์ต๋๋ค.
ํ์ค DISTINCT ์ฟผ๋ฆฌ๋ ํ์ ๋ชจ๋ ์ปฌ๋ผ์์ ์ ํํ ์ผ์นํ๋ ํ์ ํํฐ๋งํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ ์ฟผ๋ฆฌ์ฒ๋ผ ์ฌ์ฉํ๋ฉด ์ปฌ๋ผ์ ์๋ธ์ ์ ์ง์ ํ์ฌ ๊ตฌ๋ณํ ์ ์์ผ๋ฉฐ ์ ๋ ฌ ํ ์ฒซ ๋ฒ์งธ๋ก ์ผ์นํ๋ ํ๋ง ์ ์ง๋ฉ๋๋ค.> **:bulb:์ฐธ๊ณ **
> ์ด ๊ธฐ๋ฅ์ PostgreSQL์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.### 2.11 Multiple Aggregates In One Query
> ํ๋์ ์ฟผ๋ฆฌ๋ก ๋ค์ค ์ง๊ณํ๊ธฐ
```sql
-- MySQL
SELECT
SUM(released_at = 2001) AS released_2001,
SUM(released_at = 2002) AS released_2002,
SUM(director = 'Steven Spielberg') AS director_stevenspielberg,
SUM(director = 'James Cameron') AS director_jamescameron
FROM movies
WHERE streamingservice = 'Netflix';
-- PostgreSQL
SELECT
COUNT(*) FILTER (WHERE released_at = 2001) AS released_2001,
COUNT(*) FILTER (WHERE released_at = 2002) AS released_2002,
COUNT(*) FILTER (WHERE director = 'Steven Spielberg') AS
director_stevenspielberg,
COUNT(*) FILTER (WHERE director = 'James Cameron') AS
director_jamescameron
FROM movies
WHERE streamingservice = 'Netflix';
```
์ด๋ค ๊ฒฝ์ฐ์๋ ์ฌ๋ฌ ๊ฐ์ง ๋ค๋ฅธ ํต๊ณ๋ฅผ ๊ณ์ฐํด์ผ ํฉ๋๋ค. ์๋ง์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ๋ ๋์ , ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ํต๊ณผํ์ฌ ๋ชจ๋ ์ ๋ณด๋ฅผ ์์งํ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
๋ฐ์ดํฐ์ ์ธ๋ฑ์ค์ ๋ฐ๋ผ ์คํ ์๊ฐ์ด ๋นจ๋ผ์ง๊ฑฐ๋ ๋๋ ค์ง ์ ์์ผ๋ฏ๋ก ๋ฐ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ ์คํธํด์ผ ํฉ๋๋ค.> **:bulb:์ฐธ๊ณ **
> ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๋ฌธ ์น ์ฌ์ดํธ์ธ SQLFordevs.com: [Multiple Aggregates in One Query](https://sqlfordevs.com/multiple-aggregates-in-one-query) ์์ ์ด ์ฃผ์ ์ ๊ด๋ จ๋ ๊ด๋ฒ์ํ ๊ธ์ ์์ฑํ์ต๋๋ค.### 2.12 Limit Rows Also Including Ties
> ๋์ผ ๊ฐ์ ๊ฐ์ง ํ๋ ํฌํจ์ํค๋ limit
```sql
-- PostgreSQL
SELECT *
FROM teams
ORDER BY winning_games DESC
FETCH FIRST 3 ROWS WITH TIES;
```
์คํฌ์ธ ๋ฆฌ๊ทธ ํ์ ์์๋ฅผ ๋งค๊ธฐ๊ณ ์์ 3๊ฐ ํ์ ๋ณด์ฌ์ฃผ๊ณ ์ถ๋ค๊ณ ์์ํด ๋ณด์ธ์. ๋๋ฌธ ๊ฒฝ์ฐ์ง๋ง, ์์ฆ์ด ๋๋ ๋ ์ต์ 2๊ฐ ํ์ ์น๋ฅ ์ด ๋์ผํ ์ ์์ต๋๋ค. ๋ ๋ค 3์์ธ ๊ฒฝ์ฐ ๋ ๋ค ํฌํจํ๋๋ก limit์ ํ์ฅํ ์ ์์ต๋๋ค.
๋ง์ฝ ์ํ ๋ชจ๋ 3๋ฒ์งธ์ ์์นํ ๋ ์ํ์ ํฌํจํด์ limit ์ ํ์ฅํ๊ธฐ๋ฅผ ์ํ ์ ์์ต๋๋ค. ์ด ๋ WITH TIES ์ต์ ์ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. ํฌํจ๋ ๊ฐ๊ณผ ๋์ผํ ๊ฐ์ ๊ฐ์ง ์ผ๋ถ ํ์ด ์ ์ธ๋์ง ์๊ฒ limit์ ์ด๊ณผํ๋๋ผ๋ ํด๋น ํ๊น์ง ํฌํจ์์ผ ์ค๋๋ค.> **:bulb:์ฐธ๊ณ **
> ์ด ๊ธฐ๋ฅ์ PostgreSQL์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค.### 2.13 Fast Row Count Estimates
> ๋น ๋ฅธ ํ ๊ฐ์ ์ถ์
```sql
-- MySQL
EXPLAIN FORMAT=TREE SELECT * FROM movies WHERE rating = 'NC-17' AND price < 4.99;
-- PostgreSQL
EXPLAIN SELECT * FROM movies WHERE rating = 'NC-17' AND price < 4.99;
```
์ผ์นํ๋ ํ ์๋ฅผ ํ์ํ๋ ๊ฒ์ ๋๋ถ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ค์ํ ๊ธฐ๋ฅ์ด์ง๋ง ๋๊ท๋ชจ DB์์๋ ๊ตฌํํ๊ธฐ ์ด๋ ค์ด ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค. DB๊ฐ ํด์๋ก ํ ์ ๊ณ์ฐ ์๋๊ฐ ๋๋ ค์ง๋๋ค.
๊ฐ์๋ฅผ ๊ณ์ฐํ๋ ๋ฐ ๋์์ด ๋๋ ์ธ๋ฑ์ค๊ฐ ์์ผ๋ฉด ์ฟผ๋ฆฌ ์๋๊ฐ ๋งค์ฐ ๋๋ ค์ง๋๋ค. ๊ทธ๋ฌ๋ ๊ธฐ์กด ์ธ๋ฑ์ค๋ก๋ ์์ญ๋ง ๊ฐ์ ์ธ๋ฑ์ค๋ฅผ ๋น ๋ฅด๊ฒ ๊ณ์ฐํ ์๋ ์์ต๋๋ค.
ํ์ง๋ง ์ผ๋ถ ์ฌ์ฉ ์ฌ๋ก์์๋ ๋๋ต์ ์ธ ํ ์๋ง์ผ๋ก๋ ์ถฉ๋ถํ ์ ์์ต๋๋ค. DB ์ฟผ๋ฆฌ ํ๋๋๋ ํญ์ DB์ ์คํ ๊ณํ์ ์์ฒญํ์ฌ ์ถ์ถํ ์ ์๋ ์ฟผ๋ฆฌ์ ๋ํ ๋๋ต์ ์ธ ํ ์๋ฅผ ๊ณ์ฐํฉ๋๋ค.### 2.14 Date-Based Statistical Queries With Gap-Filling
### 2.15 Table Joins With A For-Each Loop## 3. Schema
### 3.1 Rows Without Overlapping Dates
### 3.2 Store Trees As Materialized Paths
### 3.3 JSON Columns to Combine NoSQL and Relational Databases
### 3.4 Alternative Tag Storage With JSON Arrays
### 3.5 Constraints for Improved Data Strictness
### 3.6 Validation Of JSON Colums Against A Schema
### 3.7 UUID Keys Against Enumeration Attacks
### 3.8 Fast Delete Of Big Data With Partitions
### 3.9 Pre-Sorted Tables For Faster Access
### 3.10 Pre-Aggregation of Values for Faster Queries## 4. Indexes
### 4.1 Indexes On Functions And Expressions
### 4.2 Find Unused Indexes
### 4.3 Safely Deleting Unused Indexes
### 4.4 Index-Only Operations By Including More Columns
### 4.5 Partial Indexes To Reduce Index Size
### 4.6 Partial Indexes For Uniqueness Constraints
### 4.7 Index Support For Wildcard Searches
### 4.8 Rules For Multi-Column Indexes
### 4.9 Hash Indexes To Descrease Index Size
### 4.10 Descending Indexes For Order By
### 4.11 Ghost Conditions Against Unindexed Columns์ถ์ฒ : [SqlForDevs.com](https://sqlfordevs.com/ebook)