문제

Find the name of the user who has rated the greatest number of movies. In case of a tie, return the lexicographically smaller user name.

Find the movie name with the highest average rating in February 2020. In case of a tie, return the lexicographically smaller movie name.

 

두 문제이다. 하나는 가장 영화 리뷰를 많이 남긴 사람의 이름을 출력해야한다. 만약 리뷰 수가 같다면 사전순으로 더 작은 사람을 출력한다.

다른 하나는 2020년 2월에 평균 평점이 가장 높은 영화 이름을 찾야야한다. 동점인 경우에 사전순으로 더 작은 영화 이름을 출력한다.

 

테이블 및 예제

[Movies]

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| movie_id      | int     |
| title         | varchar |
+---------------+---------+
movie_id is the primary key (column with unique values) for this table.
title is the name of the movie.

 

[Users]

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| user_id       | int     |
| name          | varchar |
+---------------+---------+
user_id is the primary key (column with unique values) for this table.

 

MovieRating

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| movie_id      | int     |
| user_id       | int     |
| rating        | int     |
| created_at    | date    |
+---------------+---------+
(movie_id, user_id) is the primary key (column with unique values) for this table.
This table contains the rating of a movie by a user in their review.
created_at is the user's review date. 

 

[Examples]

Input: 
Movies table:
+-------------+--------------+
| movie_id    |  title       |
+-------------+--------------+
| 1           | Avengers     |
| 2           | Frozen 2     |
| 3           | Joker        |
+-------------+--------------+
Users table:
+-------------+--------------+
| user_id     |  name        |
+-------------+--------------+
| 1           | Daniel       |
| 2           | Monica       |
| 3           | Maria        |
| 4           | James        |
+-------------+--------------+
MovieRating table:
+-------------+--------------+--------------+-------------+
| movie_id    | user_id      | rating       | created_at  |
+-------------+--------------+--------------+-------------+
| 1           | 1            | 3            | 2020-01-12  |
| 1           | 2            | 4            | 2020-02-11  |
| 1           | 3            | 2            | 2020-02-12  |
| 1           | 4            | 1            | 2020-01-01  |
| 2           | 1            | 5            | 2020-02-17  | 
| 2           | 2            | 2            | 2020-02-01  | 
| 2           | 3            | 2            | 2020-03-01  |
| 3           | 1            | 3            | 2020-02-22  | 
| 3           | 2            | 4            | 2020-02-25  | 
+-------------+--------------+--------------+-------------+
Output: 
+--------------+
| results      |
+--------------+
| Daniel       |
| Frozen 2     |
+--------------+

 

풀이

임시테이블을 사용해서 조건에 맞는 테이블을 생성하고 limit을 걸어서 열을 뽑아낸다. 후에 UNION을 사용해서 합쳐주면 결과를 도출 가능하다.

WITH 
g_movie AS (
    SELECT r.movie_id,COUNT(r.user_id) AS cnt, m.title, u.name
    FROM MovieRating AS r
    JOIN Movies AS m ON r.movie_id = m.movie_id JOIN Users AS u ON u.user_id = r.user_id
    GROUP BY r.user_id
),
fin1 AS (
SELECT name AS results
FROM g_movie
ORDER BY cnt DESC, name
limit 1
),
FE20 AS(
    SELECT m.title AS results
    FROM MovieRating AS mr JOIN Movies AS m 
        ON mr.movie_id = m.movie_id
    WHERE mr.created_at BETWEEN '2020-02-01' AND '2020-02-28'
    GROUP BY mr.movie_id 
    ORDER BY AVG(mr.rating) DESC, m.title
    limit 1
)
SELECT * FROM FE20
UNION ALL
SELECT * FROM fin1

 

처음에는 각각 임시테이블을 만들고 조건에 맞는 열을 뽑아냈는데 UNION을 하는게 어려워졌다.

따라서 위와 같은 방식으로 열을 뽑아내는 임시테이블을 쭉 만들어낸 다음에 테이블을 가져와서 UNION 해주었다.

