SQL을 배우다가 가장 머릿속에 “그림”이 안 그려지는 구간이 있다.
바로 JOIN/LEFT JOIN으로 테이블이 합쳐진 뒤, 거기서 GROUP BY로 “묶는 순간”이다.
GROUP BY는 “대표 1행”을 만들려고 한다.
그런데 SELECT *는 “모든 컬럼을 달라”고 한다.
이 둘을 같이 쓰면 SQL 입장에선 어떤 값을 대표로 남겨야 할지 결정할 근거가 없다.
“patients에 있는 patient_id는 admissions에도 무조건 있을 것”이라고 머릿속에서 전제해버렸다는 점.
아래 쿼리를 보자.
SELECT *
FROM patients
GROUP BY patient_id;
이건 SQL에게 사실상 이렇게 요구하는 것이다.
1) “patient_id별로 데이터를 묶어서”
2) “각 patient_id마다 대표 행 하나를 만들어라”
3) “그리고 그 대표 행의 모든 컬럼(*)을 보여줘라”
문제는 여기서 바로 터진다.
문제의 본질: “대표로 무엇을 남길 건데?”
patient_id는 그룹 기준이라 OK.
그런데 나머지 컬럼들은?
👉 선택 기준이 없으면, 결과는 일관된 답이 될 수 없다.
그래서 표준 SQL에서는 에러가 정상이다.
(수업에서도 “GROUP BY는 SELECT 안의 비집계 컬럼과 일치해야 함”을 강사님께서 언급하셨음.)
일부 MySQL 환경에서는 위 쿼리가 “실행”될 수 있다.
하지만 이건 논리적으로 정답을 만든 게 아니라, MySQL이 임의의 행 하나를 골라서 보여준 것에 가깝다.
👉 “그냥 되길래 썼다”가 사고로 이어짐
그렇기에 처음부터 제대로 써야 한다.
그래서 GROUP BY를 쓸 때는 보통 두 가지 중 하나를 한다.
예를 들어 “가장 최신 입원일” 같은 요구라면:
SELECT
p.patient_id,
p.name,
MAX(a.admission_date) AS max_date
FROM patients p
JOIN admissions a
ON p.patient_id = a.patient_id
GROUP BY p.patient_id, p.name;
MAX(admission_date)가 “대표 기준”이 된다.
patient_id, name은 “그룹의 키”로 명확해진다.
(수업자료의 핵심 문장 그대로 “GROUP BY는 SELECT 안의 비집계 컬럼과 일치해야 함”을 맞추고 있다.)
ex. 최신 입원 기록의 diagnosis까지 같이 가져오고 싶으면 MAX(date)만으론 부족할 수 있다.
왜냐하면 날짜는 뽑았는데, 그 날짜에 해당하는 diagnosis를 “같이” 뽑는 건 별개의 문제이기 때문.
이때는 보통:
ROW_NUMBER() 같은 윈도우 함수를 쓴다(DB 지원 시)사실상 p.name은 patient_id에 완전히 종속되어 있으니
(한 환자 id에 이름은 정확히 하나. 이를 함수 종속이라 함)
GROUP BY 뒤에 적지 않아도 MySQL에서는 실행될 수 있으나(함수 종속 인식)
엄격한 SQL 모드(Oracle)에서는 함수 종속을 인정하지 않기 때문에 에러가 발생한다.
✅ 그냥 SELECT에 집계가 아닌 컬럼이 있다면 → GROUP BY에도 적어라.
patients의 모든 patient_id가 본래 admissions에도 전부 있을 것
그리고 입원하지 않은 환자는 admission_date 값이 'NULL'로 채워져 있을 것
DB의 특징에 대해 이해한다 해놓구 진짜 제대로 이해한게 아니란걸 오늘 깨달음...
(1) 왼쪽(A) 테이블의 모든 행은 반드시 출력
(2) 오른쪽(B)에 매칭되는 값이 있으면 채우고
(3) 없으면 NULL로 표시
즉, admissions 테이블 “원본”에 NULL이 있는 게 아니다.
LEFT JOIN 결과셋에서만 “매칭 실패한 컬럼 자리”가 NULL로 채워진다.
admissions는 “입원 기록” 테이블이니까
애초에 “입원하지 않은 환자”라는 행 자체가 존재하지 않는다.
그 빈자리를 LEFT JOIN이 NULL로 채우는 거다.
이 한 문장으로 머릿속 이미지가 완전히 바뀐다.
수업자료에 이런 경고가 있다.
ON: 테이블 연결 조건
WHERE: 연결 후 필터링 조건
순서가 어긋나면 데이터 누락이 나올 수도 있음
특히 WHERE 안에 JOIN 조건을 쓰면 데이터가 잘리는 경우가 많음
LEFT JOIN을 쓴 다음에, 오른쪽 테이블 컬럼으로 WHERE 필터를 걸면 어떤 일이 생길까?
SELECT *
FROM patients p
LEFT JOIN admissions a
ON p.patient_id = a.patient_id
WHERE a.admission_date >= '2020-01-01';
입원 기록이 없는 환자들은 a.admission_date가 NULL
WHERE 조건에서 NULL >= '2020-01-01'은 TRUE가 아니다
결국 그 환자들은 결과에서 제거됨
👉 겉으로는 LEFT JOIN인데, 결과는 INNER JOIN처럼 줄어든다.
SELECT *
FROM patients p
LEFT JOIN admissions a
ON p.patient_id = a.patient_id
AND a.admission_date >= '2020-01-01';
이 두 쿼리는 겉보기엔 조건 위치만 바뀌었지만 결과가 완전히 달라진다.
# 기본
SELECT column1, column2, ... # 출력 하고자 하는 컬럼명
FROM table_name
WHERE 조건 # 조건이 있는 경우 작성
ORDER BY column ASC; or DESC; # 정렬이 필요할 때 작성(항상 WHERE 뒤!) ASC: 오름차순, DESC: 내림차순
SELECT column명 AS 별칭 FROM table_name; # Column 별칭
SELECT ... FROM table_name AS 별칭; # Table 별칭
SELECT DISTINT column명 FROM table_name;: 중복 컬럼 제거HAVING 조건;: GROUP BY 결과를 다시 필터링(GROUP BY 이후에만 사용 가능)LIMIT 개수; or LIMIT offset, 개수;: 결과 개수 제한
[실습1 문제 내용]

