SQL에서 쿼리를 짜다 보면, NULL 값과 비교할 일이 꽤 많습니다.그런데 아래처럼 작성했을 때, 결과가 전혀 나오지 않았던 적 있지 않나요?처음에는 문법 오류가 아니라서 문제를 눈치채지 못하기도 합니다.그런데 결과가 비어 있는 걸 보면 이상하다는 느낌이 들죠.SQL
날짜를 필터링할 때 아래처럼 BETWEEN을 사용하는 경우가 많습니다.한 달 치 데이터를 뽑고 싶을 때 자주 쓰는 패턴이죠.하지만, 실무에서는 이 방식이 의도와 다른 결과를 낼 수 있다는 점, 알고 계셨나요?BETWEEN은x <= 값 <= y 구조로 동작하기
고객 ID처럼 문자 + 숫자 조합으로 구성된 값을 정렬할 때,예상과 다른 순서가 나오는 경우가 있습니다.예를 들어, 고객 번호가 'cust1'부터 'cust99'까지 있다고 가정해볼게요.이제 아래처럼 정렬을 수행하면 어떻게 될까요?❗ 예상과 다른 정렬 결과실제로는 아래
SQL에서 행 수를 셀 때 자주 사용하는 COUNT() 함수.비슷해 보이는 아래 두 가지 방식, 결과가 항상 같을까요?COUNT(\*)는 NULL 여부와 관계없이 모든 행을 집계합니다.즉, 테이블에 존재하는 전체 행 수를 반환합니다.COUNT(컬럼명)은 해당 컬럼이 N
컬럼에 별칭(alias)을 붙일 땐 보통 이렇게 작성합니다 :그런데 실수로 아래처럼 작성하는 경우가 있습니다 :겉보기엔 문법상 괜찮아 보일 수 있지만, 실제로는 에러가 나거나 결과가 예상과 다르게 나올 수 있어요.SQL에서 '이름'처럼 작은따옴표로 감싸면별칭이 아니라
LEFT JOIN을 썼는데도, 예상보다 결과가 적게 나오는 경우가 있습니다.쿼리 문법은 틀리지 않았지만, 데이터가 일부 누락된 느낌이 들죠.이럴 땐 WHERE절에 걸린 조건을 의심해볼 필요가 있습니다.이 쿼리는 LEFT JOIN을 썼지만, WHERE o.status =
집계 함수를 조건에 넣었더니 쿼리가 실행되지 않았던 적 있으신가요?그럴 땐 WHERE가 아니라 HAVING을 써야 할 타이밍입니다.이 쿼리는 실행되지 않고 오류가 발생합니다.📌 PostgreSQL Error 예시WHERE 절은 GROUP BY보다 먼저 실행되기 때문에
비율을 계산했는데, 쿼리는 문제 없어 보이는데 결과가 전부 0으로 나왔던 적 있으신가요? 예를 들어, 전체 사용자 중 몇 명이 구매로 전환되었는지를 계산한다고 할 때: ❌ 문제 상황 쿼리 자체는 틀리지 않았지만, 전환율이 0 또는 1로만 나오는 현상이 발생할 수
값이 올랐는지, 상태가 바뀌었는지 확인하려면 직전 값과의 비교가 필요합니다.이럴 때 사용하는 함수가 바로 LAG()입니다.LAG(stock_price)로 전일 주가를 불러오고오늘 가격과 비교해 상승 여부를 1/0으로 표시✔️ LAG()는 이전 값을 가져올 때 사용됩니다
쿼리를 짜다 보면,같은 로직을 반복해서 쓰거나,쿼리 흐름을 나누고 싶을 때가 있습니다.서브쿼리와 CTE(Common Table Expression)는 이럴 때 사용할 수 있는 선택지인데요.둘 다 쓸 수 있지만,용도와 상황에 따라 더 나은 선택이 달라집니다.→ 결과는 같
쿼리를 짜다 보면,중복이 생길까 봐 DISTINCT를 습관처럼 붙이는 경우가 있습니다.하지만 항상 붙이는 건 좋은 습관이 아닙니다.데이터 구조에 따라 성능에 불필요한 연산이 추가될 수 있기 때문입니다.📌 트랜잭션 테이블 (ex. orders)→ 한 유저가 여러 번 주
UNION은 두 쿼리 결과를 합친 뒤 중복을 제거합니다.하지만 중복이 없다는 걸 이미 알고 있다면,굳이 UNION을 쓸 이유는 없습니다.이럴 땐 UNION ALL이 더 빠르고 가볍습니다.→ 중복이 없다는 걸 알고 있다면, UNION ALL이 쿼리 비용을 줄이는 더 좋은
인덱스를 걸었는데도 쿼리가 느리게 실행된 적 있으신가요?그 원인 중 하나는, WHERE절에서 컬럼에 함수를 적용했기 때문일 수 있습니다.→ order_date에 인덱스가 있어도, 컬럼에 함수가 적용되면 인덱스를 타지 못하고 전체 테이블을 스캔하게 됩니다.→ 이렇게 함수
순위를 매기는 쿼리,어떤 함수를 써야 할지 헷갈렸던 적 있으신가요?ROW_NUMBER, RANK, DENSE_RANK는 모두 순위를 구하지만동점 처리 방식이 다릅니다.ROW_NUMBER() → 동점 상관없이 순서대로 번호 부여 → 무조건 1, 2, 3, 4, ...RA
SQL에서 테이블을 결합할 때, INNER JOIN이나 LEFT JOIN은 자주 사용하지만, CROSS JOIN은 상대적으로 덜 쓰입니다. 하지만 모든 경우의 조합이 필요할 땐 CROSS JOIN이 필요합니다. __ ✅ 예시 : 상품 × 날짜 조합 예를 들어, 상
SQL에서 ORDER BY는 결과를 보기 좋게 정렬해주는 기능이지만,데이터를 정렬하는 연산이 추가되기 때문에 성능 저하의 원인이 될 수 있습니다.• ORDER BY는 데이터를 한 번에 다 읽어서, 순서를 매기고, 다시 배열하는 과정을 거칩니다.• 데이터 양이 많아질수록
SQL 작성 시 조인 순서에 따라 쿼리 속도가 몇 배 차이 날 수 있습니다. ⸻ ✅ 왜 순서가 중요한가? SQL은 작성한 코드 순서대로 실행되지 않습니다. DB 엔진은 쿼리를 최적화를 위해 실행 계획(Execution Plan)을 따로 만들어 처리합니다. 그 과정
사용자 행동 데이터를 분석하다 보면 단순히 "구매 수가 몇 건이다" 같은 수치만으로는 부족할 때가 많습니다.그보다는 사용자가 어떤 경로를 통해 구매에 도달했는지, 그리고 어디서 이탈했는지를 파악하는 쪽이 더 실질적인 인사이트로 이어질 수 있습니다.이를 위해 흔히 사용하
SQL에서 NOT IN을 사용하다가, 결과가 예상과 다르게 아예 안 나온 적은 없으신가요?왜 그런 결과가 나왔을까요?SQL에서 NULL은 "값이 없다"는 상태이기 때문에,어떤 값과도 직접 비교할 수 없습니다.따라서 id != NULL 같은 조건은 항상 UNKNOWN으로
MAU 수치만 보면 유입이 활발해 보일 수 있지만, 실제로는 반복해서 방문하는 유저가 거의 없을 수도 있습니다. 그래서 단순히 MAU만 보기보다, stickiness(고착도)까지 함께 확인하는 것이 실제 사용 행태를 이해하는 데 유용할 수 있습니다. ❓ sticki
SQL에서 조건별 집계 값을 구할 때, 보통 CASE WHEN을 많이 사용하죠.하지만 쿼리가 길어지거나, 조건이 반복되면 가독성이 떨어질 수 있어요.그럴 때 FILTER (WHERE ...) 문법을 쓰면 더 깔끔하게 표현할 수 있습니다.위 쿼리는 남성과 여성 유저 수를
같은 테이블 안의 데이터를 서로 연결해야 하는 경우가 있습니다.대표적인 예로는 상사와 직원 관계를 들 수 있습니다.아래는 employees 테이블의 예시입니다.각 직원의 상사 이름을 함께 조회하려면,같은 테이블을 두 번 불러 조인해야 합니다.이처럼 같은 테이블을 두 번
GROUP BY 1은 SELECT절의 첫 번째 컬럼을 기준으로 그룹화하라는 의미입니다.→ GROUP BY region과 동일한 결과가독성이 떨어짐 : 컬럼명을 직접 쓰지 않기 때문에, 쿼리를 처음 보는 사람이 이해하기 어려움.유지보수에 불리함 : SELECT절 순서가
JOIN을 할 때 조인 컬럼명이 같으면 USING도 쓸 수 있지만,실무에선 ON을 더 자주 사용합니다.왜일까요?USING을 쓰면 결과 컬럼에 customer_id 하나만 남습니다.컬럼 이름이 customer_id로 통일돼서, 어느 테이블에서 온 값인지 구분하기 어렵습니
ORDER BY로 정렬할 때 NULL 값이 위에 오게 할 지, 아래로 가게 할 지 지정하고 싶었던 적 있으신가요? 이럴 땐 NULLS FIRST 또는 NULLS LAST를 사용하면 원하는 위치에 NULL을 배치할 수 d ✅ NULLS FIRST / NULLS LAST 사용하기 → 배송일이 없는 주문(=NULL)은 목록 맨 아래로 표시 → 최신 날짜를...
매출 데이터를 집계할 때, 지역별, 상품별 합계뿐만 아니라 소계(지역별)와 전체 총계까지 한 번에 보고 싶은 경우가 많습니다. 이럴 때 GROUP BY와 UNION을 여러 번 써서 처리할 수 있습니다. 그러나, 이 방식은 번거롭습니다. ✅ ROLLUP으로 깔끔하게
데이터가 존재하는지만 확인하고 싶을 때, 혹시 아래처럼 쓰고 있지 않으신가요?단순히 존재여부만 확인한다면, 불필요한 데이터 로드없이 이렇게 써보세요.
INNER JOIN에서 조건을 WHERE에 두면, 모든 데이터를 조인한 후 필터링합니다.반면, ON에 조건을 걸면 조인 대상 자체를 줄일 수 있어 더 효율적입니다.INNER JOIN이라면, 조건은 WHERE절이 아닌 ON절에 작성하여 성능을 최적화하세요.
숫자처럼 생긴 값이 실제로는 문자열(VARCHAR)인 경우가 있습니다.겉보기엔 잘 동작하지만, 실제로는 1001이 '1001'로 암묵 변환되어 비교되는 거예요.인덱스가 적용되지 않아 성능 저하 발생조인 시 누락되거나, 불필요한 형변환이 일어남타입을 확인하고, 타입에 맞
SQL 쿼리를 작성할 때, 인덱스가 걸린 컬럼을 썼다고 안심해도 될까요?데이터가 많아질수록 쿼리 성능이 중요해집니다. 실행 계획을 통해 인덱스를 잘 타고 있는지 꼭 확인해보세요.이때 결과가 아래와 같다면 :👉🏻 Seq Scan = Sequential Scan = 테
숫자 계산을 했는데, 예상과 다르게 결과가 NULL로 나오는 경우가 있습니다.예를 들어, 아래처럼 단순 합산을 했을 때 :숫자 + 숫자인데 결과가 NULL이라면?👉🏻 그 중 최소 하나가 NULL이기 때문입니다.SQL에서는 아래처럼 동작합니다 :즉, 하나라도 NULL
데이터를 현재 시점 기준으로 필터링할 때, 어떤 함수를 사용하시나요?겉보기엔 비슷하지만, 결과는 꽤 다를 수 있습니다.예를 들어, 지금이 8월 3일 오후 11시라면 NOW() - 7 days → 7월 27일 오후 11시부터CURRENT_DATE - 7 → 7월 27일
LEFT JOIN은 자주 사용하는 조인 방식이지만,오른쪽 테이블(조인 대상)에 인덱스가 없을 경우, 예상보다 심각한 성능 저하가 발생할 수 있습니다.인덱스가 없는 경우→ employees 테이블의 각 행마다, resigned_employees 테이블을 전체 탐색(ful
'최근 7일', '올해 1월~오늘까지'처럼특정 기간의 날짜를 모두 나열해야 할 때가 있습니다.날짜 테이블을 조인할 수도 있지만,급하게 ad-hoc 분석을 하거나 날짜 테이블이 없는 환경이라면 어떻게 할까요?orders에서 날짜를 가져와 쓰면, 해당 테이블에 없는 날짜는
이메일 도메인이나 제품명 일부만으로 데이터를 검색해야 할 때가 있습니다.이럴 때 자주 쓰는 게 바로 LIKE 함수입니다.% 기호는 0개 이상의 임의 문자열을 의미합니다.따라서 위치에 따라 다음처럼 활용할 수 있습니다.앞이 고정된 검색뒤가 고정된 검색중간에 포함된 검색고
SQL을 쓰다 보면 컬럼 alias를 WHERE절에서 쓰려다 에러가 나는 경우가 있습니다.그런데 ORDER BY절에서는 alias를 쓸 수 있죠.왜 이런 차이가 생길까요?컬럼 alias : SELECT 절에서 계산식이나 컬럼에 붙이는 별칭하지만 모든 절에서 쓸 수 있는
실무에서 평균이나 비율을 계산하다 보면, 분모가 0인 경우가 종종 발생합니다.이때 단순히 나눗셈을 하면 SQL은 에러를 발생시킵니다.NULLIF(expr1, expr2)두 값이 같으면 NULL 반환다르면 expr1 반환즉, NULLIF(order_count, 0)은 o
조인을 했는데 결과 행 수가 늘어날 때,“조인 잘못했나?” 하고 당황한 적 있으신가요?하지만 행이 늘어났다는 게 꼭 오류를 의미하지 않습니다.데이터가 어떤 구조로 쌓여 있는지 이해하면,그 변화가 자연스러운 경우도 많습니다.이 두 테이블을 조인하면 이렇게 됩니다.행이 늘
CASE WHEN은 유연하지만, 참·거짓으로만 나뉘는 단순 분기엔 코드가 길어집니다. 예를 들어, 활성 상태를 표시한다고 할 때 👇🏻 SELECT user_id, CASE WHEN is_active = TRUE THEN 'Active' EL
데이터를 다루다 보면 전화번호, 우편번호처럼 불필요한 문자(-, (, ), 공백 등)가 섞여 있는 경우가 많습니다. 눈으로 보기엔 같아 보여도, 이런 문자 하나 때문에 조인이 안 되거나 비교 결과가 달라질 때가 있습니다. 이럴 땐 REGEXP_REPLACE()로 한
데이터를 분석하다 보면상위 10% 고객, 매출액 기준으로 4분위로 분할처럼 값의 크기를 기준으로 구간을 나누고 싶은 상황이 자주 있습니다.이럴 때 사용할 수 있는 함수가 바로 NTILE() 입니다.NTILE()은 정렬된 데이터를 n개의 구간(분위, quantile) 으
FULL OUTER JOIN은 다양한 DBMS에서 갖추고 있는 기능이지만, MySQL에서는 지원되지 않습니다.즉, 한 번의 쿼리로 양쪽 테이블의 모든 행을 포함하는 결과를 얻을 수 없어요.하지만 간단히 LEFT JOIN과 RIGHT JOIN을 조합하면동일한 결과를 만들
아래 조건을 만족하는 쿼리를 작성한다고 가정해봅시다.“국내(KR) 고객 중에서최근 30일 내 구매 이력이 있거나,VIP 등급 고객”여러 조건인데 OR 조건이 있으므로, 괄호 사용에 유의해야 합니다.SQL은 AND가 OR보다 우선순위가 높습니다.즉, 아래처럼 해석됩니다
보고서용 SQL을 실행할 때마다 날짜나 지역 코드를 계속 바꿔 써야 해서 번거로울 때가 있습니다.특히 조건이 여러 개일 경우, 매번 WHERE 절을 수정하기도 귀찮죠.이럴 때 DBeaver에서는 쿼리를 “실행 시 입력받는 형태”로 작성할 수 있습니다.SQL 안에 파라미
일일 활성 유저(DAU)나 구매 건수만으로는 서비스의 ‘건강도’를 제대로 판단하기 어렵습니다.숫자는 늘어날 수 있지만, 새로운 유저만 계속 들어오고 기존 유저가 빠르게 이탈하고 있을 수도 있죠.이럴 때 유용한 방법이 코호트(Cohort) 분석입니다.사용자를 ‘시간의 흐
단순히 일자별 합계만 보면 “그날의 성과”는 알 수 있지만,시간의 흐름에 따라 얼마나 누적되고 있는지는 보이지 않습니다.이럴 때는 윈도우 함수 SUM() OVER() 를 사용하면 한 줄의 SQL로 누적 합계를 계산할 수 있습니다.각 날짜별 매출 합계와 함께, 누적 매출
데이터를 집계하다 보면 “전체 대비 비율”을 구할 일이 자주 있습니다.보통 기본 함수를 사용해 분모 분자를 나눠 계산하기도 하지만, 구조가 더 복잡해지고 가독성이 떨어질 때가 있습니다.이럴 때 일부 DB(Oracle, Snowflake, Redshift 등)에서 사용할
플래그(Yes/No) 형태의 지표를 만들다 보면, 특정 조건이 한 번이라도 발생했는지를 판단해야 하는 경우가 있는데요. 보통은 아래와 같은 형태로 처리합니다. 동작은 정확하지만, 여러 플래그를 동시에 만들어야 할 때 쿼리 가독성이 떨어진다는 단점이 있습니다. 이때
데이터 출력 시 정렬 기준을 직접 지정해야 하는 상황이 있습니다.예를 들어 status 값을 아래 순서로 보여주고 싶다고 해볼게요.pendingprocessingdone그런데 기본 정렬을 적용하면 알파벳 순서 때문에 done → pending → processing 또
SQL에서 조건에 따라 값을 변환할 때 보통 CASE WHEN을 사용합니다.하지만 Oracle 환경에서는 같은 로직을 DECODE로 훨씬 간단하게 표현할 수 있습니다.특히 단순 매핑 구조에서는 DECODE가 짧고 읽기 편하다는 장점이 있습니다.조건이 많아질수록 코드가
MySQL에서 JOIN 쿼리를 작성했을 때 에러 없이 결과는 나오는데,건수가 미묘하게 다르거나 성능이 느린 것처럼 느껴질 때가 있습니다.이때 원인으로 생각해볼 수 있는 것이 조인 키 타입 불일치입니다.문법상 문제는 없고, 실제로도 결과는 나옵니다.그래서 문제를 바로
SQL에서 최댓값을 구해야 할 때, 우리는 보통 MAX 함수를 떠올립니다.하지만 집계가 아니라, 같은 행 안에 있는 여러 값 중 최댓값 하나를 고르고 싶은 경우도 있습니다.이럴 때 활용할 수 있는 함수가 있을까요?GREATEST는 하나의 row 안에 있는 여러 값 중
로그나 이력 데이터를 다루다 보면 각 사용자별로 최신 1건만 추출해야 하는 상황이 있습니다. 이때 대부분 아래 패턴을 씁니다. ROW_NUMBER()로 순번 생성 서브쿼리 / CTE로 한 번 감싸기 바깥에서 WHERE rn = 1 결과는 맞지만, 쿼리를 좀 더 간
쿼리가 제대로 동작할 지 확신이 서지 않는 순간이 있습니다.복잡한 윈도우 함수가 의도대로 동작하는지, 조인 조건에서 NULL이 잘 처리되는지 결과를 직접 보기 전까지는 알기 어려울 때가 있습니다.이걸 확인하려고 매번 실제 대용량 테이블을 돌리는 건 비용도 크고, 비효율
날짜 데이터를 다루다 보면 값 자체가 아니라 ‘표현 방식’을 바꿔야 하는 경우가 있습니다. 예를 들어 2026-01-04 00:00:00 같은 값을 2026-01-04 형태로 출력하고 싶을 때입니다. MySQL에서는 이런 경우 DATE_FORMAT() 함수를 사용할 수 있습니다. 📆 DATE_FORMAT()이란? DATE_FORMAT()은 날짜 또는 ...
MySQL에서는 조건식이 TRUE = 1, FALSE = 0으로 평가됩니다.이 특성 때문에 조건 자체를 숫자처럼 사용할 수 있는 경우가 있습니다.조건을 만족하는 경우만 1로 계산되기 때문에, CASE WHEN 없이도 조건 집계가 가능합니다.
값이 다른지를 확인해야 하는데 NULL이 섞여 있는 경우가 있습니다.이럴 때 PostgreSQL에서는 NULL 비교를 명확하게 하기 위해 IS DISTINCT FROM을 사용할 수 있습니다.IS DISTINCT FROM은 NULL을 포함해 두 값이 실제로 다른지를 비교
데이터를 필터링하다 보면조건은 맞는 것 같은데 결과가 줄어드는 순간이 있습니다.특정 값을 제외했을 뿐인데,원하는 데이터까지 같이 사라집니다.이런 경우,NULL 때문에 조건이 의도와 다르게 동작할 수도 있습니다.품절(OUT_OF_STOCK)과고객 변심(CUSTOMER_C
FROM 절에 서브쿼리를 사용할 때는 반드시 Alias(별칭)를 붙여야 합니다. 왜 그럴까요? FROM (SELECT …)는 실제 테이블이 아니라 이름 없는 임시 결과 집합(파생 테이블)입니다. SQL 엔진은 이 결과를 참조하기 위해 식별할 이름이 필요합니다.