이번 얘기를 하기 전에 앞에서 얘기한 내용들을 다시 한번 정리해 보자.
우선 첫 글에서 데이터베이스와 테이블에 대한 얘기를 했었다. 데이터베이스(Database)는 바깥 세상의 데이터 들을 파일 형태로 잘 저장하여 이용하기 위해서 만든 것이고, 테이블(Table)은 엑셀 같이 네모난 격자 형태로 데이터를 추상화 해 저장한 것이다(그렇다고 실제 데이터가 네모나진 않을 것이다). 데이터의 의미를 나타내는 세로 줄인 컬럼(Column)이라는 개념이 있고, 가로 줄은 로(Row)라고 해서 데이터의 행 수 또는 개 수를 나타낸다. 테이블의 제일 큰 장점은 사람에게 친숙한 네모난 칸 들로 보이게 만들어져 꽤 직관적이고 가시적이라는 것이다. 요즘의 데이터 세상에서 볼 수 있는 눈에 보이지도 않고, 수학적으로 상상해야 되는 다 차원 데이터 구조 들에 비해서는 특히 말이다.
두 번째 글에서 방 안의 특정 서랍 안에서 야구 공을 찾는 그림을 통해서, 데이터베이스에서 하나의 테이블 안의 데이터 행의 컬럼 하나를 찾아오는 예제를 보여주고, 영어의 문법 순서를 기반으로 Select, Insert, Delete 그리고 테이블을 만드는 Create 문까지 체크해봤다(지금 뒤돌아 보니 데이터를 변경하는 Update 문을 빼놓긴 했지만 Update 문도 "UPDATE 특정 컬럼을 어떻게 WHERE 어디에서"의 형태로 delete 와 거의 비슷하다고 보면 된다). 그 과정에서 테이블을 만들 때 쓰는 DEFAULT, NOT NULL 등의 키워드 들을 잠시 언급하며 그런 키워드들이 테이블을 사용하는 다양한 외부 환경(프로그램)에 대해서 테이블 안의 데이터들의 정합성을 잘 지켜주는 편리한 도구라는 얘기를 했었다.
세 번째 글에서는 잠시 하나의 테이블을 벗어나서 (두 개의) 테이블 간의 관계를 나타내는 썸과 비슷한 JOIN 이라는 키워드와 약간의 변형인 짝사랑 같은 LEFT JOIN 에 대해서 얘기하면서 텅 비어있는 NULL 이라는 키워드에 대해서도 지나가며 언급 했다. 그 담엔 UNION을 통해서 서로 같거나 이질적인 테이블들의 컬럼이 서로 힘을 합쳐 하나의 테이블 같이 동작하는 부분을 얘기하고, 이 부분에서 SQL 쿼리의 결과 또한 하나의 가상의 테이블 형태여서 그걸 자유롭게 사용할 수 있다는 것도 슬쩍 얘기한 것 같다. 그리고 하나의 테이블 안의 사건이긴 하지만 데이터 들 간의 관계를 그룹 짓는 GROUP BY 를 얘기하며, 단체 안에서의 개인의 사라짐과, 그 대신 생겨나는 수치적 통계들에 대해서 얘기 한 듯 하다.
테이블들이 혼자서 또는 여럿이서 움직이는 부분을 보여줬고, 그 결과 또한 가상이나 실제의 테이블로 귀결 된다는 것을 보여줬으므로, 이제 가져온 데이터를 어떻게 표현하고, 또 데이터를 가져오는 과정을 어떻게 좀 더 정교하게 하는 지에 대한 문법적인 얘기만 남은 것 같다.
앞에서 설명한 부분과 연결한다면 "SELECT *** FROM *** WHERE ***" 에서 SELECT 와 FROM 사이에 어떤 일이 생기고, WHERE 뒤에 어떤 일이 생길 수 있느냐 에 대한 얘기이다. 사실 이 부분은 우리가 업무에서 많이 사용하는 엑셀을 상상해 보면 쉽게 알 수 있는 부분이기도 하다. 우리가 엑셀에 특히 구조화된 표 모양의 데이터를 넣은 뒤 주로 하는 일이 바로 그 데이터 들을 조합해 새로운 데이터를 만들어 내거나, 중복 데이터를 삭제 하거나, 합하거나, 조건 문을 걸어 조건에 따른 분류를 하거나 같은 일이다. 또한 데이터를 정렬하거나, 특정 조건이나 날짜의 데이터만 걸러내거나 특정 샘플 만을 가져오거나 하려 한다.
이와 비슷한 일을 우리는 SQL 문장을 통해서 할 수 있다고 보면 된다. 다만 엑셀에서 표 형태의 데이터를 조작할 때의 제약들처럼 같은 형태의 데이터를 가지고 정리한다고 볼 수 있기 때문에, 아마 처음 사용하다 보면 보기에는 잘 돌아갈 거 같은 쿼리들이 에러가 나기도 할 것이다. 해당 부분은 요즘 같이 AI 로 쿼리를 만들어 사용하다 보면 결코 만나지 못할 부분일 수도 있겠지만, 앞에 얘기한 관계들을 머리 속에 개념화 시키는데는 좋은 경험들이므로 한번쯤은 만나 고민을 해봤음 하는 바램도 있긴하다. AI는 일종의 자동화의 산물이라고 생각하는데 모든 자동화는 수동화의 실패 경험이 기본으로 배경으로 있을 때 가장 세련되게 구현될 수 있다고 보기 때문이다.
[SELCET 와 FROM 사이의 요소들]
앞에서 일반적으로 모든 SELECT 쿼리는 결국은 테이블 모양의 데이터가 반환 된다고 했다. 해당 부분을 상상해 보면 반환 되는 것은 하나의 셀(Cell)일 수도 있고, 하나의 줄(Row)일 수도 있고, 하나의 열(Column)일 수도 있고, 네모난 상자일 수도 있다(결국은 네모난 상자에 모두 속하긴 한다). 보통 여기서 사용하는 연산자 들은 열을 기준으로 동작을 하게 된다.
우선 결과로 보여주는 열에 대해서 SUM(합), COUNT(행 수), AVG(평균) 같은 부분을 결과로 낼 수 있다. 일반적으로는 이 함수를 사용하려면, 앞에서 설명한 GROUP BY 등으로 데이터를 묶어 사용하거나 테이블 전체에 대해서만 기본적으로 사용이 가능하다고 볼 수 있다. 그래서 만약 테이블에 직원 이름, 연봉이 있고, 결과로 직원 이름, 연봉, 회사 전체 연봉을 표시하고 싶었다면 아래와 같이 조금 비 효율 적인 서브 쿼리가 필요했었다.
여기서부터 드물게 아래 사이트에서 쿼리 10개까지는 SQLITE 기준으로 데이터 저장부터 실습을 할 수 있음을 발견하여 사이트도 구경할 겸 현재 쿼리만 한번 실습을 해보자. 무료 사용의 경우 3시간 후나 브라우저를 종료했다 다시 열면 데이터가 모두 사라지는 듯 하니 참고하자. 기본적으로 서브스크립션 대여를 목적으로 한 사이트 같아 너무 쿼리를 자주 날려도 실례일 듯 하고, 또한 아마 해당 제한으로는 좀 더 연속적인 쿼리 과정에 대한 실습은 못할 듯 하기도 하고, 로컬 디비이긴 하지만 하나 설치해 보는 경험도 나쁘진 않을 듯 해서, 다음 시간에는 SQLITE 를 PC에 함 설치해 사용해 보도록 해보자.
https://sqliteonline.com/

