CASE
식을 쓰자. 이번에는 조건이 복잡하므로 단순 CASE 말고, 검색 CASE를 쓰자LEFT(문자열, 정수)
: 문자열 자르기CONCAT(어쩌고, 저쩌고, 어찌저찌..)
: 이게 두개만 받는 경우도 있고, 세개 이상 받을 수 있는 경우도 있다. SQL 마다 다르다!LOWER(문자열)
: 알파벳 대문자를 소문자로!GROUP BY
를 까먹지 말자. 근데 이거 맨날 헷갈림..==
이 아니라 =
를 쓴다!REPLACE
OR TRANSLATE
둘다 가능하지만, MYSQL에는 TRANSLATE
가 없음JOIN
-> JOIN
종류를 알아놓자ABS
: 절댓값JOIN
의 기본값: INNER JOIN
SELECT
절에서 COUNT(*)
가 왜 들어가지?ORDER BY LAT_N DESC LIMIT 1
말고 WHERE
로 다중 조건을 할 순 없을까?Query a count of the number of cities in CITY having a Population larger than 100,000.
SELECT COUNT(NAME)
FROM CITY
WHERE POPULATION > 100000;
Query the total population of all cities in CITY where District is California.
SELECT POPULATION
FROM CITY
WHERE DISTRICT = 'California';
# 출력
124966
121780
92256
SELECT SUM(POPULATION)
FROM CITY
WHERE DISTRICT = 'California';
Query the average population of all cities in CITY where District is California.
AVG
SELECT AVG(POPULATION)
FROM CITY
WHERE DISTRICT = 'California';
Query the average population for all cities in CITY, rounded down to the nearest integer.
ROUND
SELECT ROUND(AVG(POPULATION))
FROM CITY;
Query the sum of the populations for all Japanese cities in CITY. The COUNTRYCODE for Japan is JPN.
==
이 아니라 =
를 쓴다! 자꾸 이거 헷갈려서 틀림 ㅠSELECT SUM(POPULATION)
FROM CITY
WHERE COUNTRYCODE = 'JPN';
Query the difference between the maximum and minimum populations in CITY.
SELECT MAX(POPULATION) - MIN(POPULATION)
FROM CITY;
Samantha was tasked with calculating the average monthly salaries for all employees in the EMPLOYEES table, but did not realize her keyboard's key was broken until after completing the calculation. She wants your help finding the difference between her miscalculation (using salaries with any zeros removed), and the actual average salary.
Write a query calculating the amount of error (i.e.: actual-miscalculated average monthly salaries), and round it up to the next integer.
Note: Salary is per month.
Constraints
2061
The table below shows the salaries without zeros as they were entered by Samantha:
Samantha computes an average salary of 98.00. The actual average salary is 2159.00.
The resulting error between the two calculations is 2159.00-98.00=2061.00. Since it is equal to the integer 2061, it does not get rounded up.
REPLACE
OR TRANSLATE
REPLACE(str, find, repl)
TRANSLATE
가 없음ROUND
: 반올림, CEILING
(또는 CEIL
): 올림, FLOOR
: 내림SELECT CEILING(AVG(Salary) - AVG(REPLACE(salary, '0', '')))
FROM EMPLOYEES;
SHOW COLUMNS FROM EMPLOYEES;
만약 그냥 CHAR(컬럼명)
을 하게되면, 아스키문자열로 바뀐다.
SELECT Name
, Salary
, CHAR(Salary)
FROM EMPLOYEES;
오라클에서는 그냥TO_CHAR
면 문자열로 바뀐다.
select TO_CHAR(12345) -- 12345: 공백 제거 없이 그대로 문자열로 바뀜
, TO_CHAR(123.45, 'FM0000.900') -- 0123.450: 1000의 자리 고정, 소수 세번째 자리 고정
, TO_CHAR(0.59, 'FM099.0') -- 000.6: 100의 자리 고정, 소숫점이 형식보다 길어서 반올림
, TO_CHAR(0.59, 'FM999.0') -- .6: 정수 고정 안됨, 소수점이 형식보다 길어서 반올림
, TO_CHAR(123, 'FM0') -- 오류
from EMPLOYEES;
그런데 MYSQL에서는 문자열로 변환 함수가 기억이 안나서 결국 검색해버렸다: CAST(컬럼명 AS 새로운 타입)
SELECT Name
, Salary
, CAST(Salary AS CHAR) -- Int -> Char
FROM EMPLOYEES;
이것저것 실험해봤는데, REPLACE(컬럼명, 바꿀 문자열, 새로운 문자열)
에 어떤 타입의 컬럼을 넣어도, 그 타입과 맞지 않아도 잘 찾아서 문자열로 바꿔줬다!!
SELECT Name
, Salary
, REPLACE(CAST(Salary AS CHAR), '0', '')
, REPLACE(CAST(Salary AS CHAR), 0, '') -- CHAR인데, 숫자 0 -> ''이 되네?
, REPLACE(Salary, 0, '')
, REPLACE(Salary, '0', '') -- INTEGER인데, 문자열 '0' -> ''으로 바뀌네?
FROM EMPLOYEES;
자동형변환 해주는걸 고려하면,
1. 0을 찾아서 빈 문자열('')로 바꾼다
2. 평균을 구한다.
3. 잘못된 평균값과 옳게 구한 평균값의 차를 구해 올림하여 출력한다.
SELECT CEILING(AVG(Salary) - AVG(REPLACE(Salary, 0, '')))
FROM EMPLOYEES;
자동형변환 해주는걸 고려하지 않고 정석(?)대로 하면..
1. Salary 컬럼을 Int에서 Char 타입으로 바꾼다.
2. 0을 찾아서 빈 문자열('')로 바꾼다
3. 다시 Signed(부호있는 숫자) 타입으로 바꾼다.
4. 평균을 구한다.
5. 잘못된 평균값과 옳게 구한 평균값의 차를 구해 올림하여 출력한다.
SELECT CEILING(AVG(Salary) - AVG(CAST(REPLACE(CAST(Salary AS CHAR), '0', '') AS SIGNED)))
FROM EMPLOYEES;
We define an employee's total earnings to be their monthly salary X months worked, and the maximum total earnings to be the maximum total earnings for any employee in the Employee table. Write a query to find the maximum total earnings for all employees as well as the total number of employees who have maximum total earnings. Then print these values as 2 space-separated integers.
where employee_id is an employee's ID number, name is their name, months is the total number of months they've been working for the company, and salary is the their monthly salary.
69952 1
The table and earnings data is depicted in the following diagram:
The maximum earnings value is 69952. The only employee with earnings = 69952 is Kimberly, so we print the maximum earnings value (69952) and a count of the number of employees who have earned $69952 (which is 1) as two space-separated values.
SELECT
절에서 COUNT(*)
가 왜 들어가지?SELECT (months*salary) AS earnings, COUNT(*)
FROM Employee
GROUP BY earnings
HAVING earnings = (SELECT MAX(months*salary) FROM Employee);
단순히 months * salary로 groupby를 하고, 그 수를 출력하면 최댓값만 출력할 수 없다!
SELECT MAX(months * salary)
, COUNT(*)
FROM Employee
GROUP BY months * salary;
우선 months * salary을 earnings로 별칭을 짓고, 그룹화 및 정렬 한다. '내림차순 했을 때 첫번째'가 바로 '최댓값'이기 때문이다.
SELECT months * salary AS earnings
, COUNT(*)
FROM Employee
GROUP BY earnings
ORDER BY earnings DESC
LIMIT 1;
groupby에서는 alias를 정의하는건 불가능하지만, select에서 정의한 alias를 groupby에서 사용하는건 가능하다! groupby가 select보다 먼저 실행되지만 알아서 적용된다.(다른 DB는..찾아보기!)
GROUP BY months * salary AS earnings
: 불가능!
아쉽게도 해커랭크 MYSQL의 버전이 5.7이라서, 8.0 버전 이전의 경우 WITH 구문 사용이 불가능하다는걸 알게되었다.
WITH earnings AS (
SELECT MAX(months * salary) AS max_earnings
, COUNT(*) AS count
FROM Employee
GROUP BY months * salary
ORDER BY max_earnings DESC
LIMIT 1
)
SELECT *
FROM earnings;
그래서 대신에 서브쿼리를 사용했다.
SELECT *
FROM (
SELECT MAX(months * salary) AS max_earnings
, COUNT(*) AS count
FROM Employee
GROUP BY months * salary
ORDER BY max_earnings DESC
LIMIT 1
) AS earnings;
WHERE
에 서브쿼리가 들어간다면:다만 매 행마다 저 조건을 탐색해야해서.. 아마 많이 느릴 것 같다.
SELECT MAX(months * salary), COUNT(*)
FROM Employee
WHERE months * salary = (SELECT MAX(months * salary) FROM Employee);
HAVING
에 서브쿼리가 들어간다면:마찬가지로 mysql에 한해서 select절의 alias를 where절과 having절에서 사용 가능하다.
단, having의 서브쿼리안에서는 alias가 오류가 났다.
HAVING earnings = (SELECT MAX(earnings) FROM Employee)
: 오류
SELECT (months*salary) AS earnings, COUNT(*)
FROM Employee
GROUP BY earnings
HAVING earnings = (SELECT MAX(months*salary) FROM Employee);
Query the following two values from the STATION table:
where LAT_N is the northern latitude and LONG_W is the western longitude.
Your results must be in the form:
lat lon
where lat is the sum of all values in LAT_N and lon is the sum of all values in LONG_W. Both results must be rounded to a scale of 2 decimal places.
SELECT ROUND(SUM(LAT_N), 2), ROUND(SUM(LONG_W), 2)
FROM STATION;
Query the sum of Northern Latitudes (LAT_N) from STATION having values greater than 38.7880 and less than 137.2345. Truncate your answer to decimal places.
where LAT_N is the northern latitude and LONG_W is the western longitude.
SELECT TRUNCATE(SUM(LAT_N), 4)
FROM STATION
WHERE LAT_N > 38.7880 AND LAT_N < 137.2345;
Query the greatest value of the Northern Latitudes (LAT_N) from STATION that is less than 137.2345. Truncate your answer to 4 decimal places.
where LAT_N is the northern latitude and LONG_W is the western longitude.
SELECT TRUNCATE(MAX(LAT_N), 4)
FROM STATION
WHERE LAT_N < 137.2345;
Query the Western Longitude (LONG_W) for the largest Northern Latitude (LAT_N) in STATION that is less than 137.2345. Round your answer to decimal places.
where LAT_N is the northern latitude and LONG_W is the western longitude.
ORDER BY LAT_N DESC LIMIT 1
말고 WHERE
로 다중 조건을 할 순 없을까?SELECT ROUND(LONG_W, 4)
FROM STATION
WHERE LAT_N < 137.2345
ORDER BY LAT_N DESC LIMIT 1;
SELECT ROUND(LONG_W, 4)
FROM STATION
WHERE LAT_N = (SELECT MAX(LAT_N) FROM station WHERE LAT_N < 137.2345);
WHERE
안에 서브쿼리를 넣자WHERE LAT_N = (SELECT MAX(lat_n) FROM station) AND LAT_N < 137.2345;
라고 하면 애초에 충돌을 해버림!!위의 풀이에서 바로 ORDER BY LAT_N DESC LIMIT 1
를 하느냐, 아니면 WHERE
절에서 서브쿼리로 MAX(LAT_N)
를 찾느냐 중 어느것이 더 빠른지 궁금해졌다.
일반적으로 시간 복잡도를 나타내는 빅오표기법은 이렇다.
그런데.. SQL에서는 어떨까?
서브쿼리 빼고 단순히 min/max와 order by & limit을 비교해보면, 최악의 상황에서는 min/max가 가장 빠르지만, 바람직한 상황이라면 둘의 차이는 없다고 한다.
찾아보니 인덱스에 따라, DB에 따라 차이가 난다고 한다. 더군다나 위 쿼리문에서는 서브쿼리에 min, max가 들어가므로 또 다른 변수가 생긴 셈이다.
이 고민을 정리하면 정렬기준과 출력 해야하는 컬럼이 다른 경우이고, 두가지 풀이방법이 있다.
그리고 이 두가지 풀이방법에 정답은 없다! 내 쿼리 계획에 따라 각각 실행해보며 비교하거나, 그때그때 잘 결정하자.
no answer to your question
Query the smallest Northern Latitude (LAT_N) from STATION that is greater than 38.7780. Round your answer to 4 decimal places.
where LAT_N is the northern latitude and LONG_W is the western longitude.
ORDER BY
로ASC
: 오름차순 정렬하기SELECT ROUND(LAT_N, 4)
FROM STATION
WHERE 38.7780 < LAT_N
ORDER BY LAT_N ASC LIMIT 1;
MIN
사용
SELECT ROUND(MIN(LAT_N), 4)
FROM STATION
WHERE LAT_N > 38.7780;
Query the Western Longitude (LONG_W)where the smallest Northern Latitude (LAT_N) in STATION is greater than 38.7780. Round your answer to 4 decimal places.
where LAT_N is the northern latitude and LONG_W is the western longitude.
SELECT ROUND(LONG_W, 4)
FROM STATION
WHERE 38.7780 < LAT_N
ORDER BY LAT_N ASC LIMIT 1;
WHERE에 서브쿼리로 풀면:
SELECT ROUND(LONG_W, 4)
FROM STATION
WHERE LAT_N = (
SELECT MIN(LAT_N)
FROM STATION
WHERE LAT_N > 38.7780
);
Query the Manhattan Distance between points and on a 2D plane and round it to a scale of 4 decimal places.
- : minimum value in Northern Latitude (LAT_N in STATION).
- : minimum value in Western Longitude (LONG_W in STATION).
- : maximum value in Northern Latitude (LAT_N in STATION).
- : maximum value in Western Longitude (LONG_W in STATION).
ROUND
와 ABS
중 어느것을 먼저하느냐에 따라 두가지 방법 존재SELECT ABS(ROUND(MIN(LAT_N), 4) - ROUND(MAX(LAT_N), 4)) + ABS(ROUND(MIN(LONG_W), 4) - ROUND(MAX(LONG_W), 4))
FROM STATION;
SELECT ROUND(ABS(MIN(LAT_N) - MAX(LAT_N)),4) + ROUND(ABS(MIN(LONG_W) - MAX(LONG_W)),4)
FROM STATION;
생각해보니, 최종적으로 마지막에만 반올림을 하면 되는거였다.
SELECT ROUND(ABS(MIN(LAT_N)-MAX(LAT_N)) + ABS(MIN(LONG_W)-MAX(LONG_W)), 4)
FROM STATION;
Consider and to be two points on a 2D plane where are the respective minimum and maximum values of Northern Latitude (LAT_N) and are the respective minimum and maximum values of Western Longitude (LONG_W) in STATION.
Query the Euclidean Distance between points and and format your answer to display 4 decimal digits.
POWER(숫자, 거듭제곱 횟수)
: 거듭제곱SQRT(숫자)
: 제곱근SELECT ROUND(SQRT(POWER(MIN(LAT_N) - MAX(LAT_N), 2) + POWER(MIN(LONG_W) - MAX(LONG_W), 2)), 4)
FROM STATION;
A median is defined as a number separating the higher half of a data set from the lower half. Query the median of the Northern Latitudes (LAT_N) from STATION and round your answer to decimal places.
OVER(세부기준..)
: GROUP BY
나 서브쿼리를 사용하지 않고 간단하게 분석함수(SUM, MAX, COUNT)나 집계함수(GROUP BY, ORDER BY)를 사용할 수 있음놀랍게도 mysql에는 median 함수가 없어서 직접 중앙값을 구해야한다.
처음 이 문제를 풀었을 때, 많은 블로그들이 percent_rank()로 백분률이 0.5인 행을 찾아 문제를 통과했다. 그러나 이 방법은 큰 반례가 존재한다.
SELECT ROUND(LAT_N, 4)
FROM (
SELECT LAT_N
, PERCENT_RANK() OVER (ORDER BY LAT_N ASC) AS percent
FROM STATION
) AS a
WHERE percent = 0.5;
행의 개수가 짝수인 경우, 백분률이 0.5인 경우가 존재하지 않기 때문이다.
중앙값을 직접 구하는 방법을 찾다가 좋은 블로그를 발견했다.
[SQL] MEDIAN 함수 없이 중앙값 구하기 글을 참고해서, row_number() 와 count() 를 이용해서 두가지 방법으로 중앙값을 구해보았다.
자칫하면 오류나기 쉬워서 꽤 헤맸고, 헤매면서 윈도우 함수에 대해 쪼끔 더 익숙해졌다.
중앙값이 위치한 행을 찾아서 그 값들의 평균을 구한다.
SELECT ROUND(AVG(LAT_N), 4)
FROM (
SELECT LAT_N
, COUNT(*) OVER() AS cnt
, ROW_NUMBER() OVER (ORDER BY LAT_N ASC) AS row_num
FROM STATION
) AS a
WHERE 2*row_num BETWEEN cnt AND cnt+2;
따라서 2*row_num이 n과 n+2 사이인 행들만 찾아내서 평균을 구하면(짝수인 경우 행이 2개이므로) 중앙값!!
1-1과 같은 방법인데, 내림과 올림을 사용했다.
SELECT ROUND(AVG(LAT_N), 4)
FROM (
SELECT LAT_N
, COUNT(*) OVER() AS cnt
, ROW_NUMBER() OVER (ORDER BY LAT_N ASC) AS row_num
FROM STATION
) AS a
WHERE row_num BETWEEN floor((cnt+1)/2) AND ceil((cnt+1)/2);
우선 홀수 행인 경우, 전체 행의 개수를 2로 나눈게 곧 중앙값의 행이다.
그런데 짝수 행인 경우, 전체 행의 개수를 2로 나누면 소수점이 발생한다.
중앙값은 3행과 4행을 구해서 이들의 평균을 구해야하는데, 이는 각각 3.5행의 내림값과 올림값과 같다.
FROM의 서브쿼리에서 COUNT(*)
시에 OVER()
을 사용해야만 하지 않으면, groupby가 되어서 다음 오류가 발생한다.
COUNT(*)
는 단일값을 출력하는 함수인데, 이것을 FROM 절에 넣어서 모든 행마다 행의 수를 출력하라면 반드시 OVER()
을 써줘야 한다. ERROR 1140 (42000) at line 15: In aggregated query without GROUP BY, expression #1 of SELECT list contains nonaggregated column 'STATION.LAT_N'; this is incompatible with sql_mode=only_full_group_by
1-1에서 만약 (2*row_num - cnt) between 0 and 2
을 하게 되면, 다음 오류가 발생한다. 아무래도 2*row_num - cnt
이 마이너스가 되는 경우, 컬럼 타입의 범위를 벗어나서 생기는 오류로 보인다.
ERROR 1690 (22003) at line 6: BIGINT UNSIGNED value is out of range in '((2 *
a
.row_num
) -a
.cnt
)'
ROW_NUMBER()와 COUNT(*)를 사용하는건 같은데, WHERE절에서 CASE WHEN
을 사용해서 짝수 행인 경우와 홀수 행인 경우를 나눈다.
MOD()
: 나머지 구하기SELECT ROUND(AVG(LAT_N), 4)
FROM (
SELECT LAT_N
, COUNT(*) OVER() AS cnt
, ROW_NUMBER() OVER (ORDER BY LAT_N ASC) AS row_num
FROM STATION
) AS a
WHERE
CASE
WHEN MOD(cnt, 2) = 0 THEN row_num IN (cnt/2, (cnt/2)+1) -- 짝수 행인 경우
ELSE row_num = (cnt+1)/2 -- 홀수 행인 경우
END;
윈도우 함수를 얼렁뚱땅(=그때그때 구글링 하면서) 익혔더니, 매번 검색하게 된다. 한번 날잡고 다져봐야지.