문제

Write a solution to calculate the number of bank accounts for each salary category.

The salary categories are:

  • "Low Salary": All the salaries strictly less than $20000.
  • "Average Salary": All the salaries in the inclusive range [$20000, $50000].
  • "High Salary": All the salaries strictly greater than $50000.

The result table must contain all three categories. If there are no accounts in a category, return 0.

Return the result table in any order.

 

문제는 조건에 맞추어서 20000달러, 50000달러를 기준으로 Salary에 해당하는 계정의 수를 찾으면 된다.

 

테이블 & 예제

[Accounts]

+-------------+------+
| Column Name | Type |
+-------------+------+
| account_id  | int  |
| income      | int  |
+-------------+------+
account_id is the primary key (column with unique values) for this table.
Each row contains information about the monthly income for one bank account.

 

[Examples]

Input: 
Accounts table:
+------------+--------+
| account_id | income |
+------------+--------+
| 3          | 108939 |
| 2          | 12747  |
| 8          | 87709  |
| 6          | 91796  |
+------------+--------+
Output: 
+----------------+----------------+
| category       | accounts_count |
+----------------+----------------+
| Low Salary     | 1              |
| Average Salary | 0              |
| High Salary    | 3              |
+----------------+----------------+

 

풀이

풀이 방법은 임시테이블을 만들어서 각 조건에 맞으면 1 틀리면 0을 부여하는 방법으로 진행 후 Union을 사용해서 해당 열의 SUM을 가져왔다.

하나씩 풀이하겠다.

1. 임시테이블을 만들어서 조건에 맞는 값에 1 부여하기

CASE WHEN을 이용해서 만들어주었다.

WITH temp AS (
    SELECT 
        *,
        CASE WHEN income < 20000 THEN 1 ELSE 0 END AS "Low_S",
        CASE WHEN income BETWEEN 20000 AND 50000 THEN 1 ELSE 0 END AS "Average_S",
        CASE WHEN income > 50000 THEN 1 ELSE 0 END AS "High_S"
    FROM Accounts
)
SELECT *
FROM temp

 

결과는 아래와 같다.

| account_id | income | Low_S | Average_S | High_S |
| ---------- | ------ | ----- | --------- | ------ |
| 3          | 108939 | 0     | 0         | 1      |
| 2          | 12747  | 1     | 0         | 0      |
| 8          | 87709  | 0     | 0         | 1      |
| 6          | 91796  | 0     | 0         | 1      |

 

이제 각 열을 행으로 바꾸고, 열별 SUM값을 구하면된다. 이 문제를 포스팅하게 된 계기이기도하다.

 

2. UNION ALL 이용하기

SELECT 문에 데이터를 문자열로 넣고 열의 이름을 지정한 후 이렇게 만들어진 여러 테이블을 UNION을 하면 완성이다! WOW

우선 임시로 테이블을 하나정도 만들어보겠다.

SELECT 'High Salary' AS category, SUM(High_S) AS accounts_count 
FROM temp
| category    | accounts_count |
| ----------- | -------------- |
| High Salary | 3              |

 

SQL 문제를 풀 때 마다 매번 새롭게 배우는 것 같다.

여튼 이렇게 각 Salary마다 테이블을 만들어서 UNION을 해주면 된다.

WITH temp AS (
    SELECT 
        *,
        CASE WHEN income < 20000 THEN 1 ELSE 0 END AS "Low_S",
        CASE WHEN income BETWEEN 20000 AND 50000 THEN 1 ELSE 0 END AS "Average_S",
        CASE WHEN income > 50000 THEN 1 ELSE 0 END AS "High_S"
    FROM Accounts
)
SELECT 'High Salary' AS category, SUM(High_S) AS accounts_count 
FROM temp
UNION ALL
SELECT 'Low Salary' AS category, SUM(Low_S) AS accounts_count 
FROM temp
UNION ALL
SELECT 'Average Salary' AS category, SUM(Average_S) AS accounts_count 
FROM temp