화면에서 위쪽에 쿼리를 넣고 "Run" 버튼을 실행하면, 왼 쪽에 생성한 테이블 이름이 나오고, 아래 쪽에서는 조회의 경우에는 결과가 나오게 된다(처음엔 아래 부분을 한번 클릭해 가이드를 지워야 하는 듯도 싶다).
우선 테이블 생성 쿼리를 돌려서 테이블이 생성 됬음을 확인 하고,
CREATE TABLE employees (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
salary INTEGER NOT NULL
);
이후 데이터를 넣어보자, 앞의 글에서 살짝 언급 했듯이 id 라는 값은 자동 증가 하는 값이 들어가기 때문에 입력 시 생략을 해도 에러가 나지 않으면서도 일련번호가 생겨 관리하기 좋아진다.
INSERT INTO employees (name, salary) VALUES
('박지민', 3500),
('이영희', 5000),
('홍길동', 4000);
아래가 바로 전통적인 해당 테이블을 이용해, "직원 이름, 연봉, 직원 전체 연봉" 을 보여주는 쿼리이다. 쿼리를 보면 컬럼 안에 서브 쿼리 형태의 쿼리가 하나 통째로 들어가서 전체 연봉을 sum 을 해서 컬럼 자리를 차지한다(앞에서 얘기했지만 모든 결과가 결국 테이블 형태기 때문에 이런 유연한 끼워 넣음이 가능한 듯 싶다)
SELECT
e1.name,
e1.salary,
(SELECT SUM(e2.salary)
FROM employees e2) AS dept_total
FROM employees e1;

