MariaDB 강좌 / COUNT·SUM·AVG·MIN·MAX로 집계하기
COUNT·SUM·AVG·MIN·MAX는 여러 행을 개수·합계·평균·최솟값·최댓값으로 요약합니다. COUNT(*)는 행을 세고 COUNT(열)은 NULL이 아닌 값을 세며, 다른 집계도 NULL과 빈 결과를 특별하게 처리하므로 분모와 미입력의 의미를 먼저 정해야 합니다.
집계할 대상과 측정 단위부터 정하기
상품 종류 수와 총 재고 수량, 평균 단가와 재고 금액 합계는 서로 다릅니다. 상품 한 행이 무엇을 나타내는지, 숫자의 단위가 원인지 개인지, 같은 상품이 여러 행에 반복되는지 먼저 확인합니다. 숫자를 더하는 문법만 맞아도 업무상 합계가 틀릴 수 있습니다. 그룹마다 별도 합계가 필요하면 GROUP BY·HAVING으로 묶고 집계 결과 걸러내기으로 확장합니다. 테이블을 연결한 뒤에는 여러 테이블 JOIN하기: 중복 행과 집계 오류 확인처럼 행 증가로 합계가 중복되는지도 확인해야 합니다.
| 요구 사항 | 표현 | 단위 |
|---|---|---|
| 상품 행 수 | COUNT(*) | 행 |
| 알려진 메모 수 | COUNT(note) | NULL이 아닌 값 |
| 재고 총수량 | SUM(stock) | 개 |
| 평균 재고 | AVG(stock) | 상품당 개 |
| 재고 금액 | SUM(price*stock) | 원 |
메모 상태와 재고가 섞인 상품 데이터 준비하기
학습용 MariaDB 서버의 shopcourse에 course_user로 접속합니다. 명령줄 클라이언트와 UTF-8 SQL 파일 실행 방법대로 코드를 UTF-8 파일에 저장하고 SOURCE C:/mariadb-lab/lesson.sql;처럼 실제 파일 경로로 실행할 수 있습니다. SQL 문은 운영체제 터미널이 아닌 MariaDB 연결 안에서 실행합니다.
먼저 SELECT VERSION(), DATABASE();와 SHOW TABLES;로 대상 서버·DB·이름 중복을 확인합니다. 아래 products_22_lab는 이 글의 독립된 연습용 이름입니다. 이미 존재하면 정의와 행을 확인하고, 보관할 자료가 없는 이 글의 실습 테이블임이 확실할 때만 마지막 절의 정리 순서로 지운 뒤 다시 만듭니다. 기존 강좌의 동명 없는 테이블이나 운영 데이터는 삭제하지 않습니다.
USE shopcourse; CREATE TABLE products_22_lab ( product_id INT NOT NULL AUTO_INCREMENT, product_name VARCHAR(100) NOT NULL, category VARCHAR(30) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, note VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (product_id), CONSTRAINT ck_p22_price CHECK (price >= 0), CONSTRAINT ck_p22_stock CHECK (stock >= 0) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO products_22_lab (product_id,product_name,category,price,stock,note) VALUES (1, '노트', '문구', 3500.00, 20, NULL), (2, '볼펜', '문구', 1500.00, 0, ''), (3, '머그컵', '생활', 8000.00, 12, '선물 포장'), (4, '스탠드', '생활', 25000.00, 5, NULL), (5, '파일', '문구', 1500.00, 50, ''), (6, '케이블', '전자', 8000.00, 0, '호환 확인');
여섯 행 중 메모가 NULL인 행은 두 개, 빈 문자열인 행은 두 개입니다. 재고가 0인 상품도 실제로 존재하는 상품이므로 행 개수에 포함합니다. NULL 처리의 의미를 함께 확인하면 집계에서 값이 빠지는 이유를 이해하기 쉽습니다.
한 번의 조회에서 서로 다른 집계를 비교하기
SELECT COUNT(*) AS all_rows,COUNT(note) AS known_notes, SUM(stock) AS stock_total,AVG(stock) AS stock_average, MIN(price) AS lowest_price,MAX(price) AS highest_price, SUM(price*stock) AS inventory_total FROM products_22_lab;
| 결과 열 | 예상 값 | 해석 |
|---|---|---|
| all_rows | 6 | 모든 상품 행 |
| known_notes | 4 | 빈 문자열은 값이므로 포함 |
| stock_total | 87 | 20+0+12+5+50+0 |
| stock_average | 14.5와 같은 값 | 표시 소수 자릿수는 타입에 따라 다름 |
| lowest_price / highest_price | 1500.00 / 25000.00 | 가격의 경계 |
| inventory_total | 366000.00 | 행별 가격×재고를 합산 |
AVG(stock)는 행마다 stock을 읽고 NULL이 아닌 값의 합을 해당 값 수로 나눕니다. 0은 평균의 분모와 분자에 포함됩니다. NULL은 미확인 값이고 0은 확인된 수량이 0이라는 차이를 유지하세요. COUNT의 별표·열 기준와 AVG의 NULL 처리를 참고할 수 있습니다.
조건에 맞는 행이 없을 때 확인하기
SELECT COUNT(*),SUM(stock),AVG(stock),COALESCE(SUM(stock),0) FROM products_22_lab WHERE price < 0;
결과는 0, NULL, NULL, 0입니다. COUNT는 행이 없으면 0이지만 합계와 평균은 NULL이므로 화면 표시와 실제 계산의 의미를 분리합니다. 재고 금액이 없다는 의미로 합계를 0으로 표시할 수 있어도, 표본이 전혀 없는 평균을 실제 평균 0으로 기록하는 것은 다른 판단입니다. SUM의 빈 결과 처리를 확인하세요.
행별 계산과 집계 순서를 구분하기
SUM(price*stock)는 각 행의 재고 금액을 먼저 구해 더합니다. SUM(price)*SUM(stock)는 전체 가격 합과 전체 재고 합을 곱하므로 전혀 다른 값입니다. 결과가 커 보인다는 이유로 무조건 소수점 문제라고 판단하지 말고 계산의 대상과 순서를 먼저 확인합니다.
SELECT product_id,price,stock,price*stock AS row_value FROM products_22_lab ORDER BY product_id;
MIN(price)와 MAX(price)는 가격 값만 반환하며 그 가격의 상품명까지 자동으로 골라 주지는 않습니다. 같은 최저가 상품이 여러 개일 수 있으므로 실제 상품 목록이 필요하면 가격 조건이나 정렬 기준을 별도로 정합니다. 집계 옆에 임의의 비집계 열을 붙여 대표 상품이라고 해석하지 않습니다.
중복·누락을 확인하고 연습 데이터 정리하기
처음에는 기본키를 포함한 전체 행을 조회하고 수기로 작은 합계를 대조합니다. 원래 여섯 상품이 맞는지, NULL이 0으로 임의 치환되지 않았는지, JOIN으로 행이 늘어난 결과에 바로 합산하지 않았는지를 확인합니다. 큰 값의 합계는 결과 타입과 애플리케이션의 숫자 처리 범위도 함께 검토해야 합니다.
반복하기 전 현재 DB와 실제 정의·행을 확인하고 필요한 자료를 백업합니다. 보관할 데이터가 없는 이 글의 전용 실습 객체만 아래 순서로 정리합니다. 외래키가 있으면 자식부터 지웁니다. DROP TABLE은 구조와 데이터를 모두 없애며 일반적인 ROLLBACK으로 복원되지 않으므로 취소 가능한 DML 실습과 분리합니다.
DROP TABLE shopcourse.products_22_lab;










