
엑셀의 함수를 잘 다루고, MySQL을 사용한 경험이 있지만 프로그래머스의 문제가 풀리지 않았고, 작성했던 쿼리들의 효율에 의문이 있었다.
처음으로 Oracle을 사용해보면서 총 6일간 학습해 문제를 해결해 프로그래머스에서 제공하고 있는 SQL(71개) 문제를 모두 해결할 수 있었다.
앞으로 SQL을 잘 활용하기 위해 사용했던 문법과 함수를 정리하고자한다.
SELECT COUNT(DISTINCT NAME)
FROM animal_ins
WHERE name IS NOT NULL;MAX(DATETIME) timeMAX(DATETIME) "time"MAX(DATETIME) AS time * 가장 많이 사용되는 형태MAX(DATETIME) AS "time"ORA-00923: FROM keyword not found where expected)가 발생 링크SELECT MAX(DATETIME) AS 시간
FROM ANIMAL_INS;DECODE(검사 대상이 될 열 또는 데이터, 연산이나 함수의 결과,
조건1, 데이터가 조건1과 일치할 때 반환할 결과,
조건2, 데이터가 조건2와 일치할 때 반환할 결과,
...
조건n, 데이터가 조건n과 일치할 때 반환할 결과,
위 조건1~조건n과 일치한 경우가 없을 때 반환할 결과
)CASE
WHEN 조건1 THEN 조건1의 결과 값이 TRUE일 때 반환
WHEN 조건2 THEN 조건2의 결과 값이 TRUE일 때 반환
...
WHEN 조건n THEN 조건n의 결과 값이 TRUE일 때 반환
ELSE 위 조건1~조건n과 일치하는 경우가 없을 때 반환
ENDCASE WHEN ~ THEN ~ LESE ~ END 문법을 알고 있는게 좋다. 링크SELECT order_id,product_id,TO_CHAR(out_date,'YYYY-MM-DD') AS out_date, (
CASE WHEN out_date IS NULL THEN '출고미정'
WHEN out_date<TO_DATE('20220502','YYYYMMDD') THEN '출고완료'
ELSE '출고대기' END) AS 출고여부
FROM food_order
ORDER BY order_id;SELECT flavor
FROM (
SELECT flavor, SUM(total_order) AS total_order
FROM(
SELECT *
FROM first_half
UNION ALL
SELECT *
FROM july
)
GROUP BY flavor
ORDER BY total_order DESC
)
WHERE ROWNUM < 4;SELECT ai.animal_id, ai.name
FROM animal_ins ai
LEFT JOIN animal_outs ao
ON ai.animal_id=ao.animal_id
WHERE ai.datetime>ao.datetime
ORDER BY ai.datetime;MAX, MIN, COUNT 등의 함수를 조건에 바로 사용할 수 없고 다른 서브쿼리를 사용할 수 있다. 링크SELECT *
FROM places
WHERE host_id IN (
SELECT host_id
FROM places
GROUP BY host_id
HAVING COUNT(*) >= 2
)
ORDER BY id;~별이란 키워드가 보인다면 정말 많이 사용되기 때문에 숙지 필요GROUP BY에 들어가는 컬럼
다중행 함수(SUM, COUNT, MIN, MAX 등)를 적용한 칼럼
SELECT animal_type, COUNT(*)
FROM animal_ins
WHERE animal_type = 'Cat' OR animal_type = 'Dog'
GROUP BY animal_type
ORDER BY animal_type;
GROUP BY 절을 통해 그룹화된 결과 값의 범위를 제한하는 데 사용SELECT cart_id
FROM cart_products
WHERE name IN ('Milk','Yogurt')
GROUP BY cart_id
HAVING COUNT(DISTINCT name)=2
ORDER BY cart_id ASC;ORDER BY절은 정렬 로직으로 시간적 비용이 큰 작업이다.SELECT flavor
FROM first_half
ORDER BY total_order DESC, shipment_id;SELECT *
FROM (
SELECT ai.name, ai.datetime
FROM animal_ins ai LEFT JOIN animal_outs ao
ON ai.animal_id = ao.animal_id
WHERE ao.animal_id IS NULL
ORDER BY ai.datetime
)
WHERE ROWNUM < 3SELECT 열
FROM 테이블
WHERE 조건식 (
SELECT 열
FROM 테이블
WHERE 조건식);>, >=, =, <=, <, <>, ^=, !=IN, ANY, SOME, ALL, EXISTSSELECT, FROM, WHERE절에 각각 사용 가능하다.SELECT mp.member_name, rr.review_text, TO_CHAR(rr.review_date,'YYYY-MM-DD') AS review_date
FROM REST_REVIEW rr
LEFT JOIN MEMBER_PROFILE mp
ON rr.member_id = mp.member_id
WHERE mp.member_id IN (
SELECT member_id
FROM (
SELECT member_id, COUNT(*) AS count_review
FROM rest_review
GROUP BY member_id
ORDER BY count_review DESC
)
WHERE count_review = (
SELECT count_review
FROM (
SELECT member_id, COUNT(*) AS count_review
FROM rest_review
GROUP BY member_id
ORDER BY count_review DESC
)
WHERE ROWNUM = 1)
)
ORDER BY rr.review_date, review_date;TRUE AND TRUE → TRUETRUE AND FALSE → FALSEFALSE AND TRUE → FALSEFALSE AND FALSE → FALSETRUE OR TRUE → TRUETRUE OR FALSE → TRUEFALSE OR TRUE → TRUEFALSE OR FALSE → FALSENOT+, -, *, /NULL과 연산시 NULL이 나오니 주의>, >=, <=, <=!=, <>, ^=SELECT COUNT(*)
FROM user_info
WHERE TO_CHAR(joined,'YY')='21'
AND age BETWEEN 20 AND 29;SELECT [조회할 열1 이름], ...
FROM [조회할 테이블 이름]
WHERE 열 이름 IN (데이터1,...)NOT IN 을 통해서 제외한 결과를 얻을 수도 있다.SELECT ANIMAL_ID, NAME, SEX_UPON_INTAKE
FROM animal_ins
WHERE name IN ('Lucy', 'Ella', 'Pickle', 'Rogan', 'Sabrina', 'Mitty')
ORDER BY animal_id;SELECT category, price AS max_price, product_name
FROM food_product
WHERE (category,price) IN (
SELECT category, MAX(price) AS max_price
FROM food_product
WHERE category IN ('과자', '국', '김치', '식용유')
GROUP BY category
)
ORDER BY price DESC;NOT LIKE 사용가능_: 어떤 값이든 상관없이 한 개의 문자 데이터%: 길이와 상관 없이(문자 없는 경우도 포함) 모든 문자 데이터를 의미LIKE '%S': 전방일치LIKE 'S%': 후방일치LIKE '%S%': 부분일치 링크SELECT car_type, COUNT(*) AS cars
FROM car_rental_company_car
WHERE options LIKE '%시트%'
GROUP BY car_type
ORDER BY car_type;NULL은 데이터 값이 완전히 비어있는 상태NULL인지 확인하는 방법으로 IS NULL사용한다. 링크SELECT animal_id
FROM animal_ins
WHERE name IS NULL
ORDER BY animal_id;NULL이 아닌지 확인하는 방법으로 IS NOT NULL을 사용한다. 링크SELECT animal_id
FROM animal_ins
WHERE name IS NOT NULL
ORDER BY animal_id;UNION : 결과 값을 합집합으로 묶어줌. 결과 중복 제거 후 출력UNION ALL: 결과 값을 합집합으로 묶어줌. 결과 중복 값도 출력MINUS: 차집합 처리. 선행 SELECT문의 값 중 후행 SELECT에 존재하지 않는 데이터만 출력INTERSECT: 교집합 처리. 선행 SELECT문과 후행 SELECT에 모두 존재하는 데이터만 출력선행 SELECT 문의 컬럼명, 순서를 따라간다.
NULL AS user_id 꼴로 NULL 컬럼을 생성해 개수를 맞출 수 있다. 링크
SELECT *
FROM(
SELECT TO_CHAR(sales_date,'YYYY-MM-DD') AS sales_date, product_id, user_id, sales_amount
FROM online_sale
WHERE sales_date>=TO_DATE('20220301','YYYYMMDD') AND sales_date<TO_DATE('20220401','YYYYMMDD')
UNION ALL
SELECT TO_CHAR(sales_date,'YYYY-MM-DD') AS sales_date, product_id, NULL AS user_id, sales_amount
FROM offline_sale
WHERE sales_date>=TO_DATE('20220301','YYYYMMDD') AND sales_date<TO_DATE('20220401','YYYYMMDD')
)
ORDER BY sales_date, product_id, user_id;
UPPER: 대문자로 변환
LOWER: 소문자로 변환
INITCAP: 첫글자는 대문자, 나머지는 소문자
SELECT animal_id, name
FROM animal_ins
WHERE UPPER(name) LIKE '%EL%'
AND animal_type = 'Dog'
ORDER BY name;
SUBSTR(문자열 데이터, 시작 위치[, 추출 길이]) 1번부터 시작 링크SELECT SUBSTR(PRODUCT_CODE,1,2) AS category, COUNT(*)
FROM PRODUCT
GROUP BY SUBSTR(PRODUCT_CODE,1,2)
ORDER BY category;INSTR(대상 문자열 데이터, 위치를 찾으려는 부분 문자[, 시작위치, 몇 번째 나온것 반환])SUBSTR와 함께 쓰기 좋다. 링크SELECT history_id, MIN(daily_fee*(1-NVL(DISCOUNT_RATE,0)/100)*(end_date-start_date+1)) AS fee
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY ccrh
LEFT JOIN CAR_RENTAL_COMPANY_CAR ccc
ON ccrh.car_id = ccc.car_id
LEFT JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN crcdp
ON (ccc.car_type = crcdp.car_type
AND (end_date-start_date+1) >= TO_NUMBER(SUBSTR(duration_type,1,INSTR(duration_type,'일')-1)))
WHERE ccc.car_type = '트럭'
GROUP BY history_id
ORDER BY fee DESC, history_id DESC;REPLACE(문자열 데이터 또는 열 이름, 찾는 문자, 대체할 문자)CONCAT(문자열 데이터 또는 열 이름,문자열 데이터 또는 열 이름)문자열 데이터 또는 열 이름||문자열 데이터 또는 열 이름 ||를 사용해도 동일한 효과ROUND(숫자,반올림위치) 링크SELECT car_id, ROUND(AVG(end_date-start_date+1),1) AS average_duration
FROM car_rental_company_rental_history
GROUP BY car_id
HAVING ROUND(AVG(end_date-start_date+1),1) >= 7
ORDER BY average_duration DESC, car_id DESC;TRUNC(숫자,버림위치) 링크SELECT TRUNC(price,-4) AS price_group, COUNT(*) AS products
FROM product
GROUP BY TRUNC(price,-4)
ORDER BY price_group;CEIL(숫자): 지정 숫자 보다 큰 정수 중 가장 작은 정수FLOOR(숫자):지정 숫자 보다 작은 정수 중 가장 큰 정수MOD(나눗셈 될 숫자, 나눌 숫자): 지정된 숫자를 나눈 나머지날짜 데이터 + 숫자: 날짜 데이터보다 숫자만큼 일수 이후의 날짜날짜 데이터 - 숫자: 날짜 데이터보다 숫자만큼 일수 이전의 날짜날짜 데이터 - 날짜 데이터: 두 날짜 데이터 간의 일수 차이 링크일수 차이기 때문에 일일당 요금 등을 구하는 곳에서는 1일 만큼을 더할 필요가 있음
SELECT animal_id, name
FROM(
SELECT ao.animal_id, ao.name, (ao.datetime-ai.datetime) AS time
FROM animal_outs ao LEFT JOIN animal_ins ai
ON ao.animal_id = ai.animal_id
WHERE ai.animal_id IS NOT NULL
ORDER BY time DESC
)
WHERE ROWNUM<3;
날짜 데이터 + 날짜 데이터: 연산 불가TO_CHAR(datetime, 'YYYY-DD-MM')꼴로 원하는 출력 형태로 만들 수 있다. 링크SELECT animal_id, name, TO_CHAR(datetime,'YYYY-MM-DD')
FROM animal_ins
ORDER BY animal_id;SELECT DISTINCT(ccc.car_id)
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY ccrh
LEFT JOIN CAR_RENTAL_COMPANY_CAR ccc
ON ccrh.CAR_ID = ccc.CAR_ID
WHERE ccc.car_type = '세단' AND TO_CHAR(ccrh.start_date,'MM')=10
ORDER BY car_id DESC;date는 문자이기 때문에 9시가 09시로 출력되기 때문에 오답이 되는 경우가 있어 이를 숫자로 바꾸기 위해 TO_NUMBER로 감싼다.SELECT TO_NUMBER(TO_CHAR(datetime,'HH24')) AS hour, COUNT(*)
FROM animal_outs
WHERE TO_NUMBER(TO_CHAR(datetime,'HH24'))>8
AND TO_NUMBER(TO_CHAR(datetime,'HH24'))<20
GROUP BY TO_NUMBER(TO_CHAR(datetime,'HH24'))
ORDER BY hour;TO_CHAR를 사용해도 결과는 같지만, 문자열 비교보다 DATE 비교가 통상 빠르다고 해서 사용했다.SELECT *
FROM(
SELECT crcc.car_id, crcc.car_type, crcc.daily_fee*(1-crcdp.discount_rate/100)*30 AS fee
FROM CAR_RENTAL_COMPANY_CAR crcc
LEFT JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN crcdp
ON crcc.car_type=crcdp.car_type
AND crcdp.duration_type='30일 이상'
WHERE (
crcc.car_type IN('SUV','세단')
AND crcc.car_id IN(
SELECT car_id
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY car_id
HAVING MAX(
CASE WHEN (TO_DATE('2022-11-01','YYYY-MM-DD') BETWEEN start_date AND end_date) THEN 1
WHEN (TO_DATE('2022-11-30','YYYY-MM-DD') BETWEEN start_date AND end_date) THEN 1
ELSE 0 END
)=0
)
)
)
WHERE fee>=500000 AND fee<2000000
ORDER BY fee DESC, car_type ASC, car_id DESC;**NULL을 처리하기 위해 값이 없는 경우를 처리할 때 사용하는 함수**NVL(NULL인지 여부를 검사할 데이터 또는 열, 앞의 데이터가 NULL일 경우 반환할 데이터) 링크SELECT warehouse_id, warehouse_name, address, NVL(freezer_yn,'N')
FROM food_warehouse
WHERE address LIKE '경기도 %'
ORDER BY warehouse_id;
NVL(NULL인지 여부를 검사할 데이터 또는 열, 앞의 데이터가 NULL일 경우 반환할 데이터 또는 계산식, 앞의 데이터가 NULL일 경우 반환할 데이터 또는 계산식)SUM, COUNT, MAX, MIN, AVGDISTINCT, ALL 키워드를 사용할 수 있다.SELECT category, SUM(sales) AS total_sales
FROM book_sales bs
LEFT JOIN book b
ON bs.book_id = b.book_id
WHERE TO_CHAR(sales_date,'YYYY-MM') = '2022-01'
GROUP BY category
ORDER BY categoryCOUNT(*)꼴로 많이 사용하지만 COUNT(컬럼명)형태로도 사용한다.SELECT COUNT(*)
FROM animal_ins;SELECT name, COUNT(*) AS count
FROM ANIMAL_INS
WHERE name IS NOT NULL
GROUP BY name
HAVING COUNT(name)>1
ORDER BY name;SELECT MAX(price) AS MAX_PRICE
FROM PRODUCT;SELECT product_id, product_name, product_cd, category, price
FROM food_product
WHERE price = (SELECT MAX(price) FROM food_product);SELECT MIN(datetime) AS 시간
FROM animal_ins;SELECT round(avg(daily_fee)) AS AVERAGE_FEE
FROM car_rental_company_car
WHERE car_type = 'SUV';CONNECT BY LEVEL쿼리는 계층형 쿼리로 오라클에서 값을 연속적으로 증가하는 값이 필요로 할 때 사용SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL < 4;
// 1
// 2
// 3SELECT hour, sum(count) AS count
FROM(
SELECT TO_NUMBER(TO_CHAR(datetime,'hh24')) AS hour, COUNT(*) AS count
FROM animal_outs
GROUP BY TO_NUMBER(TO_CHAR(datetime,'hh24'))
UNION
SELECT LEVEL - 1 AS HOUR, 0 AS count
FROM DUAL
CONNECT BY LEVEL < 25
)
GROUP BY hour
ORDER BY hour;