그런데 위의 방식은 데이터베이스 최적화 엔진에 따라서 모든 행에서 반복으로 SUM 에 관련된 서브 쿼리가 실행될 수 있었기 때문에, 그래서 AI 가 하는 얘기로는 예전에는 아래와 같이 한번만 조회가 일어나게 되는 좀 복잡해 보이는 방식을 추천했다고 하는데, 현재는 최신의 컴파일러들이 알아서 비 효율 적인 코드를 실행 시 최적화 하듯이 위와 같이 작성해도 아래와 같이 실행해 줄 수도 있을 듯 싶다. 이런 게 보통 데이터베이스에서 얘기하는 쿼리 튜닝 요소이고 말이다.
SELECT
e1.name,
e1.salary,
total.dept_total
FROM employees e1
CROSS JOIN (
SELECT SUM(salary) AS dept_total FROM employees
) total;
앞의 부분을 전통적인 이라고 얘기했던 이유는 이 방법을 안 쓰는 뒤의 새로운 문법들은 SQLITE 같은 경우는 2014~2018년에, 상용인 Oracle 이나 MSSQL 은 2000년 초에 생겼다고 한다. 또한 신 기능들은 아마 초기의 성능 개선이나 여러 요소 때문에 DBA 팀에서 사내 쿼리 표준에 보수적으로 포함 안 하는 경우가 많았을 터이기 때문에 아마도 2010년 중 후반에나 퍼졌을 것 같고, 그 후에 오픈 소스 디비들이 그 편리함들을 차용해 도입했을 것 같다. AI 에게 의견을 물어보면 하드웨어의 발전과 용량 확대 또한 많은 기여를 했다는 데, 하나의 서버가 모든 걸 커버해야 하는 RDB 의 경우는 특히 더 많은 영향을 받지 않았을까 싶다. 이건 뭐 주위의 좀 연식이 된 DBA분들이 있으면 함 여담으로 물어봐도 될 듯 하다.
그 다음에 나온 두 가지 방법이 OVER 와 공식적으로 CTE(Common Table Expression, 공통 테이블 식)이라고 얘기하는 WITH 인데, 먼저 OVER 를 보면 앞에서 설명한 GROUP BY 역할을 하게 되며 이런 부분에 특화가 되었다. 저 OVER 는 다른 요즘 함수인 RANK 나 LAG 등과 결합하면 그룹핑이나 정렬 함수가 들어가게 되는 거 같다.
SELECT
name,
salary,
SUM(salary) OVER () AS dept_total
FROM employees;
마지막으로 마치 프로그램에서 변수를 저장하듯 테이블을 잠시 저장해 쓰는 WITH 라는 문구를 쓸 수 있다. 파이썬이라면 pandas 테이블이 하나 있는 떠 있는 듯 싶다고 표현하고 싶다.
WITH GrandSum AS (
SELECT SUM(salary) as total FROM employees
)
SELECT
e.name,
e.salary,
g.total as grand_total
FROM employees e
CROSS JOIN GrandSum g;
아마 레거시 SQL 만 주로 보다 보면 위의 쿼리들이 조금 신기해 보일 수도 있을 것 같긴한데, 데이터 추출 하는 쪽 같은 데서는 자주 볼 수 있는 것 같기도 하고, 기존 문법의 비효율성과 성능을 개선하기 위해 만든 기능 들이고 성능 적으로도 충분히 안정화가 된 것 같으니 DBA쪽과 의논해 유용하게 써도 무방하지 않을까 싶다. 다만 해당 사용이 결국 뒤에 나오는 테이블의 인덱스 같은 부분들과도 연관 되니 그냥 내가 쓰고 싶다고 언제든 쓸 수 있는 부분은 아닌 듯은 싶다.
일단 하나의 자세한 예제를 보았으니 나머지는 후루룩 넘어가 보려고 한다. 위의 SUM 과 비슷하게 행의 수를 세는 COUNT와 평균을 계산하는 AVG 가 있으나 이것은 결국 데이터 군을 어떻게 해석하려 하느냐의 차이이니 굳이 추가적인 예제는 필요 없을 듯 싶다.
다음으로는 CASE 문이 있는데 이건 프로그래밍 에서도 많이 보이는 형태이기도 하고, 사실 나중에 프로시저라는 반복이나 여러가지 조건 처리를 위해 여러가지 쿼리들이 조합된 방식으로 만들려고 하다 보면 결국 스크립트 형태의 프로그램이 되어버리긴 한다(다만 우리가 사용하는 SQLITE 는 경량 화에 특화되고 그런 복잡한 로직은 프로그램을 통해 해결하라는 철학을 가지고 있기 때문에 프로시저 문법은 지원 안 한다고 한다).
문법을 보면 CASE WHEN ELSE END 가 주인공 이며 결과는 결국 하나의 값이 된다. AI 한테 영어로 함 문장을 만들어 달라고 해봤다.
CASE
WHEN life gives you lemons
THEN make lemonade
ELSE just smile and stay positive
END
예를 들어 앞의 예에서 전체 직원 월급의 평균 보다 같거나 높으면 "이상", 낮으면 이하라고 표기하고 싶다면, 앞의 현대적인 문법인 OVER 를 배웠으니 그걸 이용해서 만들면 아래와 같다.
SELECT
name,
salary,
CASE
WHEN salary >= AVG(salary) OVER() THEN '이상'
ELSE '이하'
END AS salary_status
FROM employees;
또한 앞에서 얘기한 짝사랑의 관점에서 의미가 없이 비어있는 NULL 값을 치환해 주는 함수들도 있는데, IFNULL 이나 현재에 많이 쓰인다는 COALESCE 라는 키워드가 있다고 한다. 저런 예전 키워드 들은 약간 상황에 따라서 모양이 다른 경우가 있다. 예를 들면 IFNULL 이 MSSQL 에서 ISNULL 이고, 오라클에서는 NVL 이라고 한다. COALESCE 는 공통으로 사용되며 인자가 여러개 들어가 A 가 아니면 B , 아니면 C 로,... 해서 대체제를 다수 찾아줄 수 있어 더 유연성이 좋다고 한다.
직원들의 평균 연봉을 구하는데 연봉 금액을 비워 놓은 직원이 있어서(Null 존재) 해당 경우는 어림 잡아 4000으로 잡아서 계산하는 쿼리를 만들어 보자. 만약 저런 예외 처리가 없다면 Null 은 데이터베이스의 종류에 따라 맘대로 0으로 처리되거나 집계에서 빠져버릴 수 있다고 한다. 뭐랄까 이런 함수의 용도는 얘를 무시하지 말고 제도 권에 받아들이자의 의미라고 봐도 된다.
SELECT AVG(COALESCE(salary, 4000)) AS adjusted_average
FROM employees;
다른 테이블에 거리 주소와 상세 주소가 나눠져 있다고 보자. 2개를 가져와서 프로그램에서 합쳐도 될 수 있겠지만, 결과 자체에서 그렇게 보고 싶다면 SQL 종류에 따라 특정한 연산자(||, +)나 CONCAT_WS 같은 문자열을 합치는 키워드 들이 있다. AI 가 만들어준 예제에서는 친절하게도 혹시나 상세 주소가 비어서 에러 날 경우를 대비해서 앞에서 배운 COALESCE 함수로 NULL 일 경우를 대비해 주어서 그대로 가져왔다.
SELECT
name,
address_road || ' ' || COALESCE(address_detail, '') AS full_address
FROM employees;
다음도 약간 프로그래밍 적인 요소인데(뭐 근데 엑셀도 마찬가지지만 함수를 사용하기 시작하면 스크립트 기반의 프로그래밍 영역에 들어갔다고 보면 될 것 같다) 데이터의 형을 바꿔주거나 날짜 포맷 등을 바꿔줄 수 있다. 예를 들어 직원의 "년월일"로 들어간 입사일 데이터를 월 별로 GROUP BY 해 "년월"별 입사한 직원 수 통계치를 뽑기 위해서, 입사일 데이터를 일단 "년월"별로 변환 후 GROUP BY 하여 수를 세는 예제를 만들면 아래와 같다. 실제 입사일 데이터는 일 또는 시간까지 들어간 데이터일 수도 있지만 잘 변환해 원하는 결과를 가져온다.
SELECT
strftime('%Y-%m', hire_date) AS join_month,
COUNT(*) AS employee_count
FROM employees
GROUP BY join_month
[WHERE 뒤의 요소들]
생각보다 앞의 얘기가 길어진 듯 하지만 다음엔 WHERE 뒤이다.
사실 위의 WITH 나 OVER 가 나온 후에 이 WHERE 뒤 쪽이 SELECT 와 FROM 사이에서도 자주 보이게 됨으로서 조금 서로 간의 경계가 깨어진 듯도 싶기는 하지만(그래서 처음 배우는 사람은 더 헷갈릴 듯 싶다), 이 뒤에 나오는 키워드 들은 일종의 데이터 들을 제한하는 키워드 들이다. 요즘의 여러 다양한 빅데이터 솔루션들의 개념으로 생각하자면 필터링 역할의 키워드라고 보면 될것 같다. 실제로 사용하는 키워드들도 서로 비슷하고 말이다.
여기서도 전통적으로 보던 키워드와 현대의 프로그램 구조를 반영한 키워드들이 서로 어울려 있는 느낌이 있다. IN 이나 NOT IN인 같은 경우는 여러가지 중에 하나를 선택하는 구문이다. 앞의 예제에서 연봉이 3500이나 4000인 직원을 찾을 때 아래와 같은 정직한 쿼리를 쓸 수도 있겠지만
SELECT name, salary FROM employees
WHERE salary = 3500 OR salary = 4000;
실제로는 아래의 IN 쿼리가 더욱 간단하고 가독성도 좋다.
SELECT name, salary FROM employees
WHERE salary IN (3500, 4000);
저 IN 뒤에는 특정 값 뿐만 아니라 응용에 따라 다른 테이블의 제한된 조회 결과를 가져오는 서브 쿼리도 가능하다. 아래와 같이 2개의 테이블을 이용해 베스트셀러 테이블에 있는 과자들에 대한 정보만 가져오는 쿼리를 만들 수도 있다.
SELECT snack_name, price, calories
FROM snacks
WHERE snack_name IN (
SELECT snack_name
FROM best_sellers
);
BETWEEN ... AND 도 숫자나 날짜를 찾는 용도로 사용된다. 아래의 간단한 예제를 보자.
SELECT * FROM employees
WHERE salary BETWEEN 3500 AND 4000;
원하는 패턴을 찾는 정규표현식의 꼬마 버전이라고 할 수 있는 LIKE 검색도 있다. "김%"는 김으로 시작하는, "%김"은 김으로 끝나는, "%김%"는 가운데 김이 들어감의 의미이다.
SELECT * FROM employees
WHERE name LIKE '김%';
요즘은 나중에 자동화 얘기할때 설명하게 될 것 같은 정규표현식도 지원하는 디비들이 PostgreSQL, MySQL, Oracle 등도 있긴 하지만, 보통 정규표현식은 어떤 거를 찾느냐에 따라 시스템 리소스를 꽤 많이 차지 하는 작업으로 인지하고 있기 때문에 실제 현실에서는 얼마나 사용될지는 개인적으로는 잘 모르겠다.
또한 요즘 트랜드에 맞게 JSON 을 데이터에 넣고 해당 데이터를 파싱하는 부분도 지원하고 있긴 하지만 이것도 케이스에 잘 맞게 사용하는 경우가 얼마나 많을지는 모르겠다. 요즘은 컬럼 내 json 데이터의 요소를 가상 컬럼처럼 정리해 인덱스를 걸어 속도를 높인다든지 하는 부분이 자연스럽게 추가되는 것 같긴 하다(윈도우 안의 WSL 처럼 마치 RDB 내에 작은 NoSQL 을 키우는 느낌이다). AI 쪽에서는 하이브리드 방식으로 소규모의 json 트래픽을 정합성있게 저장하는 경우 유리하다고는 말하고 있다.
CREATE TABLE snack_orders (
id INTEGER PRIMARY KEY,
order_info TEXT
);
INSERT INTO snack_orders (order_info) VALUES (
'{"customer": "김철수", "items": ["홈런볼", "포카칩"], "total": 4500, "details": {"payment": "card"}}'
);
SELECT
order_info ->> '$.customer' AS customer_name,
order_info ->> '$.total' AS price
FROM snack_orders;
위의 케이스들에서 공통 되게 고려해야 할 부분이 한 가지 있는데, 그게 SQL 을 보다 보면 가장 자주 듣게 되는 인덱스 이다. 인덱스는 우리가 책장에 책을 한글 이나 영어 알파벳 순으로 쉽게 찾을 수 있도록 정리해 놓는 것과 비슷하다. 근데 보통 실제로 데이터 자체를 직접 정렬하는 건 아니고(보통 일련 번호 형태인 primary키 같은 경우는 이런다고 하는 듯 하다), 다른 저장소에 데이터의 식별 가능한 전체나 일부를 순서대로 저장한 것과 비슷하다고 볼 수 있다. 그래서 우리가 특정 데이터(문구)가 들어간 컬럼을 찾으려 할 때, 순서대로 잘 정리된 인덱스 테이블을 뒤져 거기서 실제 테이블 내의 데이터의 위치를 찾아서, 전체 컬럼 데이터를 순서대로 찾지 않아도 빠르게 데이터의 위치를 찾게 된다(인덱스가 없다면 원본 데이터는 기본적으로 랜덤으로 들어오는 대로 저장되었기 때문에 처음부터 하나하나 찾아야 하지 않을까 싶다. 이렇게 하나하나 찾는 걸 보통 full scan 이라고 한다).

