LIMIT OFFSET ํ์ด์ง, JSONB ์ฐ์ฐ์, ON CONFLICT(Upsert), RETURNING ๋ฌธ, WITH RECURSIVE, EXPLAIN ANALYZE ๋ฐ ์ค๋ฌด ๋ ํผ๋ฐ์ค
## 1. ํ์ด์ง ์ฟผ๋ฆฌ (LIMIT & OFFSET)
```sql
-- 1. ํ์ค LIMIT / OFFSET ํ์ด์ง
SELECT id, name, created_at
FROM users
ORDER BY id DESC
LIMIT 10 OFFSET 20; -- 21๋ฒ์งธ๋ถํฐ 10๊ฑด ์กฐํ
-- 2. SQL ํ์ค ๋ฌธ๋ฒ (FETCH FIRST)๋ ๋์ผ ์ง์
SELECT id, name
FROM users
ORDER BY id
OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY;
```
## 2. ๋ ์ง & ์๊ฐ ์ฐ์ฐ (Timestamp & Interval)
```sql
-- ํ์ฌ ์ผ์
SELECT NOW(), CURRENT_TIMESTAMP;
-- 1. ๋ ์ง ๊ฐ๊ฐ (INTERVAL ํค์๋)
SELECT NOW() + INTERVAL '1 day'; -- 1์ผ ํ
SELECT NOW() - INTERVAL '3 months'; -- 3๋ฌ ์
SELECT NOW() + INTERVAL '2 hours 30 minutes'; -- 2์๊ฐ 30๋ถ ํ
-- 2. ํฌ๋งท ๋ณํ (TO_CHAR / TO_TIMESTAMP)
SELECT TO_CHAR(NOW(), 'YYYY-MM-DD HH24:MI:SS');
SELECT TO_TIMESTAMP('2026-09-26 14:30:00', 'YYYY-MM-DD HH24:MI:SS');
-- 3. ๋ ์ง ์๋ฅด๊ธฐ (DATE_TRUNC)
SELECT DATE_TRUNC('month', NOW()); -- ์ด๋ฒ ๋ฌ 1์ผ 00:00:00
SELECT DATE_TRUNC('day', NOW()); -- ์ค๋ 00:00:00
-- 4. ์ฐ์ ๋ ์ง/์ซ์ ์์ฑ๊ธฐ (GENERATE_SERIES)
SELECT GENERATE_SERIES('2026-09-01'::DATE, '2026-09-05'::DATE, '1 day'::INTERVAL);
```
## 3. ๊ฐ๋ ฅํ ํ์
๋ณํ (Type Casting `::`)
```sql
-- PostgreSQL ํน์ ์ ์ด๊ฐํธ ์ด์ค ์ฝ๋ก (::) ์บ์คํ
๋ฌธ๋ฒ
SELECT '123'::INTEGER;
SELECT '2026-09-26'::DATE;
SELECT 45.67::NUMERIC(5, 1); -- 45.7
SELECT 'true'::BOOLEAN;
SELECT '{"name": "kim"}'::JSONB;
-- ํ์ค CAST ํจ์์ ๋์ผ
SELECT CAST('123' AS INTEGER);
```
## 4. JSON & JSONB ๋ค๋ฃจ๊ธฐ (PostgreSQL์ ํน์ฅ์ )
```sql
-- ํ
์ด๋ธ์ JSONB ์ปฌ๋ผ ์์ฑ ๋ฐ ๋ฐ์ดํฐ ์ฝ์
CREATE TABLE user_profiles (
id SERIAL PRIMARY KEY,
data JSONB NOT NULL
);
INSERT INTO user_profiles (data)
VALUES ('{"name": "kim", "age": 28, "tags": ["dev", "db"], "address": {"city": "Seoul"}}');
-- 1. -> : JSON ๊ฐ์ฒด/๋ฐฐ์ด ๋ฐํ (๋ฐ์ดํ ์ ์ง)
SELECT data->'address' FROM user_profiles; -- {"city": "Seoul"}
-- 2. ->> : ํ
์คํธ ๋ฌธ์์ด๋ก ์ถ์ถ (๋ฐ์ดํ ์ ๊ฑฐ)
SELECT data->>'name' FROM user_profiles; -- kim
SELECT data->'address'->>'city' FROM user_profiles; -- Seoul
-- 3. @> : ํฌํจ ์ฌ๋ถ ํ์ธ (GIN ์ธ๋ฑ์ค ์ง์์ผ๋ก ์ด๊ณ ์ ๊ฒ์!)
SELECT * FROM user_profiles WHERE data @> '{"tags": ["db"]}';
SELECT * FROM user_profiles WHERE data @> '{"age": 28}';
-- 4. GIN ์ธ๋ฑ์ค ์์ฑ
CREATE INDEX idx_user_data ON user_profiles USING GIN (data);
```
## 5. Upsert: ON CONFLICT (์ถฉ๋ ์ฒ๋ฆฌ)
```sql
-- ์ค๋ณต ์ถฉ๋ ์ ๋ฌด์ (DO NOTHING)
INSERT INTO users (id, name, email)
VALUES (1, 'ํ๊ธธ๋', 'hong@test.com')
ON CONFLICT (id) DO NOTHING;
-- ์ค๋ณต ์ถฉ๋ ์ ์
๋ฐ์ดํธ (DO UPDATE SET) - EXCLUDED ํค์๋ ํ์ฉ
INSERT INTO users (id, name, email, updated_at)
VALUES (1, 'ํ๊ธธ๋', 'hong@test.com', NOW())
ON CONFLICT (id)
DO UPDATE SET
name = EXCLUDED.name,
email = EXCLUDED.email,
updated_at = NOW();
```
## 6. RETURNING ์ (๋ฑ๋ก/์์ /์ญ์ ์ฆ์ ๋ฐํ)
```sql
-- 1. INSERT ํ ์๋ ๋ฐ๊ธ๋ ID ์ฆ์ ๋ฐํ (๋ณ๋ SELECT ๋ถํ์!)
INSERT INTO users (name, email)
VALUES ('์ด์์ ', 'lee@test.com')
RETURNING id, created_at;
-- 2. UPDATE ๋ ํ๋ค์ ์์ ํ ์ํ ๋ฐํ
UPDATE orders
SET status = 'SHIPPED'
WHERE status = 'PAID'
RETURNING id, total_amount, updated_at;
-- 3. DELETE ๋ ๋ ์ฝ๋์ ๋ฐฑ์
์ฉ ๋ฐ์ดํฐ ๋ฐํ
DELETE FROM temp_logs
WHERE created_at < NOW() - INTERVAL '30 days'
RETURNING *;
```
## 7. ๋ฐฐ์ด(Array) & ์ง๊ณ (ARRAY_AGG & STRING_AGG)
```sql
-- 1. ๋ฐฐ์ด ์ปฌ๋ผ ์์ฑ ๋ฐ ์กฐ์
CREATE TABLE posts (id SERIAL, tags TEXT[]);
INSERT INTO posts (tags) VALUES (ARRAY['postgres', 'sql']);
SELECT * FROM posts WHERE 'sql' = ANY(tags);
-- 2. ์ฌ๋ฌ ํ์ ํ๋์ ์ฝค๋ง ๋ฌธ์์ด๋ก ํฉ์น๊ธฐ
SELECT dept_id, STRING_AGG(name, ', ' ORDER BY name) AS members
FROM employees GROUP BY dept_id;
-- 3. ์ฌ๋ฌ ํ์ ํ๋์ JSON/Array ๋ฐฐ์ด๋ก ๋ฌถ๊ธฐ
SELECT dept_id, ARRAY_AGG(id ORDER BY id) AS member_ids
FROM employees GROUP BY dept_id;
```
## 8. ๊ณ์ธตํ ์ฌ๊ท ์ฟผ๋ฆฌ (WITH RECURSIVE)
```sql
WITH RECURSIVE org_chart AS (
-- 1. ์ด๊ธฐ ์ฟผ๋ฆฌ (๋ฃจํธ ๋ํ)
SELECT emp_id, manager_id, emp_name, 1 AS level, emp_name::TEXT AS path
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- 2. ์ฌ๊ท ์ฟผ๋ฆฌ (ํ์ ํ์)
SELECT e.emp_id, e.manager_id, e.emp_name, o.level + 1, o.path || ' > ' || e.emp_name
FROM employees e
INNER JOIN org_chart o ON e.manager_id = o.emp_id
)
SELECT level, emp_name, path FROM org_chart ORDER BY path;
```
## 9. ์ฑ๋ฅ ๋ถ์ (EXPLAIN ANALYZE & BUFFERS)
```sql
-- ์ค์ ์ฟผ๋ฆฌ๋ฅผ ์คํํ์ฌ ์ค์ ์ํ ์๊ฐ(Actual Time) ๋ฐ I/O ๋ฒํผ ํํธ์จ ํ์ธ
EXPLAIN (ANALYZE, BUFFERS, COSTS, VERBOSE)
SELECT * FROM users WHERE email = 'test@example.com';
-- ๐ก ์ฒดํฌํฌ์ธํธ:
-- Seq Scan (ํ ์ค์บ) vs Index Scan / Index Only Scan
-- Buffers: shared hit (๋ฉ๋ชจ๋ฆฌ ์บ์ ํํธ) vs read (๋์คํฌ I/O)
```
## 10. ์ค๋ฌด ์ ์ง๋ณด์ & ์ธ์
๊ด๋ฆฌ (pg_stat_activity)
```sql
-- 1. ํ์ฌ ์คํ ์ค์ธ ์ฟผ๋ฆฌ ๋ฐ ๋๊ธฐ ์ธ์
ํ์ธ
SELECT pid, usename, client_addr, state, query_start, query
FROM pg_stat_activity
WHERE state != 'idle' AND pid <> pg_backend_pid()
ORDER BY query_start;
-- 2. ์ธ์
์์ ์ทจ์ (์ฟผ๋ฆฌ๋ง ์ทจ์)
SELECT pg_cancel_backend(12345);
-- 3. ์ธ์
๊ฐ์ ์ข
๋ฃ (์ปค๋ฅ์
๋๊ธฐ)
SELECT pg_terminate_backend(12345);
-- 4. ํ
์ด๋ธ ํต๊ณ ๊ฐฑ์ ๋ฐ ๋ฐ๋ ํํ ์ ๋ฆฌ (VACUUM)
VACUUM (VERBOSE, ANALYZE) users;
```
์๊ฒฌ ๋ฐ ์ง๋ฌธ
0์์ง ๋ฑ๋ก๋ ์๊ฒฌ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋๊ธ์ ๋จ๊ฒจ๋ณด์ธ์!
๋๊ธ ์์
๋๊ธ ์ญ์