An open API service indexing awesome lists of open source software.

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

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
-- MySQL

INSERT 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)