특정한 한 개의 컬럼이나 몇 개의 컬럼을 묶어서 인덱스를 생성할 수 있는데, 세상에 공짜는 없어서 보통 테이블 내의 저장된 컬럼 내의 데이터 크기에 비례한 어느 정도의 공간을 인덱스 하나 당 차지하게 된다(원본 데이터를 구별할 일부 데이터를 색인 해야 하다 보니). 그래서 성능을 위해서 인덱스를 쿼리에서 요구하는 대로 한없이 늘리다 보면 배보다 배꼽이 더 커지는 일이 생길 수 있기 때문에 보통 어느 정도의 필수 인덱스들이 생성되게 되면, 이후에는 정말 어쩔 수 없는 경우가 아니면 DBA쪽에서 추가 인덱스는 잘 만들어 주려 하지 않는 듯 싶다. 특히 저장된 데이터 양이 몇 억 몇 십억으로 엄청 큰 테이블이라면, 추가 인덱스를 만드는 건 용량과 시간을 많이 잡아먹는 일이 될 테고 말이다.
인덱스는 작은 규모의 데이터를 가진 환경에서는 그다지 그 효용성을 인지하지 못하게 될 수도 있기 때문에, 디비를 전문적으로 관리하는 사람이 없는 회사에서는 프로그램이 인덱스를 잘 지키지 않고 호출할 수도 있다(어떤 경우는 인덱스 자체가 거의 없었다는 상황도 들어봤다). 해당 부분이 규모가 작을 때는 별 일이 없이 살다가 데이터의 규모가 커지면서 인덱스를 사용하지 않는 쿼리들의 결과가 느려지고 추가로 데이터베이스의 자원을 대부분 차지하게 됨으로서 전체 사이트의 속도를 느리게 만들 수 있는 바오밥 나무 같은 일이 생길 수도 있다. 또 그 정도로 관리하는 데이터베이스였다면 아마 앞에서의 여러 테이블들의 상관 관계도 그렇게 확장성에 유리하게 설계도 안 되었을 가능성도 높을 것이고 말이다. 앞에서 쿼리와 테이블을 이해하려면 디비를 호출하는 프로그램도 어느 정도 이해하는 게 좋다고 했는데, 반대로 바깥 세상의 프로그램을 잘 이해하려면 호출할 대상인 디비 내부의 테이블과 쿼리를 어느 정도 이해하는 게 반대로도 필요하다고 생각한다.
다음으로 WHERE 과 직접접인 관계는 없지만 앞에서 나온 OVER, WITH 같은 쪽이나 결과의 정렬 부분에 대한 키워드가 있다. 이건 엑셀의 데이터 정렬 기능을 생각하면 완전히 동일하다.
밑의 예제는 임직원 데이터에 대해서 연봉은 높은 순서(DESC)로, 만약 연봉이 같다면 차 순위인 이름이 빠른 순서(ASC)로 정렬해 보여준다.
SELECT name, salary
FROM employees
ORDER BY salary DESC, name ASC;
마지막으로 언급할 부분은 결과를 제한 하거나 건너 뛰는 부분이다. 실제 쿼리를 날리다 보면 데이터가 너무 많이 나와서, 필요한 데이터만 몇 개 보고 싶을 수 있다. 역사적인 이유 때문에 데이터베이스 별로 LIMIT(SQLITE, MySQL)나 TOP(MSSQL), ROWNUM, FETCH FIRST(ORACLE) 등으로 약간 씩 문법이 달라 처음에 다른 데이터베이스를 쓸 때 괜한 혼란을 주곤 한다. 거의 모든 데이터베이스에 있을 OFFSET 문법은 정렬된 순서의 데이터에서 특정 숫자를 건너 띄고 조회하는 게시판의 페이징 쿼리 등에서 사용된다(이 키워드가 없다면 프로그램이 전체 데이터를 가져와 화면에서 나눠서 보여줘야 할 텐데 생각만 해도 아찔하다). 아래는 기존 테이블에서 연봉이 높은 사람 순서로 2명(LIMIT)을 가져오는데, 가장 연봉이 높은 1명은 제외(OFFSET)하고 가져온다.
SELECT name, salary
FROM employees
ORDER BY salary DESC
LIMIT 2
OFFSET 1;
이렇게 되면 SQL 의 기본적인 문법이 왜 그렇게 구성되었는지는 아는 부분은 대략 설명했다고 생각이 든다. 설명한 방식이 좀 공감이 갔음 하는 바램이 있고, 서당개 식으로 배운 거기 때문에 혹시 틀린 내용이 있더라도 양해를 해줬음 한다. 다음 시간에는 좀 예전 스타일의 구조 같긴 하지만 AI와 의논하며 앞에서 얘기했던 책 대여점 프로그램에 필요한 테이블들을 만들고, 데이터를 넣고, 쿼리를 만들면서 이제까지 설명한 부분들을 녹여 복습하고 문법 설명에 대한 마무리를 할까 한다. 또 SQLITE 도 한번 설치해 보고(다른 디비와는 달리 SQL 서버라기 보다 편집기가 설치되는 느낌이긴 하지만) 데이터가 실제 컴퓨터에 어떻게 저장 되느냐도 간단하게 봐보자.
- Fin -
'프로그래밍' 카테고리의 다른 글
| SQL 이해해보기 #05 - 책 대여 프로그램 만들기 (0) | 2026.03.02 |
|---|---|
| SQL 이해해보기 #03 - 관계에 대해 살펴보기 (0) | 2026.03.02 |
| SQL 이해해보기 #02 - 쿼리가 만들어지는 과정 (1) | 2025.12.21 |
| SQL 이해해보기 #01 - 데이터베이스와 테이블 이해하기 (0) | 2025.12.21 |
| SQL 이해해보기 - 들어가면서 (0) | 2025.12.21 |