문제

Write a solution to find the prices of all products on 2019-08-16. Assume the price of all products before any change is 10. Return the result table in any order.

2019-08-16일의 제품 가격을 구해야한다. 이후에 가격이 정해졌다면 기본가격인 10으로 계산해야한다.

 

테이블 & 예제

[Products]

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| product_id    | int     |
| new_price     | int     |
| change_date   | date    |
+---------------+---------+

 

[Examples]

Input: 
Products table:
+------------+-----------+-------------+
| product_id | new_price | change_date |
+------------+-----------+-------------+
| 1          | 20        | 2019-08-14  |
| 2          | 50        | 2019-08-14  |
| 1          | 30        | 2019-08-15  |
| 1          | 35        | 2019-08-16  |
| 2          | 65        | 2019-08-17  |
| 3          | 20        | 2019-08-18  |
+------------+-----------+-------------+
Output: 
+------------+-------+
| product_id | price |
+------------+-------+
| 2          | 50    |
| 1          | 35    |
| 3          | 10    |
+------------+-------+

풀이

아..잊고있던 Union을 활용하는 문제였다.

역시 생각을 유연하게 해야하는데 더 많은 선택지를 고려하는 연습이 부족한가 싶다. 그래도 잘하고있다!!

Solution에서 가져와서 풀이를 작성한다.

 

1. 2019-08-16 이후에 변경되는 컬럼들만을 선택해서 제품가격을 10으로 변경한다.

: 이걸 CTE를 이용해서 기존 데이터안에 같이 들어가게 해주었는데 16일에 가장 근접한 가격을 찾는데 어려움이 많았다.

오히려 이렇게 16일 이후의 날짜만을 모아서 컬럼을 생성해주는 것이 더 편리하다.

SELECT
    DISTINCT(product_id),
    10 AS price,
    change_date
FROM products
GROUP BY product_id
HAVING
    min(change_date) > '2019-08-16'
| product_id | price | change_date |
| ---------- | ----- | ----------- |
| 3          | 10    | 2019-08-18  |

 

2. 16일 이전의 값에 대해서 가장 최신 가격을 산출해야한다.

방식은 이러하다. 각 product_id 별로 change_date의 max값을 가져와서 동일한 열만을 추출한다.

WITH temp AS (
    SELECT
        product_id,
        new_price,
        change_date,
        max(change_date) OVER (PARTITION BY product_id ORDER BY product_id)AS recent_date
    FROM
        products
    WHERE 
        change_date <= '2019-08-16'

)
SELECT product_id, new_price AS price
FROM temp
WHERE change_date = recent_date;

 

3. 합친다!! > UNION, 전체 코드는 이러하다.

WITH temp AS (
    SELECT
        product_id,
        new_price,
        change_date,
        max(change_date) OVER (PARTITION BY product_id ORDER BY product_id)AS recent_date
    FROM
        products
    WHERE 
        change_date <= '2019-08-16'

)
SELECT product_id, new_price AS price
FROM temp
WHERE change_date = recent_date

UNION

SELECT
    DISTINCT(product_id),
    10 AS price
FROM products
GROUP BY product_id
HAVING
    min(change_date) > '2019-08-16'

 

참고) UNION & UNION ALL

[UNION]

여러 쿼리문들을 합쳐서 하나의 쿼리문으로 만들어주는 방법이다.

- 중복된 값을 제거하고 보여준다는 특징이 있다!

- 이 떄문에 UNION ALL 보다 속도가 느리다

 

[UNION ALL]

여러 쿼리문들을 합쳐서 하나의 쿼리문으로 만들어주는 방법이다. (??)

- 차이점은 중복된 값을 모두 보여준다는 점이다.

 

[JOIN과의 차이점]

JOIN은 열을 기준으로 결합한다는점, UNION은 행으로 결합한다는 점이 차이점이다!

+ Recent posts