Closed yeahdy closed 4 days ago
Employee 테이블에서 두 번째로 높은 고유한 급여를 찾기 Salary 중복 제거
1) 처음 작성한 코드
SELECT Salary AS SecondHighestSalary
FROM (
SELECT Salary, RANK() OVER (ORDER BY Salary DESC) AS rk
FROM Employee
) AS RankSalary
WHERE rk = 2;
틀린 이유 : null값 반환X
2) 정답 코드
SELECT MAX(Salary) as SecondHighestSalary
FROM (SELECT salary, dense_rank() over(order by salary desc) as rk
FROM Employee
) Employee
where rk = 2;
3) 다른 사람 풀이
SELECT (
SELECT DISTINCT Salary
FROM Employee
ORDER BY Salary DESC
LIMIT 1 OFFSET 1
) AS SecondHighestSalary;
SELECT MAX(salary) AS SecondHighestSalary
FROM Employee
WHERE salary < (SELECT MAX(salary)FROM Employee);
DENSE_RANK(), RANK(), ROW_NUMBER()
LIMIT OFFSET : 시작행부터 특정 행 개수만 출력
사용 : 부분적으로 원하는 범위만 지정할 때 (1행 숫자 0)
SELECT [컬럼명] FROM [테이블명] WHERE [조건식]
LIMIT OFFSET [출력할 행의 개수] OFFSET [시작행];
LIMIT 4 OFFSET 2; -- 3, 4, 5, 6행 출력
LIMIT [시작행], [출력할 행의 개수]; -- 콤마(,) 입력해서 OFFSET 생략 LIMIT 1,3; -- 2번째 행부터 3개 행만 출력
SKILLCODES
와 개발자들의 프로그래밍 스킬 정보를 담은 DEVELOPERS
테이블이 있을 때, 다음과 같은 조건에 부합하도록 GRADE
출력하기A
: Front End 스킬과 Python 스킬을 함께 가지고 있는 개발자B
: C# 스킬을 가진 개발자C
: 그 외의 Front End 개발자_SKILLCODE가 400 (=b'110010000')이라면, 이는 SKILLCODES 테이블에서 CODE가 256 (=b'100000000'), 128 (=b'10000000'), 16 (=b'10000') 에 해당하는 스킬을 가졌다는 것을 의미함
WITH FE AS (
SELECT SUM(CODE) CODE
FROM SKILLCODES
WHERE CATEGORY = "Front End"
)
, C AS (
SELECT CODE
FROM SKILLCODES
WHERE NAME = "C#"
)
, PYTHON AS (
SELECT CODE
FROM SKILLCODES
WHERE NAME = "Python"
)
, DEVELOPER_GRADE AS (
SELECT
CASE
WHEN a.SKILL_CODE & b.CODE != 0 AND a.SKILL_CODE & d.CODE != 0 THEN 'A'
WHEN a.SKILL_CODE & c.CODE != 0 THEN 'B'
WHEN a.SKILL_CODE & b.CODE != 0 THEN 'C'
END AS GRADE,
a.ID, a.EMAIL
FROM DEVELOPERS a, FE b, C c, Python d
)
SELECT *
FROM DEVELOPER_GRADE a
WHERE a.GRADE IS NOT NULL
ORDER BY a.GRADE, a.ID
FE
: SKILLCODES
에서 CATEGORY
가 Front End
인 것의 코드를 모두 더함SKILL_CODE
와 &
연산했을 때, 해당 개발자가 프론트엔드 개발자인지 확인하기 위한 용도C
: SKILLCODES
에서 NAME
이 C#
인 데이터의 CODE
저장PYTHON
: SKILLCODES
에서 NAME
이 Python
인 데이터의 CODE
저장DEVELOPER_CODE
CASE
절을 통해 조건에 맞는 GRADE
를 출력할 수 있도록 함추가로,
CASE
문을 통해GRADE
컬럼을 추가했을 때,WHERE GRADE IS NOT NULL
로 실행하면(1054, "Unknown column 'GRADE' in 'where clause'")
오류가 발생하면서 안되지만,HAVING GRADE IS NOT NULL
을 사용하면 결과 값이 나오더라고요!WHERE
,HAVING
모두 필터링 역할을 하는데,WHERE
절은SELECT
하기 전에 실행되고,HAVING
은SELECT
이후에 실행되기 때문에 그런 것 같습니다!
Python이나 C# 스킬을 가진 개발자의 ID, 이메일, 이름, 성을 조회
DEVELOPERS
테이블에서 SKILL_CODE
가 400 (=b'110010000') 이라면,
이는 SKILLCODES
테이블에서 CODE
가 256 (=b'100000000'), 128 (=b'10000000'), 16 (=b'10000') 에 해당하는 스킬을 가졌다는 것을 의미
SELECT
d.ID, d.EMAIL, d.FIRST_NAME, d.LAST_NAME
FROM DEVELOPERS d
INNER JOIN SKILLCODES s
ON d.SKILL_CODE & s.CODE > 0
WHERE s.NAME IN ('Python','C#')
GROUP BY d.ID, d.EMAIL, d.FIRST_NAME, d.LAST_NAME
ORDER BY d.ID;
INNER JOIN SKILLCODES s ON d.SKILL_CODE & s.CODE > 0
같은 자릿수에 1
이 있을 경우 비트연산 시 0 이상의 값이 나온다GROUP BY d.ID, d.EMAIL, d.FIRST_NAME, d.LAST_NAME
GROUP BY
로 중복제거WITH CTE AS(
SELECT *, ID- ROW_NUMBER() OVER() AS ID_DIFF
FROM STADIUM
WHERE PEOPLE > 99
)
SELECT ID, VISIT_DATE, PEOPLE
FROM CTE
WHERE ID_DIFF IN (SELECT ID_DIFF FROM CTE GROUP BY ID_DIFF HAVING COUNT(ID_DIFF) >= 3)
ORDER BY VISIT_DATE
가상 테이블 활용
1) ROW_NUMBER()
: 각 행에 순차적인 순위를 매김
2) 행의 ID와 순위값의 차이 출력 : 연속으로 이루어진 행들은 (ID -순위값)이 같음
3) 사람의 수가 100이상인 행만 출력
예) 가상 테이블 출력 결과
가상 테이블의 ID_DIFF 컬럼값이 3개 이상인것 만 출력해서 결과값 출력
자동차 종류가 '세단' 또는 'SUV' 인 자동차 중 2022년 11월 1일부터 2022년 11월 30일까지 대여 가능
30일간의 대여 금액이 50만원 이상 200만원 미만인 자동차에 대해서 자동차 ID, 자동차 종류, 대여 금액
대여 금액을 기준 내림차순 > 자동차 종류를 기준 오름차순 > 자동차 ID를 기준으로 내림차순 정렬
CAR_RENTAL_COMPANY_CAR
: 대여 자동차들 정보 테이블(c)
CAR_RENTAL_COMPANY_RENTAL_HISTORY
: 자동차 대여 기록 정보 테이블(rh)
CAR_RENTAL_COMPANY_DISCOUNT_PLAN
: 자동차 종류 별 대여 기간 종류 별 할인 정책 정보 테이블(dp)
SELECT
c.CAR_ID,
c.CAR_TYPE,
FLOOR((c.DAILY_FEE*30) * (1-(dp.DISCOUNT_RATE/100))) FEE
FROM CAR_RENTAL_COMPANY_CAR c
LEFT JOIN (
SELECT CAR_ID, MIN(START_DATE) START_DATE, MAX(END_DATE) END_DATE
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY rh
GROUP BY CAR_ID
) rh
ON c.CAR_ID = rh.CAR_ID
INNER JOIN CAR_RENTAL_COMPANY_DISCOUNT_PLAN dp
ON c.CAR_TYPE = dp.CAR_TYPE
AND dp.DURATION_TYPE LIKE '30%'
AND c.CAR_TYPE IN ('세단','SUV')
WHERE '2022-11-01' > rh.END_DATE OR '2022-11-30' < rh.START_DATE
HAVING FEE BETWEEN 500000 AND 1999999
ORDER BY FEE DESC, CAR_TYPE, CAR_ID DESC;
WHERE
조건으로 필터링
LEFT JOIN
한 이유?
한번도 대여를 하지 않은 자동차는 자동차 대여 기록 정보 테이블(rh)에 정보(CAR_ID)가 없기 때문에 대여 내역이 없는 자동차도 포함하기 위해 사용 (INNER JOIN
을 하면 대여 기록이 없는 자동차는 조회 불가)INNER JOIN ON
필터링
FLOOR(숫자)
소수점 버림 > 정수로 변환WHERE
와 INNER ON
필터링 모두 가능한데, 성능상 차이가 있을까?ON
절에서 필터링을 하면 먼저 조인 테이블에서 필터링된 데이터를 토대로 조인을 실행하여 불필요한 데이터를 사전에 처리해서 빠른 연산이 가능하지 않을까 생각함WHERE
와 INNER ON
필터링은 동일한 실행 계획을 가지므로 성능상 차이가 없음WHERE
와 LEFT JOIN ON
필터링은 성능상 차이가 있을까?LEFT JOIN
은 첫번째 테이블과 두번째 테이블의 일치하는 모든 행을 조인하기 때문에 ON
과 WHERE
절에 조건에 뒀을 때 결과 레코드에서 차이가 있어서 성능을 비교할 순 없음
예) SQL filter LEFT TABLE before left joinLEFT JOIN ~ ON 절
에서 필터링을 통해 불필요한 데이터를 사전에 제거하고 조인을 하기 때문에 성능에 유리할 수도 있음? (일반화 할 수는 없음)2021년에 가입한
전체 회원들 중 상품을 구매한 회원수와 상품을 구매한 회원의 비율
구하기
상품을 구매한 회원의 비율
소수점 두번째자리에서 반올림
정렬 : 전체 결과는 년을 기준
으로 오름차순 정렬
, 년이 같다면 월을 기준
으로 오름차순 정렬
SELECT
YEAR(A.SALES_DATE) AS YEAR,
MONTH(A.SALES_DATE) AS MONTH,
COUNT(DISTINCT A.USER_ID) AS PURCHASED_USERS,
ROUND(
COUNT(DISTINCT A.USER_ID) /
(SELECT COUNT(*) FROM USER_INFO WHERE YEAR(JOINED) = 2021), 1)
AS PUCHASED_RATIO
FROM
ONLINE_SALE A
JOIN
USER_INFO B ON A.USER_ID = B.USER_ID
WHERE
YEAR(B.JOINED) = 2021
GROUP BY
YEAR(A.SALES_DATE), MONTH(A.SALES_DATE)
ORDER BY
year, month;
2021년에 가입한 회원 필터링 : WHERE YEAR(B.JOINED) = 2021
회원 ID 기준 JOIN
년, 월 별로 출력, 정렬 : GROUP BY, ORDER BY
구매한 회원 아이디 중복 방지 : DISTINCT USER_ID
비율
: ROUND(
COUNT(DISTCINT USER_ID)SELECT COUNT(*) FROM USER_INFO WHERE YEAR(JOINED) = 2021), 1) -- 소수 둘째 자리에서 반올림
지난주 지영님 풀이 중 가상 테이블 풀이
WITH TOTAL_USERS_2021 AS (
SELECT COUNT(DISTINCT USER_ID) AS T_COUNT
FROM USER_INFO
WHERE YEAR(JOINED) = 2021
)
SELECT YEAR(SALES_DATE) AS YEAR, MONTH(SALES_DATE) AS MONTH
, COUNT(DISTINCT B.USER_ID) AS PURCHASED_USERS
, ROUND(COUNT(DISTINCT B.USER_ID) / (SELECT * FROM TOTAL_USERS_2021), 1) AS PURCHASED_RATIO
FROM USER_INFO A
JOIN ONLINE_SALE B
ON A.USER_ID = B.USER_ID
WHERE YEAR(JOINED) = 2021
GROUP BY YEAR(SALES_DATE), MONTH(SALES_DATE)
ORDER BY YEAR, MONTH
가상 테이블을 사용하면
중복 연산 방지
,최적화 가능성
Students
, Subjects
, Examinations
테이블 존재
Students
, Subjects
테이블끼리는 겹치는 키 없음-- CROSS JOIN 이용
SELECT st.student_id, st.student_name ,sb.subject_name,
COUNT(e.student_id) AS attended_exams
FROM Students st
CROSS JOIN Subjects sb
LEFT JOIN Examinations e
ON e.student_id = st.student_id
AND e.subject_name = sb.subject_name
GROUP BY st.student_id, st.student_name, sb.subject_name
ORDER BY st.student_id, st.student_name, sb.subject_name
학생-과목
에 대한 모든 조합 묶음이 필요하기에 CROSS JOIN
사용
CROSS JOIN
: 각 테이블의 행에 대한 카디션 곱을 만들어줌
LEFT JOIN
GROUP BY
와 COUNT
학생-과목
조합에 대해 응시 횟수를 세야하기에 GROUP BY로 학생, 과목을 설정CROSS JOIN과 LEFT JOIN한 모습을 직관적으로 볼 수 있는 그림
그리고..! 처음에 그냥 CROSS JOIN없이 JOIN으로만 했는데도 답으로 인정은 됐는데, 이건 데이터가 충분하지 못해서 그런거지 않을까 생각됩니닷..!
ID 1 : GENOTYPE 8 -> 1000₍₂₎ ID 2 : GENOTYPE 15 -> 1111₍₂₎ ID 3 : GENOTYPE 1 -> 1₍₂₎ ID 4 : GENOTYPE 13 -> 1101₍₂₎
각 대장균 별 보유한 형질을 다음과 같다 ID 1 : 4 ID 2 : 1, 2, 3, 4 ID 3 : 1 ID 4 : 1, 3, 4
SELECT COUNT(*) AS COUNT
FROM ECOLI_DATA E,
(SELECT CONV(GENOTYPE, 10, 2) MOD 10 AS ONE,
(CONV(GENOTYPE, 10, 2) MOD 100) DIV 10 AS TWO,
(CONV(GENOTYPE, 10, 2) MOD 1000) DIV 100 AS THREE,
ID
FROM ECOLI_DATA) G
WHERE E.ID = G.ID AND G.TWO = 0 AND (G.ONE = 1 OR G.THREE = 1);
SELECT COUNT(ID) AS COUNT
FROM ECOLI_DATA
WHERE (GENOTYPE & 2 != 2) AND (GENOTYPE & 1 = 1 OR GENOTYPE & 4 = 4)
SELECT COUNT(ID) AS COUNT
FROM ECOLI_DATA
WHERE (GENOTYPE & 2 != 2) AND (GENOTYPE & 5 > 5)
9주차 때, 예진님이 풀이하신 방법입니다!
CONV(data, 원본 진법, 변환할 진법)
CONV(GENOTYPE, 10, 2) MOD 10
의 출력값이 1이됨CONV(GENOTYPE, 10, 2) MOD 100) DIV 10
의 출력값이 1이됨CONV(GENOTYPE, 10, 2) MOD 1000) DIV 100
의 출력값이 1이됨GENOTYPE & 2 != 2
: 2번 형질을 가지지 않음
GENOTYPE & 1 = 1 OR GENOTYPE & 4 = 4
: 위와 같은 원리로 1번 또는 3번 형질을 가짐
GENOTYPE & 5 > 5
: 5 의 이진수는 101 로 AND 비트연산 시 첫번째와 세번째 자리에 1이 존재한다면 0 이상이 나오게 됨
예진님 리뷰 인용
ECOLI_DATE
테이블이 있을 때, 각 분기 별로 분화된 대장균의 개체 총 수 구하기처음 작성한 코드
WITH CTE AS (
SELECT
QUARTER(DIFFERENTIATION_DATE) `QUARTER`
, COUNT(QUARTER(DIFFERENTIATION_DATE)) `ECOLI_COUNT`
FROM ECOLI_DATA
GROUP BY QUARTER
)
SELECT CONCAT(a.QUARTER, 'Q') `QUARTER`, a.ECOLI_COUNT
FROM CTE a
수정한 코드
SELECT
CONCAT(QUARTER(DIFFERENTIATION_DATE), 'Q') `QUARTER`
, COUNT(ID) `ECOLI_COUNT`
FROM ECOLI_DATA
GROUP BY QUARTER
ORDER BY QUARTER
QUARTER
을 통해 주어진 날짜에 대해 분기를 알 수 있도록 함CONCAT()
을 통해 분기에 대한 숫자 뒤에 Q
가 올 수 있도록 함QUARTER
에 따라 GROUP BY
를 한 뒤, 정렬처음 SQL의 실행 순서와 GROUP BY에서는 Alais를 사용할 수 있다는 것을 고려하지 못하고, 임시 테이블을 생성하고 결과 출력하였고, 이후 아래 코드로 수정함
-- RECURSIVE 사용
WITH RECURSIVE TM AS(
SELECT 0 AS HOUR
UNION ALL
SELECT HOUR+1 FROM TM WHERE HOUR < 23
)
SELECT A.HOUR, IFNULL(B.COUNT, 0) AS COUNT FROM TM A LEFT JOIN ( SELECT HOUR(DATETIME) AS HOUR, COUNT(HOUR(DATETIME)) AS COUNT FROM ANIMAL_OUTS GROUP BY HOUR(DATETIME) ) B ON A.HOUR = B.HOUR
> 재귀 방법은 다혜님이 잘 설명해주셔서 넘어가겠습니다!
- 세션 변수 사용 방식
```sql
SET @HOUR := -1;
SELECT (@HOUR := @HOUR+1) as HOUR,
(SELECT COUNT(*) FROM ANIMAL_OUTS WHERE HOUR(DATETIME) =@HOUR) AS COUNT
FROM ANIMAL_OUTS
WHERE @HOUR < 23
세션변수 @HOUR
선언
세션 변수
@
를 붙여 사용세션변수 초기값 설정
시간 컬럼 생성
(@HOUR := @HOUR+1)
:=
대입연산, 비교연산자 =과 혼동을 피하기위해 :=
형태로 사용
재귀 테이블 대신 세션변수를 사용한 것으로 전체적인 형태는 동일
프로시저 사용 방식
DELIMITER $$
CREATE PROCEDURE GetAnimalCountByHour()
BEGIN
-- 프로시저 내부에서 사용할 변수 선언
DECLARE i INT DEFAULT 0; -- i, 반복문의 카운터 역할, 0으로 초기화
DECLARE hour_count INT; -- 각 시간대의 레코드 개수 저장
-- 시간별 결과를 저장할 임시 테이블 생성
CREATE TEMPORARY TABLE HourlyCounts (HOUR INT, COUNT INT);
-- 0시부터 23시까지 반복
WHILE i < 24 DO
-- 특정 시간대의 COUNT 값을 계산
SELECT COUNT(*) INTO hour_count
FROM ANIMAL_OUTS
WHERE HOUR(DATETIME) = i;
-- 결과를 임시 테이블에 저장
INSERT INTO HourlyCounts (HOUR, COUNT) VALUES (i, hour_count);
-- 시간 증가
SET i = i + 1;
END WHILE;
-- 결과 출력
SELECT * FROM HourlyCounts;
-- 임시 테이블 삭제
DROP TEMPORARY TABLE HourlyCounts;
END$$
DELIMITER ;
CALL GetAnimalCountByHour();
- `프로시저`
- 미리 정의된 작업 집합, 데이터베이스에서 특정 작업을 수행하기 위해 저장된 SQL코드 블록
- 작성 방식
- `CREATE PROCEDURE` : 프로시저 정의
- `CALL` : 프로시저 실행
- `BEGIN ... END` : 프로시저 내부에 포함될 작업 정의
> 프로시저는 보통 저장 프로시저를 말하고, 이는 여러 테이블이나 특정작업을 반복해야할때 유용하게 사용한다고 합니다.
> 참고로 프로그래머스 환경에서는 프로시저가 안돌아가서..! 사용은 못하지만, 개념만 대충 알고 한번도 사용안해봤던 프로시저의 개념을 짚고 넘어갈 수 있었던 기회였습니다!
tiv_2016
의 합계 구하는 문
tiv_2015
값이 같은 사람들이 2명 이상인 그룹에 속해있어야함lat
, lon
)에 있으면 안됨
SELECT
ROUND(SUM(tiv_2016), 2) AS `tiv_2016`
FROM Insurance
WHERE tiv_2015 IN (
SELECT tiv_2015
FROM Insurance
GROUP BY tiv_2015
HAVING COUNT(pid) > 1
)
AND (lat, lon) IN (
SELECT lat, lon
FROM Insurance
GROUP BY lat, lon
HAVING COUNT(pid) = 1
)
ROUND(SUM(tiv_2016), 2)
를 이용한 소수점 둘째 자리 반올림 계산HAVING
을 이용하여 조건을 사용하여 그룹을 필터링
뭘 적어야... 잘 적었을까요.. 다른 분들 풀이도 동일합니도..
SELECT
DATE_FORMAT(SALES_DATE, "%Y-%m-%d") SALES_DATE,
PRODUCT_ID,
USER_ID,
SALES_AMOUNT
FROM
ONLINE_SALE
WHERE
SALES_DATE LIKE '2022-03%'
UNION ALL
SELECT
DATE_FORMAT(SALES_DATE, "%Y-%m-%d") SALES_DATE,
PRODUCT_ID,
NULL,
SALES_AMOUNT
FROM
OFFLINE_SALE
WHERE
SALES_DATE LIKE '2022-03%'
ORDER BY
SALES_DATE,
PRODUCT_ID,
USER_ID
SELECT DATE_FORMAT(SALES_DATE, '%Y-%m-%d') AS SALES_DATE, PRODUCT_ID, USER_ID, SALES_AMOUNT
FROM
(SELECT SALES_DATE, PRODUCT_ID, USER_ID, SALES_AMOUNT
FROM ONLINE_SALE
WHERE DATE_FORMAT(SALES_DATE, '%Y-%m') = '2022-03'
UNION ALL
SELECT SALES_DATE, PRODUCT_ID, NULL AS USER_ID, SALES_AMOUNT
FROM OFFLINE_SALE
WHERE DATE_FORMAT(SALES_DATE, '%Y-%m') = '2022-03'
) A
ORDER BY SALES_DATE, PRODUCT_ID, USER_ID
해당 문제에서.. Union All과 Union의 결과값이 동일하게 나옵니다..!
추천문제와 전주의 본인 순번 전 사람이 풀었던 문제 입니다.