[실습 1-1 코드 작성 결과]

[실습 1-2 에러 발생 코드]
알맞게 작성한 것 같은데 실행이 안되고 에러가 떴다.
찾아보니 FROM, JOIN 구문에서 별칭을 지정해줬으면 쭉 별칭으로 써야한다고...
별칭으로 전부 수정해주니 정상적으로 실행이 됐다.

[실습 1-2 해결 코드]

[실습2 문제 내용]

[실습 2-1 에러 발생 코드]
DISTINCT의 위치는 무조건 SELECT 뒤.
(그리고 DISTINCT는 중복 열 제거가 아니다. 중복 행 제거임!!)
중복되는 patient_id를 없애고 싶으면 그냥 SELECT할 때 admissions에서 patient_id를 안가져오면 됨.
코드1(출력 불가)

코드2(patient_id 열 2개)

코드3(patient_id 열 1개)

👉 DISTINCT는 patient_id만 보는 게 아니라 (patient_id, admission_date) 조합 전체를 본다
그래서
은 다른 행 → 제거 대상 아님
(patient_id 컬럼이 하나인 것은 애초에 admissions table에서 가져오지 않았기 때문)

여기서 생긴 착각:
“어? patient_id 컬럼이 하나만 남았네? DISTINCT가 중복 열을 지운 건가?”
❌ 아니다. 실제로 무슨 일이 일어났나?
① p.* 안에는 patients.patient_id 하나만 존재
② a.patient_id는 SELECT 목록에 없음. 그래서 출력 자체가 안 됨
③ DISTINCT는 (p의 모든 컬럼 + admission_date) 조합 기준으로 행만 비교
👉 열이 줄어든 게 아니라, 애초에 SELECT하지 않았을 뿐

[실습 2-1 해결 코드]

[실습 2-2 에러 발생 코드]
만약에 모든 환자의 담당의를 출력하되 아직 담당의 배정 안 된 경우도 출력하라면 patients 기준이 맞음.
하지만..?
문제가 "모든 의사의 담당 환자 정보를 출력하라.", "환자 배정 전이어도 출력하라."
(환자가 아직 배정이 안된 이제 막 사원만 받은 의사도 존재할 수 있으니..,?)이기 때문에
doctors를 기준으로 LEFT JOIN 하는 것이 맞다.

[실습 2-2 해결 코드]

[애초에 id만 부여받고 환자 배정이 안된 의사가 존재하지 않음]
다만 이번 문제에서 patients 기준으로 해도 의사 정보가 모두 불러와졌던 것이
애초에 신입이 없었기 때문에 모든 의사가 다 담당하고 있어서지
만약 신입 의사 정보가 존재했다면 결과는 달라졌을 것.

[실습3 문제 내용]

[실습 3-1 코드 작성 결과]

[실습 3-2 코드 작성 결과]

[실습4 문제 내용]

[실습 4-1 코드 작성 결과]

[실습 4-2 에러 발생 코드]