대체적으로 연습 및 실무에서 윈도우 함수 관련 쿼리문을 많이 작성해봤던터라, 강의 이해가 잘되었던 것 같다.

 

FIRST VALUE, LAST VALUE, QUALIFY 연습문제 등을 통해서 몰랐던 새로운 함수들을 알게되었고

특히 QUALIFY 함수는 유용하게 쓰일 수 있을 것 같다.!!!

 

 

 

탐색함수 연습문제 1~3


문제 1) user들의 다음 접속 월과 다다음 접속 월을 구하는 쿼리를 작성해주세요.

LEAD는 반드시 정렬이 먼저 되어야 함 -> ORDER BY 추가

SELECT *,
       LEAD(visit_month) OVER (PARTITION BY user_id ORDER BY visit_month ASC) AS lead_visit_month,
       LEAD(visit_month,2) OVER (PARTITION BY user_id ORDER BY visit_month ASC) AS lead2_visit_month



FROM advanced.analytics_function_01


문제 2) user들의 다음 접속 월과 다다음 접속 월, 이전 접속 월을 구하는 쿼리를 
작성해주세요.


SELECT *,
       LEAD(visit_month) OVER (PARTITION BY user_id ORDER BY visit_month ASC) AS after_visit_month,
       LEAD(visit_month,2) OVER (PARTITION BY user_id ORDER BY visit_month ASC) AS after2_visit_month,
       LAG(visit_month) OVER (PARTITION BY user_id ORDER BY visit_month ASC) AS before_visit_month

ORDER BY user_id, visit_month       
FROM advanced.analytics_function_01


문제 3) 유저가 접속 했을 때, 다음 접속까지의 간격


SELECT *,
       after_visit_month - visit_month AS diff

FROM
(       
    SELECT *,
    LEAD(visit_month, 1) OVER (PARTITION BY user_id ORDER BY visit_month) AS after_visit_month

    FROM advanced.analytics_function_01
)

 

 

 

 

FIRST VALUE -> 첫번째 값 구하기 (NULL 값 포함) \, NULL 값을 제외하고 싶으면, OVER 앞 IGNORE NULLS 추가
LAST VALUE -> 마지막 값 구하기 (NULL 값 포함), NULL 값을 제외하고 싶으면, OVER 앞 IGNORE NULLS 추가


집계함수는 NULL을 제외 후 연산, COUNT * 제외


* 파티션이 없을 때
프레임 범위 기본값
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING


* 파티션이 있을 때
프레임 범위 기본값
파티션 내에서 현재 로우 까지가 기본값

---------------------------------------------------------------------

---------------------------------------------------------------------


Frame 설정하는 2가지 방법

1) ROWS
- 물리적인 행 수를 기준으로 경계를 지정
- 이전 행, 이후 3개의 행
- ROWS Frame를 더 많이 사용

2) RANGE
- 논리적인 값의 범위를 기준으로 지정
- 값의 3일 전, 3일 후




EX) 

AVG(col) OVER (PARTITION BY product_type ORDER BY timestamp

ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
Frame의 시작과 끝 지점 명시하기
- PRECEDING : 현재 행 기준으로 이전 행
- CURRENT ROW : 현재 행
- FOLLOWING : 현재 행 기준으로 이후 행
- UNBOUNDED : 처음부터 또는 끝까지(사전적 의미 : 묶이지 않고 제한되지 않음)




AVG(col) OVER (PARTITION BY product_type ORDER BY timestamp
ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING)
 = 현재 행과 그 앞뒤 한 행씩을 포함해서 평균

AVG(col) OVER (PARTITION BY product_type ORDER BY timestamp
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
 = 파티션의 처음부터 현재 행을 포함한 평균

 

 

 

 

FRAME 함수 연습문제

-- amount_total: 전체 SUM
-- cumulative_sum : row 시점에 누적 SUM
-- cumulative_sum_by_user : row 시점에 유저별 누적 SUM
-- last_5_orders_avg_amount : order_id 기준으로 정렬하고, 직전 5개 주문의 평균 amount
-- 집계분석함수() OVER(PARTITION BY ~~~ ORDER BY ~~~ ROWS BETWEEN A AND B)



SELECT *,
       SUM(amount) OVER() AS amount_total,
-- OVER()에 아무것도 들어가지 않을 수 있음
       SUM(amount) OVER(ORDER BY order_id) AS cumulative_sum,
       SUM(amount) OVER(PARTITION BY user_id ORDER BY order_id) AS cumulative_sum_by_user,
       AVG(amount) OVER(ORDER BY order_id ROWS BETWEEN 5 PRECEDING AND 1 PRECEDING)
       

FROM advanced.orders      
ORDER BY user_id, order_date

 

 

 

 

QUALIFY 연습문제


WHERE 대신 QUALIFY를 사용하면 윈도우 함수의 결과에 대해 필터링할 수 있음
WHERE과 같이 사용하는 경우엔 WHERE 아래에 작성하면 됨

 SELECT
 order_id,
 order_date,
 user_id,
 amount,
 SUM(amount) OVER (PARTITION BY user_id) AS amount_total
 FROM advanced.orders
 WHERE 1=1
 QUALIFY amount_total >= 500

 

 

 

 

 

윈도우 함수 연습문제


문제 1

SELECT user,
       team,
       query_date,
       COUNT(query_date) OVER (PARTITION BY user) AS total_query_cnt
FROM advanced.query_logs
ORDER BY user, query_date




문제 2 

WITH base AS (
    SELECT 
          EXTRACT(week FROM query_date) AS week_number,
          user,
          team,
          COUNT(query_date) AS total_query_cnt
  FROM advanced.query_logs
  GROUP BY week_number, user, team
)

SELECT 
      week_number,
      team,
      user,
      total_query_cnt,
      RANK() OVER (PARTITION BY team ORDER BY total_query_cnt DESC) AS ranking_in_team
FROM base
QUALIFY ranking_in_team = 1
ORDER BY week_number, team




문제 3

WITH base AS (
    SELECT 
          EXTRACT(week FROM query_date) AS week_number,
          user,
          team,
          COUNT(query_date) AS query_cnt
  FROM advanced.query_logs
  GROUP BY week_number, user, team
)

SELECT 
      user,
      team,
      week_number,
      query_cnt,
      LAG(query_cnt) OVER (PARTITION BY user ORDER BY week_number) AS prev_week_query_cnt
FROM base 
ORDER BY user, week_number




문제 4

SELECT 
      user,
      team,
      query_date,
      query_count,
      SUM(query_count) OVER (PARTITION BY user ORDER BY query_date,
      ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS cumulative_query_count
FROM (
      SELECT 
          user, 
          team,
          query_date,
          COUNT(query_date) AS query_count
  FROM advanced.query_logs
  GROUP BY 1,2,3
)
ORDER BY user, query_date




문제 5

WITH raw_data AS (
     SELECT DATE '2024-05-01' AS date, 15 AS number_of_orders UNION ALL
     SELECT DATE '2024-05-02', 13 UNION ALL
     SELECT DATE '2024-05-03', NULL UNION ALL
     SELECT DATE '2024-05-04', 16 UNION ALL
     SELECT DATE '2024-05-05', NULL UNION ALL
     SELECT DATE '2024-05-06', 18 UNION ALL
     SELECT DATE '2024-05-07', 20 UNION ALL
     SELECT DATE '2024-05-08', NULL UNION ALL
     SELECT DATE '2024-05-09', 13 UNION ALL
     SELECT DATE '2024-05-10', 14 UNION ALL
     SELECT DATE '2024-05-11', NULL UNION ALL
     SELECT DATE '2024-05-12', NULL
)

SELECT 
      date,
      LAST_VALUE(number_of_orders ignore nulls) OVER (ORDER BY date) AS number_of_orders
FROM raw_data




문제 6


WITH raw_data AS (
     SELECT DATE '2024-05-01' AS date, 15 AS number_of_orders UNION ALL
     SELECT DATE '2024-05-02', 13 UNION ALL
     SELECT DATE '2024-05-03', NULL UNION ALL
     SELECT DATE '2024-05-04', 16 UNION ALL
     SELECT DATE '2024-05-05', NULL UNION ALL
     SELECT DATE '2024-05-06', 18 UNION ALL
     SELECT DATE '2024-05-07', 20 UNION ALL
     SELECT DATE '2024-05-08', NULL UNION ALL
     SELECT DATE '2024-05-09', 13 UNION ALL
     SELECT DATE '2024-05-10', 14 UNION ALL
     SELECT DATE '2024-05-11', NULL UNION ALL
     SELECT DATE '2024-05-12', NULL
)

SELECT 
      date, 
      number_of_orders,
      AVG(number_of_orders) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg
FROM (
      SELECT 
            date,
            LAST_VALUE(number_of_orders ignore nulls) OVER (ORDER BY date) AS number_of_orders
      FROM raw_data
)




문제 7


WITH base AS (
  SELECT 
    event_date,
    event_timestamp,
    DATETIME(TIMESTAMP_MICROS(event_timestamp),'Asia/Seoul') AS event_datetime,
    event_name,
    user_id,
    user_pseudo_id,
    LAG(DATETIME(TIMESTAMP_MICROS(event_timestamp),'Asia/Seoul')) OVER (PARTITION BY user_pseudo_id ORDER BY event_timestamp) AS before_event_datetime
  FROM advanced.app_logs
  WHERE 1=1
  AND event_date = '2022-08-18'
)

SELECT
    *,
    DATETIME_DIFF(event_datetime, before_event_datetime, SECOND) AS second_diff,
    CASE WHEN 
      DATETIME_DIFF(event_datetime, before_event_datetime, SECOND) IS NULL
          OR DATETIME_DIFF(event_datetime, before_event_datetime, SECOND) >= 20 THEN 1 
      ELSE 0 
    END AS session_start,
    SUM(CASE WHEN DATETIME_DIFF(event_datetime, before_event_datetime, SECOND) >= 20 THEN 1 ELSE 0 END)
    OVER (PARTITION BY user_pseudo_id ORDER BY event_datetime) + 1 AS session_temp
FROM base

 

  1. STRUCT 과 UNNEST 처음 접해보는 내용이라, 복습 필요.
  2. PIVOT 내용 중 ANY_VALUE는 데이터 양이 많고, 어떤 데이터들이 어떤 특성을 가지고 담겨있는지 정확하게 모른다면 활용하면 위험하겠다는 생각이 들었음.

 

Q1. STRUCT, UNNEST

1. array_exercises 테이블에서 각 영화(title)별로 장르(genres)를 UNNEST해서 보여주세요.



SELECT title,
      --  genres,
       genre
       

FROM advanced.array_exercises AS ae
CROSS JOIN UNNEST(genres) AS genre

-- genres는 평면화가 된 데이터를 의미
-- genres가 지금 배열
-- ARRAY : 같은 타입의 여러 데이터를 저장하고 싶을 때
-- ARRAY를 풀때 Flattten(평면화) -> UNNEST
-- UNNEST릃 할 때는 CROSS JOIN + UNNEST(ARRAY_COLUMN) 컬럼 명시









2) array_exercises 테이블에서 각 영화(title)q별로 배우(actor)와 배역(character)을 보여주세요. 배우와 배역은 별도의 컬럼으로 나와야 함.



SELECT title,
      --  actors

-- actor에 직접 접근하면 어떨까 -> 새로운 컬럼으로 가능하나, 매번 SAFE_OFFSET을 지정해야 함
-- actors = [STRUCT(STRING,STRING)]

actors[SAFE_OFFSET(0)].actor AS first_actor,
actors[SAFE_OFFSET(0)].actor AS first_character,
actors[SAFE_OFFSET(1)].actor AS second_actor,
actors[SAFE_OFFSET(1)].actor AS second_character

-- 배열에 직접 접근이 아닌 UNNEST로 풀어야 편리할 듯

FROM advanced.array_exercises as ae



---------------------------------------------------------------
---------------------------------------------------------------




SELECT title,
       actor.actor,
       actor.character

FROM advanced.array_exercises as ae
CROSS JOIN UNNEST(actors) AS actor
-- actors가 배열










3) array_exercises 테이블에서 각 영화(title) 별로 배우(actor), 배역(character), 장르(genre)를 출력. 한 row에 배우, 배역, 장르가 모두 표시되어야 함.




SELECT title,
      --  actors, #ARRAY<STRUCT(STRING, STRING)>
      actor.actor as actor,
      actor.character as character,
      -- genres # ARRAY<STRING>
      genre

FROM advanced.array_exercises
CROSS JOIN UNNEST(actors) AS actor
CROSS JOIN UNNEST(genres) AS genre

 

 

 

Q2. PIVOT

1-1)
1) orders 테이블에서 유저(user_id)별로 주문 금액(amount)의 합계를 PIVOT해주세요. 
날짜(order_date)를 행(Row)으로, user_id를 열(Column)으로 만들어야 합니다

SELECT order_date,
       IF(user_id = 1, amount , NULL) AS user_1,
       IF(user_id = 2, amount , NULL) AS user_2,
       IF(user_id = 3, amount , NULL) AS user_3

FROM(
  SELECT order_date,
        user_id,
        amount       

  FROM advanced.orders
  GROUP BY order_date, user_id, amount
  ORDER BY order_date
)
---------------------------------------------------------------
---------------------------------------------------------------

1-2)
SELECT order_date,
       MAX(IF(user_id = 1, amount , NULL)) AS user_1,
       MAX(IF(user_id = 2, amount , NULL)) AS user_2,
       MAX(IF(user_id = 3, amount , NULL)) AS user_3

FROM(
  SELECT order_date,
        user_id,
        amount       

  FROM advanced.orders
  GROUP BY order_date, user_id, amount
  ORDER BY order_date
)

GROUP BY order_date
ORDER BY order_date




---------------------------------------------------------------
---------------------------------------------------------------


2)  orders 테이블에서 날짜(order_date)별로 유저들의 주문 금액(amount)의 합계를 PIVOT해주세요. user_id를 행(Row)으로, order_date를 열(Column)으로 만들어야 합니다.

SELECT order_date,
       SUM(IF(user_id = 1, amount , NULL)) AS user_1,
       SUM(IF(user_id = 2, amount , NULL)) AS user_2,
       SUM(IF(user_id = 3, amount , NULL)) AS user_3
FROM advanced.orders
GROUP BY order_date       
ORDER BY order_date



backtick 활용
any value는 어디에 활용할 수 있을지? -> 데이터는 믿을수 없기에 일부 데이터만 보고 사용 판단하기엔 위험할 것 같음.

SELECT user_id,
       SUM(IF(order_date = "2023-05-01", amount, 0)) AS `2023-05-01`,
       SUM(IF(order_date = "2023-05-02", amount, 0)) AS `2023-05-02`,
       SUM(IF(order_date = "2023-05-03", amount, 0)) AS `2023-05-03`,
       SUM(IF(order_date = "2023-05-04", amount, 0)) AS `2023-05-04`,
       SUM(IF(order_date = "2023-05-05", amount, 0)) AS `2023-05-05`,


FROM advanced.orders
GROUP BY user_id
ORDER BY user_id



---------------------------------------------------------------
---------------------------------------------------------------

 
3) orders 테이블에서 사용자(user_id)별, 날짜(order_date)별로 주문이 있다면 1, 없다면 0으로 PIVOT 해주세요. user_id를 행(Row)으로, order_date를 열(Column)로 만들고 주문을 많이 해도 1로 처리합니다

3-1) 주문 여부 1,0

SELECT user_id,
       SUM(IF(order_date = "2023-05-01", 1, 0)) AS `2023-05-01`,
       SUM(IF(order_date = "2023-05-02", 1, 0)) AS `2023-05-02`,
       SUM(IF(order_date = "2023-05-03", 1, 0)) AS `2023-05-03`,
       SUM(IF(order_date = "2023-05-04", 1, 0)) AS `2023-05-04`,
       SUM(IF(order_date = "2023-05-05", 1, 0)) AS `2023-05-05`,


FROM advanced.orders
GROUP BY user_id
ORDER BY user_id



3-2) 횟수


SELECT user_id,
       SUM(IF(order_date = "2023-05-01", 1, 0)) AS `2023-05-01`,
       SUM(IF(order_date = "2023-05-02", 1, 0)) AS `2023-05-02`,
       SUM(IF(order_date = "2023-05-03", 1, 0)) AS `2023-05-03`,
       SUM(IF(order_date = "2023-05-04", 1, 0)) AS `2023-05-04`,
       SUM(IF(order_date = "2023-05-05", 1, 0)) AS `2023-05-05`,


FROM advanced.orders
GROUP BY user_id
ORDER BY user_id





-- 앱 로그 PIVOT


WITH base AS(
    SELECT event_date,
          event_timestamp,
          event_name,
          user_id,
          user_pseudo_id,
          MAX(IF(param.key = "firebase_screen", param.value.string_value, NULL)) AS firebase_screen,
          MAX(IF(param.key = "food_id", param.value.int_value, NULL)) AS food_id,
          MAX(IF(param.key = "session_id", param.value.string_value, NULL)) AS session_id

    -- * EXCEPT(event_params)
          
    FROM advanced.app_logs
    CROSS JOIN UNNEST(event_params) AS param
    WHERE event_date = "2022-08-01"
    GROUP BY ALL

)

SELECT event_date,
       COUNT(user_id) AS user_cnt
FROM base
WHERE event_name = "click_cart" AND food_id = 1544
GROUP BY event_date

 

 

 

 

Q3. 퍼널

-- 이중 WITH 문
WITH BASE AS(
      SELECT event_date,
            event_timestamp,
            event_name,
            user_id,
            user_pseudo_id,
            platform,
            --  event_param

            MAX(IF(event_param.key = "firebase_screen", event_param.value.string_value, NULL )) AS firebase_screen,
            -- MAX(IF(event_param.key = "food_id", event_param.value.int_value, NULL )) AS food_id,
            MAX(IF(event_param.key = "session_id", event_param.value.string_value, NULL )) AS session_id
            

      FROM advanced.app_logs

      CROSS JOIN UNNEST(event_params) AS event_param

      WHERE event_date BETWEEN "2022-08-01" AND "2022-08-18"
      GROUP BY ALL
)


--event_name + screen (필요한 이벤트만 조건 걸어서 사용)
  ,filter_event_and_concat_event_and_screen AS(
  

  SELECT * EXCEPT(event_name, firebase_screen, event_timestamp),
        CONCAT(event_name, "-", firebase_screen) AS event_name_with_screen,
        DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_datetime
  FROM BASE
  WHERE event_name IN ("screen_view","click_payment")
  )


--step_number + COUNT
--CASE WHEN 사용
SELECT event_date,
       event_name_with_screen,
       
       CASE WHEN event_name_with_screen = "screen_view-welcome" THEN 1
            WHEN event_name_with_screen = "screen_view-home" THEN 2
            WHEN event_name_with_screen = "screen_view-food_category" THEN 3
            WHEN event_name_with_screen = "screen_view-restaurant" THEN 4
            WHEN event_name_with_screen = "screen_view-cart" THEN 5
            WHEN event_name_with_screen = "click_payment-cart" THEN 6
       ELSE NULL
       END AS step_number,
       COUNT(DISTINCT user_pseudo_id) AS cnt
FROM filter_event_and_concat_event_and_screen
GROUP BY ALL
HAVING step_number IS NOT NULL
ORDER BY event_date

-- food_detail, search, search_result도 파악

 

 

기본 정규표현식 기호

1 ^ 문자열의 시작을 나타냄 패턴이 문자열의 맨 앞에서부터 일치해야 함
2 $ 문자열의 을 나타냄. 패턴이 문자열의 맨 끝에서 일치해야 함
3 . 임의의 단일 문자와 일치함. 어떤 문자든 한 글자와 일치할 수 있음
4 * 바로 앞의 패턴이 0 이상 반복될 수 있음을 의미함
5 + 바로 앞의 패턴이 1 이상 반복될 수 있음을 의미함
6 | OR 연산자로, 앞의 패턴 또는 뒤의 패턴 중 하나와 일치하면 됨
7 [ ] 문자 클래스에 해당, 대괄호 안에 있는 문자들 중 하나와 일치하면 됨
 - 예: [aeiou]는 "a", "e", "i", "o", "u" 중 하나의 문자와 일치
8 { } 바로 앞의 패턴이 정확히 n 반복되는 경우와 일치함
 - 예: ([DdIi]){2}는 "D", "d", "I", "i" 중 1개 문자가 두 번 연속 나타남
9 ( ) 그룹을 나타냄. 패턴의 일부를 그룹으로 묶어서 처리할 수 있음
10 [^....] 대괄호 안에 있는 문자들을 제외한 문자와 일치함
 - 예: [^aeiou]는 "a", "e", "i", "o", "u"를 제외한 문자와 일치

 

기본 패턴

1.    ^([DdIi]){2}: 문자열의 시작에서 "D", "d", "I", "i" 중 하나가 두 번 연속으로 나타남

 

2.    ^[aeiou].*[aeiou]$: 문자열이 모음으로 시작하고, 중간에 0개 이상의 임의 문자가 있으며, 마지막에 모음으로 끝

 

3.    ^[aeiou].+[aeiou]$: 문자열이 모음으로 시작하고, 중간에 최소 한 글자의 임의 문자가 있으며, 마지막에 모음으로 끝

 

4.    ^[aeiou]($|.*[aeiou]$): 문자열이 모음으로 시작, 모음 하나로 끝나거나 중간에 0개 이상의 문자, 마지막 모음으로 끝

 

5.    (^[AEIOUaeiou])|([AEIOUaeiou]$): 문자열이 모음(A, E, I, O, U, a, e, i, o, u)으로 시작하거나 끝

 

6.    ^[AEIOUaeiou]: 문자열이 모음으로 시작

 

'Data Analysis > Regex' 카테고리의 다른 글

정규표현식  (0) 2024.06.25

https://leetcode.com/problems/employees-whose-manager-left-the-company/

 

 

Find the IDs of the employees whose salary is strictly less than $30000 and whose manager left the company. When a manager leaves the company, their information is deleted from the Employees table, but the reports still have their manager_id set to the manager that left.

 

내용 요약:

매니저가 퇴사하면, Employess 목록에서 사라지지만, 다른 직원의 매니저로서 기존 설정된 기록은 남아 있다.

퇴사하면 기록이 사라진다는 말 때문에, "NULL" 로 표현된 부분을 오해하기가 너무 쉽다.

("NULL"은 퇴사와 관련이 없음) 

 

 

 

Table: Employees

+-------------+----------+
| Column Name | Type     |
+-------------+----------+
| employee_id | int      |
| name        | varchar  |
| manager_id  | int      |
| salary      | int      |
+-------------+----------+
In SQL, employee_id is the primary key for this table.
This table contains information about the employees, their salary, and the ID of their manager. Some employees do not have a manager (manager_id is null). 

 

Find the IDs of the employees whose salary is strictly less than $30000 and whose manager left the company. When a manager leaves the company, their information is deleted from the Employees table, but the reports still have their manager_id set to the manager that left.

Return the result table ordered by employee_id.

The result format is in the following example.

 

Example 1:

Input:  
Employees table:
+-------------+-----------+------------+--------+
| employee_id | name      | manager_id | salary |
+-------------+-----------+------------+--------+
| 3           | Mila      | 9          | 60301  |
| 12          | Antonella | null       | 31000  |
| 13          | Emery     | null       | 67084  |
| 1           | Kalel     | 11         | 21241  |
| 9           | Mikaela   | null       | 50937  |
| 11          | Joziah    | 6          | 28485  |
+-------------+-----------+------------+--------+
Output: 
+-------------+
| employee_id |
+-------------+
| 11          |
+-------------+

 

 

 

 

1. 수입이 3만보다 작은 직원에 매칭되는 manager_id 선택

 

2. 선택된 manager_id 중에서, "employee" 테이블의 employee_id에 포함되어 있지 않은 값을 SELECT 문으로 선택

(퇴사한 매니저는 employee_id에 존재하지 않음)

 

3. 이 과정에서 Employees 테이블을 다시 이용하므로 salary 값이 3만보다 큰 경우가 생길 수 있음.

다시 WHERE 조건에 추가 > (AND Employees.salary < 30000) 

 

4. 아래 작성한 쿼리 처림 카타시안 곱은 사용하면 안됨 > 쿼리성능 저하

 

 

예외케이스 

----------------------------------------------------------------

| employee_id | name    | manager_id | salary |
| ----------- | ------- | ---------- | ------ |
| 9           | Kamiyah | 2          | 65859  |
| 14          | Russell | 10         | 86740  |
| 11          | Roger   | 7          | 85366  |
| 15          | Alyson  | null       | 91743  |
| 17          | Elyse   | 12         | 10115  |
| 3           | Alivia  | null       | 53679  |
| 1           | Reign   | null       | 77731  |
| 7           | Brooks  | 11         | 89386  |
| 8           | Ryland  | 12         | 61431  |
| 13          | Charlee | null       | 41084  |

----------------------------------------------------------------  

 

 

 

# salary가 3만 보다 작은 employee의 manager_id 선택
WITH cond1 AS (
                SELECT manager_id
                FROM Employees 
                WHERE salary < 30000
), 

# cond1 에서 선택한 manager_id 가 Employees 테이블의 employee_id에 존재하지 않는, cond1의 manager_id 선택
cond2 AS(
            SELECT DISTINCT cond1.manager_id
            FROM Employees, cond1                       # 카타시안 곱 사용금지
            WHERE cond1.manager_id NOT IN (SELECT employee_id FROM Employees)
)

# 조건에 맞는 manager_id 만을 최종 선택 후, Employees 테이블에서 해당 manager_id를 가진 employee_id를 선택
# 이 과정에서 Employees 테이블을 다시 이용하므로 salary 값이 3만보다 큰 경우가 생길 수 있어 다시 WHERE 조건에 추가

SELECT Employees.employee_id
FROM Employees
     INNER JOIN cond2 ON Employees.manager_id = cond2.manager_id
WHERE Employees.manager_id = cond2.manager_id
AND Employees.salary < 30000
ORDER BY employee_id

 

 

'sql > easy' 카테고리의 다른 글

1527. Patients With a Condition  (0) 2024.06.25
1251. Average Selling Price  (0) 2024.06.03
1341. Movie Rating  (0) 2024.05.31
550. Game Play Analysis IV  (0) 2024.05.29
511. Game Play Analysis I  (0) 2024.03.30

https://medium.com/@jwjin0330
너무 감사하게도,,, 누군가 정규표현식을 이해하기 쉽게 정리해주셨다... 

 

활용이 필요할 때마다 찾아봐야겠다. 

 

https://medium.com/learn-from-data/%ED%95%B5%EC%8B%AC%EB%A7%8C-%EC%A0%95%EB%A6%AC%ED%95%9C-mysql-%EC%A0%95%EA%B7%9C%ED%91%9C%ED%98%84%EC%8B%9D-486c27c19568

 

핵심만 정리한 MYSQL 정규표현식

regexp, 이름부터 복잡한 너

medium.com

 

'Data Analysis > Regex' 카테고리의 다른 글

정규표현식 review  (0) 2024.08.21

https://leetcode.com/problems/patients-with-a-condition/description/

 

replace, substring, concat, left, right, lower, upper, trim, length, format, instr 정도만 알아도 SQL 데이터 변형에 큰 문제가 없겠지만, Regex 문법 까지 익숙해지면 빠른 전처리에 큰 도움이 될 것 같다.

 

 

 

Write a solution to find the patient_id, patient_name, and conditions of the patients who have Type I Diabetes. Type I Diabetes always starts with DIAB1 prefix.

Return the result table in any order.

The result format is in the following example.

 

Example 1:

Input: 
Patients table:
+------------+--------------+--------------+
| patient_id | patient_name | conditions   |
+------------+--------------+--------------+
| 1          | Daniel       | YFEV COUGH   |
| 2          | Alice        |              |
| 3          | Bob          | DIAB100 MYOP |
| 4          | George       | ACNE DIAB100 |
| 5          | Alain        | DIAB201      |
+------------+--------------+--------------+
Output: 
+------------+--------------+--------------+
| patient_id | patient_name | conditions   |
+------------+--------------+--------------+
| 3          | Bob          | DIAB100 MYOP |
| 4          | George       | ACNE DIAB100 | 
+------------+--------------+--------------+
Explanation: Bob and George both have a condition that starts with DIAB1.

 

 

 

 

#  conditions 열에 'DIAB1'이라는 문자열을 포함하는 모든 행을 선택

SELECT * 
FROM patients 
WHERE conditions REGEXP '\\bDIAB1'

'sql > easy' 카테고리의 다른 글

1978. Employees Whose Manager Left the Company  (0) 2024.08.21
1251. Average Selling Price  (0) 2024.06.03
1341. Movie Rating  (0) 2024.05.31
550. Game Play Analysis IV  (0) 2024.05.29
511. Game Play Analysis I  (0) 2024.03.30

https://leetcode.com/problems/average-selling-price/description/

 

15번째 케이스에서 Prices 테이블의 product_id가 3이상으로 추가되면서,

 

UnitSold 테이블에 없는 데이터 값도 결과값에 으로 나타나야 했기에 INNER 를 LEFT로 수정하고,

조건 결과값으로, Null값도 함께 나타날 수 있도록 WHERE문 끝에 "or UnitsSold.product_id IS NULL" 을 이어붙인 뒤 COALESCE를 ROUND 앞에 씌워 NULL값 처리를 해야했다.

 

 

 

잠깐 생각하게 만들었던 문제

다음부턴 예시에 보이지 않는 테스트 케이스를 예상해서 작성해봐야겠다.

 

*** JOIN에 AND 조건을 덧붙이는 방식보다, WHERE로 조건을 추가 작성하는 것이 확실히 쿼리속도가 더 빨랐다.

 

 

 

Write a solution to find the average selling price for each product. average_price should be rounded to 2 decimal places.

Return the result table in any order.

The result format is in the following example.

 

Example 1:

Input: 
Prices table:
+------------+------------+------------+--------+
| product_id | start_date | end_date   | price  |
+------------+------------+------------+--------+
| 1          | 2019-02-17 | 2019-02-28 | 5      |
| 1          | 2019-03-01 | 2019-03-22 | 20     |
| 2          | 2019-02-01 | 2019-02-20 | 15     |
| 2          | 2019-02-21 | 2019-03-31 | 30     |
+------------+------------+------------+--------+
UnitsSold table:
+------------+---------------+-------+
| product_id | purchase_date | units |
+------------+---------------+-------+
| 1          | 2019-02-25    | 100   |
| 1          | 2019-03-01    | 15    |
| 2          | 2019-02-10    | 200   |
| 2          | 2019-03-22    | 30    |
+------------+---------------+-------+
Output: 
+------------+---------------+
| product_id | average_price |
+------------+---------------+
| 1          | 6.96          |
| 2          | 16.96         |
+------------+---------------+
Explanation: 
Average selling price = Total Price of Product / Number of products sold.
Average selling price for product 1 = ((100 * 5) + (15 * 20)) / 115 = 6.96
Average selling price for product 2 = ((200 * 15) + (30 * 30)) / 230 = 16.96

 

 

 

 

SELECT Prices.product_id
    --   ,start_date
    --   ,end_date
    --   ,price
    --   ,SUM(units)
    --   ,SUM(price*units) AS mutiply_price_units
      ,COALESCE(ROUND(SUM(price*units)/SUM(units),2),0) AS average_price

FROM Prices
     LEFT JOIN UnitsSold ON Prices.product_id = UnitsSold.product_id
WHERE UnitsSold.purchase_date BETWEEN Prices.start_date AND Prices.end_date or UnitsSold.product_id IS NULL
GROUP BY product_id

 

 

 

'sql > easy' 카테고리의 다른 글

1978. Employees Whose Manager Left the Company  (0) 2024.08.21
1527. Patients With a Condition  (0) 2024.06.25
1341. Movie Rating  (0) 2024.05.31
550. Game Play Analysis IV  (0) 2024.05.29
511. Game Play Analysis I  (0) 2024.03.30

https://leetcode.com/problems/movie-rating/description/

 

 

 

포트폴리오 쓴다고, 그동안 쿼리연습을 못했다.

이전 기억 상기시키기에 좋은 쉬운문제

 

 

Example 1:

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     |
+--------------+
Explanation: 
Daniel and Monica have rated 3 movies ("Avengers", "Frozen 2" and "Joker") but Daniel is smaller lexicographically.
Frozen 2 and Joker have a rating average of 3.5 in February but Frozen 2 is smaller lexicographically.

 

 

-- 1. 가장 많은 수의 영화에 평점 매긴 유저 이름 반환, 동률일경우 알파벳순으르 빠른사람 상위 1명
-- 2. 2020년 2월, 평균평점이 가장 높은 영화이름 동률일경우 알파벳순으르 빠른영화 상위 1개
-- 3. 위/아래 테이블 병합



WITH CTE AS(
                SELECT MovieRating.rating
                      ,MovieRating.created_at
                      ,Users.name
                      ,Movies.title
                
                FROM MovieRating
                     INNER JOIN Movies ON MovieRating.movie_id=Movies.movie_id
                     INNER JOIN Users ON MovieRating.user_id=Users.user_id
            )

(SELECT name AS results
FROM CTE
GROUP BY name
ORDER BY count(name) DESC, name
LIMIT 1)


UNION ALL


(SELECT title AS results
FROM CTE
WHERE DATE_FORMAT(created_at, "%Y-%m") = "2020-02"
GROUP BY title
ORDER BY AVG(rating) DESC, title
LIMIT 1)

'sql > easy' 카테고리의 다른 글

1527. Patients With a Condition  (0) 2024.06.25
1251. Average Selling Price  (0) 2024.06.03
550. Game Play Analysis IV  (0) 2024.05.29
511. Game Play Analysis I  (0) 2024.03.30
1321. Restaurant Growth  (0) 2024.01.15

https://leetcode.com/problems/game-play-analysis-iv/description/

미뤄뒀던 포스팅을 이제야.... 한 20개 더 밀려있다.

 

기존의 511. Game Play Analysis I 와 유사한 문제

최초 접속 후, 다음날 다시 접속한 게임플레이어의 비율을 구하면 된다. 

 

Example 1:

Input: 
Activity table:
+-----------+-----------+------------+--------------+
| player_id | device_id | event_date | games_played |
+-----------+-----------+------------+--------------+
| 1         | 2         | 2016-03-01 | 5            |
| 1         | 2         | 2016-03-02 | 6            |
| 2         | 3         | 2017-06-25 | 1            |
| 3         | 1         | 2016-03-02 | 0            |
| 3         | 4         | 2018-07-03 | 5            |
+-----------+-----------+------------+--------------+
Output: 
+-----------+
| fraction  |
+-----------+
| 0.33      |
+-----------+
Explanation: 
Only the player with id 1 logged back in after the first day he had logged in so the answer is 1/3 = 0.33

 

 

 

SELECT ROUND(COUNT(DISTINCT player_id) / (SELECT COUNT(DISTINCT player_id) FROM Activity), 2) AS fraction
FROM Activity
WHERE (player_id, DATE_SUB(event_date, INTERVAL 1 DAY))
  IN (SELECT player_id
           , MIN(event_date) AS first_login 
      FROM Activity 
      GROUP BY player_id)

 

 

idea

 

1. WHERE문의 IN 속에, player_id 별 최초접속일을 모아둔다. 

2. event_date에서 하루를 뺀 값이 IN 값에 들어 있는 최초접속일과 일치하는 player_id를 찾도록 WHERE문 조건을 최종 작성한다(최초 접속 후 다음날 접속한 기록이 있다면, 해당 접속 일자에서 -1을 빼면 최초접속일이 될 것이다)

3. WHERE문을 통해 걸러진 플레이어 수/전체 플레이어 수

 

 

'sql > easy' 카테고리의 다른 글

1251. Average Selling Price  (0) 2024.06.03
1341. Movie Rating  (0) 2024.05.31
511. Game Play Analysis I  (0) 2024.03.30
1321. Restaurant Growth  (0) 2024.01.15
180. Consecutive Numbers  (0) 2024.01.15

(2024-02-23 ~ 2024-04-09)

 

https://bronzed-hawthorn-aa3.notion.site/Olist-Insight-d7429ac6cdb944889d99e0008ded4da0

 

Olist의 매출 분석을 통한 Insight 도출 및 이를 적용한 전략 제안 | Notion

활용 Tool : MySQL, MySQL Workbench, Pandas, Excel, Tableau, Google Colab

bronzed-hawthorn-aa3.notion.site

 

 

https://leetcode.com/problems/game-play-analysis-i/

쉬운 문제이지만, 윈도우 함수를 간단히 활용해보기에 좋은 문제

# 플레이어의 최초 로그인 날짜 구하기

1) 기본 풀이

SELECT player_id
     , MIN(event_date) AS first_login
FROM Activity
GROUP BY player_id




# 플레이어의 최초 로그인 날짜 구하기

2) 윈도우 함수 + WITH 문 활용

WITH login AS 
(
     SELECT ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date) AS player_login
          , player_id
          , event_date AS first_login
     FROM Activity
)

SELECT player_id
     , first_login
FROM login
WHERE player_login =1

 

 

 

'sql > easy' 카테고리의 다른 글

1341. Movie Rating  (0) 2024.05.31
550. Game Play Analysis IV  (0) 2024.05.29
1321. Restaurant Growth  (0) 2024.01.15
180. Consecutive Numbers  (0) 2024.01.15
Leetcode 185 - Department Top Three Salaries  (0) 2023.09.20

링크: https://leetcode.com/problems/human-traffic-of-stadium/

 

습득한 점: 

연속되는 숫자를 찾는 새로운 아이디어.

GROUP BY, PARTITION BY 차이 재확인.

hard 난이도는 아니었다.

 


문제:

사람 수 (people)가 100이상일 때, 3개 이상의 연속되는 "id"값에 대한 행을 모두 조회하는 쿼리 작성 

결과값은 "visit_date" 기준으로 오름차순 정렬

 

 

문제파악:

일단 visit_date는 정렬 빼고는 신경 쓸 필요는 없을 것 같았고,

우선 사람 수가 100명 이상인 테이블을 걸러낸 결과값을

WITH문으로 테이블을 새롭게 구성한 뒤 쿼리 작성을 시작하면 될 것 같다.

 

대략적으로 2가지 풀이법이 보인다. 셀프 조인 혹은 윈도우 함수 사용하기

 

풀이:

 

1. 'people'이 100 이상인 행들을 걸러냈을 때, id 값의 모양을 예상해본다.

데이터 사이사이에 100보다 낮은 값들이 포함되어 있었을 것이니,
아래 표와 같이 row_number()를 활용해서 강제적으로 맨 위부터 순위를 매긴 후
id 값에서 row_number값을 뺀 차이값 diff를 구하면,
연속되는 id값에서 벗어나는 순간에 diff값은 숫자가 바뀌게 된다.

 

ex) 아래 표를 예시로 든다면, id_1이 4에서 6으로 변할때, 차이값은 0에서 1로 변한다.

ex), id_2에서 5로 변할때, 차이값은 0에서 2로 변한다.

id_1 row_number diff_1 id_2 row_number diff_2
1 1 0 1 1 0
2 2 0 2 2 0
3 3 0 5 3 2
4 4 0 6 4 2
6 5 1 7 5 2
7 6 1 9 6 3

 

일종의 파티션을 나눈다고 생각하면 된다.

이런식의 계산을 이용하려면, 반드시 id 값을 기준으로 오름차순 정렬이 먼저 이루어져 있어야 한다.

WITH Sorted AS (
SELECT id
     , visit_date
     , people
     , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
FROM Stadium
WHERE people >=100
ORDER BY id     
)​

 

 

2.  diff를 기준으로 GROUP BY 이후,  행의 갯수 조회

 

기존 만들어진 WITH문을 그대로 이용해서 SELECT문에는 count(*)을 추가하고,  diff 기준으로 groupby,
HAVING count(*) >= 3을 조건으로 쿼리를 실행하면
diff의 그룹별 행의 갯수가 표현된다.

WITH Sorted AS (
SELECT id
     , visit_date
     , people
     , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
FROM Stadium
WHERE people >=100
ORDER BY id     
)

, Ranked AS (
    SELECT id
         , visit_date
         , people
         , diff
         , COUNT(*) AS consecutive_num
    FROM Sorted
    GROUP BY diff
    HAVING COUNT(*) >=3
)

3. 찾아낸 diff를 활용하여 정답 찾기

"consecutive_num" 은 3이상이면 나타나는 값이니 신경 쓸 필요는 없다.

2번의 테스트 케이스에 따르면, diff 가 2인 것이 연속된 id 4개를 가지고 있다는 것이니, 
연속되는 id값의 행들만 찾고 싶다면, diff를 기준으로 Sorted 테이블과 inner 조인 시켜주면 된다.

문제에 날짜기준 오름차순 정렬조건도 있지만, id가 커짐에따라 방문 일자도 하루씩 늘어나기 때문. 

 

WITH Sorted AS (
SELECT id
     , visit_date
     , people
     , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
FROM Stadium
WHERE people >=100
ORDER BY id     
)

, Ranked AS (
    SELECT id
         , visit_date
         , people
         , diff
         , COUNT(*) AS consecutive_num
    FROM Sorted
    GROUP BY diff
    HAVING COUNT(*) >=3
)

SELECT Sorted.id
     , Sorted.visit_date
     , Sorted.people
       
FROM Sorted
   INNER JOIN Ranked ON Sorted.diff = Ranked.diff

 

 

 

PARTITION BY 이용:

WITH Sorted AS (
    SELECT id
         , visit_date
         , people
         , id - ROW_NUMBER() OVER (ORDER BY id) AS diff
    FROM Stadium
    WHERE people >= 100
),
    consecutive_count AS (
        SELECT id
            , visit_date
            , people
            , diff
            , COUNT(*) OVER (PARTITION BY diff) AS consecutive_num
        FROM Sorted
    )

SELECT *
FROM consecutive_count
ORDER BY id

 

큰 차이는 없지만, PARTITION BY를 활용하면 더욱 직관적으로 쿼리를 작성할 수 있다.

무엇보다 GROUP BY는 조건에 따른 그룹의 첫번째 행만을 남기고, 이외 행은 다 날리게 되지만
(아래 output케이스는 Ranked 테이블에서 HAVING count(*) >=3 조건만 없다면 count(*) id가 2, 5인 행만 남을 것이다)

PARTITION BY는 조건에 해당하는 모든 id 행을 보여준다.

이후 consecutive_num >=3  인 값들만 찾으면 된다.

또한 Sorted테이블 이후에는, 정렬순서가 유지되지 않을 수도 있기에

+ ORDER BY id 추가해주면 된다.

 

 (*** GROUP BY를 활용하는 경우, 이미 id 기준 오름차순 정렬된 테이블에 inner 조인하는 것이 때문에 따로 추가할 필요는 없다)

 

https://leetcode.com/problems/restaurant-growth/description/

 

 

WITH문을 2번 중첩하여 문제를 풀이했었고,
내 풀이만 다른 분들과 달랐기에, 해결과정이 기억에 많이 남았던 문제 중 하나였다.

 
지금은 단순 풀이를 위한 쿼리작성이지만, 나중에는 쿼리 최적화 관점에서도 작성해보면 좋을 것 같다.

 

-> 쿼리 최적화 관련자료

 

 

✅ 쿼리 최적화 첫걸음 — 보다 빠른 쿼리를 위한 7가지 체크 리스트

DB에 대한 데이터 분석가의 에티켓

medium.com

 

 

 

1321. Restaurant Growth -> 이전 6일 ~ 당일 amount 합산

# 방문일별 계산
WITH grouped_visit_on AS 
(
        SELECT visited_on 
             , SUM(amount) AS amount
        FROM Customer
        GROUP BY visited_on
)
, visit_7 AS 
(    
# 방문일별 이전 6일 ~ 0일 까지 합산 및 AVG 계산, Named window
        SELECT visited_on
            , SUM(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND 0 FOLLOWING) AS amount 
            , ROUND(AVG(amount) OVER (ORDER BY visited_on ROWS BETWEEN 6 PRECEDING AND 0 FOLLOWING),2) AS average_amount
        FROM grouped_visit_on
)

# 최초 구매일로부터 6일 이후의 값들만 출력 == 1월 7일 부터
SELECT visited_on
     , amount
     , average_amount
FROM visit_7
WHERE visited_on >= (SELECT DATE_ADD(MIN(visited_on), INTERVAL 6 DAY) FROM customer)

# 1월 7일 -> WHERE 조건절 서브쿼리 삽입
#SELECT DATE_ADD(MIN(visited_on), INTERVAL 6 DAY

'sql > easy' 카테고리의 다른 글

550. Game Play Analysis IV  (0) 2024.05.29
511. Game Play Analysis I  (0) 2024.03.30
180. Consecutive Numbers  (0) 2024.01.15
Leetcode 185 - Department Top Three Salaries  (0) 2023.09.20
Leetcode 262 - Trips and Users  (0) 2023.09.01

https://leetcode.com/problems/consecutive-numbers/description/

기존에 테이블 조인 방식으로 풀었던 문제이지만, LEAD() 를 활용해서도 풀어볼 수 있었다.
큰 차이는 없지만, 윈도우함수를 활용하는 방법이 조금 더 직관적으로 이해하기 좋았음.

 

 

id NUM next afternext
1 1 1 1
2 1 1 2
3 1 2 1
4 2 1 2
5 1 2 2
6 2 2 null
7 2 null null

 

 

1) 윈도우 함수 풀이: LEAD

SELECT DISTINCT l.NUM AS ConsecutiveNums
FROM(
     SELECT NUM
          , LEAD(NUM,1) OVER (ORDER BY id) AS next
          , LEAD(NUM,2) OVER (ORDER BY id ) AS afternext
     FROM logs
 ) l
WHERE l.Num=next AND l.next=l.afternext




2) JOIN : 기존에 풀었던 방식

SELECT DISTINCT l.num AS ConsecutiveNums
FROM logs AS l
           INNER JOIN logs AS l_next ON l.id + 1 = l_next.id
           INNER JOIN logs AS l_next2 ON l.id + 2 =  l_next2.id

WHERE l.num = l_next.num AND l_next.num = l_next2.num

https://leetcode.com/problems/department-top-three-salaries/

 

습득한 점:

윈도우 함수를 활용할 때,
DENSE_RANK() OVER (PARTITION BY departmentid ORDER BY salary DESC)
 
처럼 PARTITION BYORDER BY를 함께 쓸 수 있다는 점을 알게되었음.
 
 
 
+ 순위 정하기 함수의 차이점 - ROW_NUMBER(),  RANK(),  DENSE_RANK()
 
SELECT val
     , ROW_NUMBER() OVER (ORDER BY val) AS row_number
     , RANK() OVER (ORDER BY val) AS rank
     , DENSE_RANK() OVER (ORDER BY val) AS dense_rank
FROM Sample;

 

- row_number는 어떻게 해서든 123456 으로 순위 매김 (중복포함)

 

- rank는 동일 값에 같은 등수를 부여, 공동 등수의 갯수를 고려한, 다음 순위를 매김.
 
아래 예시의 경우에는 2등과 5등은 스킵한 뒤 순위를 매김

 

- dense_rank는 공동 등수를 하나로 보고 다음 순위를 매김

VALUE ROW_NUMBER() RANK() DENSE_RANK()
1 1 1 1
1 2 1 1
2 3 3 2
3 4 4 3
3 5 4 3
4 6 6 4

 

 

 

문제:

A company's executives are interested in seeing who earns the most money in each of the company's departments. A high earner in a department is an employee who has a salary in the top three unique salaries for that department.

Write a solution to find the employees who are high earners in each of the departments.

Return the result table in any order.

 

요약: # 각 부서에서 직원들의 급여 순으로, 부서별 1위 2위 3위 찾기 (급여가 같은 사람이 2명이상이라면 모두 포함시키기)

 

 

 

문제파악:

1. Foreign key가 id 이므로 Employee와 Department의 id로 테이블 조인
 
2. 급여가 같은 직원들의 경우에도 모두 찾아내야 하므로 dense_rank 활용
 
3. 파트별로 순위 매기기 위해서 PARTITION BY ~ 활용
 
4. WHERE 조건 활용하기 위해선, 테이블 자체를 새로 생성해야 함.

 

 

 

풀이:

난이도가 hard 이지만 어렵진 않았다.

전체적인 틀은 같지만,

FROM 절에 넣어도 되고 WITH 절에 넣어도 괜찮을 것 같다.

 

1) WITH절 활용

WITH COUNTER AS (
      SELECT Dep.name DepName
          , Emp.name EmpName
          , salary
          , DENSE_RANK() OVER (PARTITION BY departmentid ORDER BY salary DESC) AS denserank
      FROM Employee AS Emp
          INNER JOIN Department AS Dep ON Dep.id = Emp.departmentId
                 )

SELECT DepName AS Department
     , EmpName AS Employee
     , salary
FROM COUNTER
WHERE denserank <=3

 

 

2) FROM절 서브쿼리 활용

SELECT t.department
     , t.employee
     , t.salary
     
FROM(
    SELECT department.name AS dapartment
        , employee.name AS employee
        , employee.salary
        , DENSE_RANK() OVER (PARTITION BY departmentid ORDER BY salary DESC) AS dr

    FROM Employee
        INNER JOIN dapartment ON employee.departmentId = dapartment.id
    ) t
WHERE t.dr <=3

마지막 9일차

 

기존 학습했던 자료들을 복습해볼 수 있었다.

그중에서 꼭 알아두면 좋을 것 위주로 기록해봐야겠다.

 

 

1)

 

여러 국가중 관심가는 국가들만 집합 생성 후,

색상마크에 삽입하면 해당 국가만 색상이 표시되고 나머지 국가는 단색으로 일괄표시된다.

 

 

 

2)

1번 항목의 우측 시각화에서, 행열 전환을 선택했을때 나타나는 시각화

 

 

 

3)

초록색 알약, 차원 값에 시각화를 선택했을때 나타나는 하이라이트를 줄 수도 있다.

 

 

 

4)

툴팁 변경을 통해, 마우스 오버시 나타나는 문구를 커스터마이징 할 수 있다.

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 8  (0) 2023.09.08
데이터시각화 7  (0) 2023.09.08
데이터 시각화 6  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08

1)

지역별 매출현황에서 지역을 선택하면, 나머지 그래프들이 변화하는 대시보드

 


 

시도 차원의 집합을 생성 후, 제품중분류별 매출 + 전체대비 매출현황 시트에 색상으로 삽입

집합은 아래그림과 같이 IN/OUT으로 표시되어 시각화된다.

 

 


 

대시보드 동작추가 - 대시보드 내, 지역별 매출현황의 지역을 선택했을 때, 나머지 시각화들이 집합값에 따라 변하게 만들기 위함.

 


2)

매개변수 생성 - 매개변수에 따라 각 제품 대분류의 드릴다운을 표현하기 위함.

아래 그림의 '허용 가능한 값' 에서 '전체' 를 값에 입력 후 최상단으로 끌어올림 (목록의 최상단에 위치하면 제품 대분류의 전체값을 표시)

 


 

제품대분류_드릴다운, 제품 중분류 레이블 계산식을 생성 후 아래그림과 같이 마크에 추가.

이때 제품 중분류 레이블은 퀵테이블 계산을 통해 구성비율을 표시할 수 있도록 체크.

 


 

매개변수 동작 추가 - 마지막으로 마우스 시각화에 있는 제품 대분류 값을 선택할 때마다 매개변수 값을 변동시키기 위함.

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 9 (END)  (0) 2023.09.08
데이터시각화 7  (0) 2023.09.08
데이터 시각화 6  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08

1)

 

위 사진 처럼, MIN(0)을 행에 입력하면 수직으로 임의의 축이 하나 생성된다.

축 기준점이 있어야 도넛 차트를 만들 수 있다.

아래처럼 차트를 형성하고 '이중축' 옵션을 선택 후, 차트 크기를 조정해주면 도넛차트를 만들 수 있다.

 

 

 

2)

그림과 같이 퀵 테이블 계산  - '누계' 를 선택하면 워터폴 차트를 만들 수 있다.

수익의 크기값을 -로 설정하면 좌측에서 우측그림으로 변한 것을 확인할 수 있다.

 

이후 좌측 상단 분석탭의 '총계' 그래프에 삽입하면, 총합계가 '열' 에 추가된 것을 확인 할 수 있다.

 

 

 

 

3)

 

3번항목의 첫번째 이미지와 같은 범프차트를 만들기 위해, 차트를 중분류 기준으로 계산되도록 설정 후, 퀵테이블 계산을 통해 '순위' 옵션을 선택한다.

 

이중축 형식을 활용하기 위해, 열의 합계(매출)을 그대로 복사한 뒤, 마크모양을 원으로 설정한다.

레이블 위치도 중앙으로 설정이 필요하다.

이후 이중축 옵션을 선택하여, 그래프를 통합한다.

 

통합된 그래프의 축이 반전이 되면, 좀 더 보기 좋은 시각화로 판단되기에 축 편집을 통해 '반전' 옵션을 선택 해주고, 축을 동기화시키면

3번항목의 첫번째 그림과 같은 그래프를 나타낼 수 있다.

 

 

 

4)

 

측정값을 복사 후, 축편집을 통해 반전시키면 아래 그림과 같은 퍼널차트 모양을 만들어낼 수 있다.

 

 

 

5)

 

 

영업 시간에 대한 계산식을 위와 같이 생성한다.

태블로의 경우 일단위로 기본설정되어있어서 분을 계산하는 경우엔 1440으로 나눠줘야 한다. (24*60)

 

특정 시도에 해당하는 시군구만 표현하기위해 '관련된 값만' 이라는 옵션을 선택한다.

 

 

레이블을 적절하게 편집해주면, 우측 그림과 같은 차트를 표현할 수 있다.

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 9 (END)  (0) 2023.09.08
데이터 시각화 8  (0) 2023.09.08
데이터 시각화 6  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08

1)

기준 일자의 종가 합계 값과, LOOKUP함수를 이용해서 현재 기준 일자의 -1일 자의 집계된 종가를 가져와서 계산

ZN함수는 null이 아니면 식을 반환, null값이면 0을 반환하는 함수

 

전일 대비 종가 등락을 표현하는 계산식을 형성 뒤, 이를 활용하여 등락을 색상으로 표현하는 계산식을 만들 수도 있다.

합계(종가)(2) 항목에 코드명을 세부항목에 두고,

등가 여부 색상을 색상 마크에 설정.

이후 두개의 그래프를 '이중축' 설정으로 통합하면 아래의 시각화를 표현할 수 있다.

 

 

 

2) Fixed, Include, Exclude

국가 > 시도 > 시군구의 계층으로 이루어진 데이터가 있다고 가정할 때,

 

Fixed: 현재 뷰는 '시군구' 인데,  '시도'를 고려하여 계산식을 작성하고 싶을 때

Include: 현재 뷰는 '시도' 인데, '국가' 수준에서 계산식을 작성하고 싶을 때

Exclude: 현재 뷰는 ' 국가' 인데, '시도'를 고려하여 계산식을 작성하고 싶을 때

 

Fixed와 Include는 경우에 따라 같은 결과를 나타낼 수 있을 것 같다.

 

 

 

 

 

3)

지도에 레이블을 삽입할 때, 백분율의 소숫점 단위로 표현가능함.

더불어, 레이블을 직접 편집하여 서식을 변경가능하다.

    - 예를 들어 시도기준, 전국기준의 텍스트 위치도 위/아래로 스위칭 할 수 있음.

 

 

 

4)

고객번호는 시각화에 삽입되어 활용되고 있는 상태에서

Fixed를 활용하면, 고객번호 차원 수준으로 시각화가 가능하다.

 

MIN 함수를 결합하여 활용하면 과거 구매 고객의 매출 비중의 이후 추이를 확인 할 수 있음.

 

 

 

5)

1. 년/분기별 고객별 최초 구매일을 행에 가져다 놓는다. 고객별 최초 구매일은 { FIXED [고객번호] : MIN([주문 일자]) } 로 구할 수 있다.

2. 재구매 시점까지의 경과 기간을 파악하기 위해, 고객별 최초 구매일과 최초 재구매일의 날짜차이를 계산식으로 구한다.

3. 가로 축으로는 경과 기간을 파악하기 위해,  '고객별 재구매 경과기간' 을 열에 놓는다.

4. MAC:Option / Window: Alt 키를 누른 채, 고객번호를 색상 마크에 두고 카운트(고유)(고객번호)를 선택한다.

5. 마지막으로 마크 모양을 사각형으로 설정하면 코호트 차트가 완성된다.

 

이 차트를 해석하면서, 데이터 리터러시 능력을 반드시 갖춰야 겠구나 라는 생각을 하게 되었다.

 

if의 경우 아래와 같이 여러 조건에 대한 값을 반환한다.

IF 조건 THEN 결과
ELIF 조건 THEN 결과
ELSE 결과
END

iif는 여러 케이스를 고려하지 않고 True 혹은 False를 구분할때 활용

IIF(조건, TRUE, FALSE)

ex) IIF(Population > 5,000,000, "high", "low")

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 8  (0) 2023.09.08
데이터시각화 7  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08
데이터 시각화 3  (0) 2023.09.08

1)

매개변수 생성시 '허용 가능한 값' 에 원하는 값들을 기재하고 이 값을 새로운 계산식에 활용하면

상단 그림과 같이, 기재했던 값들을 필터로서 활용가능하다.

값들이 서로 분리된 시각화를 표현할 수 있다.

 

 

 

2)

그림에는 이미 표시되어 있지만, 배경이미지 메뉴를 통해 작업중인 데이터 세트에 원하는 이미지를 불러올 수 있다.

그림의 픽셀에 맞게 불러와야 하고, 데이터들은 이미 x,y 값으로서 픽셀값이 주어져 있는 상태.

 

 

 

 

3)

MAKEPOINT 함수를 통해 위/경도에 점을 생성하고,
BUFFER 함수를 통해 생성된 포인트 별 반경을 시각화 할 수 있다.

 

 

 

 

 

 

4)

MAKEPOINT 함수를 통해 위/경도에 점을 생성하고,

아래 그림과 같이 MAKELINE 함수를 활용하면 각각의 점들을 이을 수 있다.

출발지와 도착지를 시각화하는데 매우 편리할 것 같다.

작업 데이터 세트가 출발지가 하나의 데이터로만 이루어져 있어서 점을 잇기만 해도 표현이 되었지만,

출발지가 다를 경우에는 추가 작업이 필요해 보인다.

 

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터시각화 7  (0) 2023.09.08
데이터 시각화 6  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08
데이터 시각화 3  (0) 2023.09.08
데이터 시각화 2  (0) 2023.09.08

1)

측정값을 복사하여 열에 추가하면, 위 그림 처럼 2개의 그래프를 표현할 수 있다.

이후 두번째 측정값 우측 클릭 후 '이중 축' 을 눌러주면 아래 그림과 같이 2개의 그래프가 하나로 통합된 시각화가 가능하다.

 

 

 

 

2)

시각화를 진행할 연도를 필터로 걸어 2개년도로 설정한 뒤,

행에 위치한 Dollar Price 값을 우측클릭 하면 나오는 기준 탭에서 '첫번째' 항목을 선택하면 아래와 같은 시각화를 만들어낼 수 있다.

2020년도 기준 국가별 가격 변동의 비율차이를 확인 할 수 있다.

 

 

 

 

3)

미세먼지 농도 수치 평균을 색상 마크에 두고, 마크 모양을 사각형으로 선택했을때, 파이썬의 히트맵과 흡사한 시각화를 나타낼 수 있다.

수치에 따라 색상을 차이나게 선택하면 지역별로, 어떤 달이 미세먼지 농도 수치가 높았는지를 파악하기 쉽다.

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 6  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 3  (0) 2023.09.08
데이터 시각화 2  (0) 2023.09.08
데이터 시각화 1  (0) 2023.07.09

1)

카페인 함유량을 매개변수로서 슬라이드를 통해 조절할 수 있도록 설정한 뒤,

이 매개변수를 활용하여 계산식을 만들고 이를 색상 마크에 넣으면 위와 같은 시각화를 만들어낼 수 있다.

* 계산식에도 매개변수 활용 가능

 

2)

시각화를 완성한 뒤, 좌측 상단 분석 탭으로 가면 축별로 평균값을 '라인'으로 나타낼 수 있다.

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 6  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08
데이터 시각화 2  (0) 2023.09.08
데이터 시각화 1  (0) 2023.07.09

1)

차원에서 메뉴명을 텍스트 마크에,

칼로리를 색상과, 크기 마크에 각각 놓으면 (측정값: 합계)

칼로리 합계에 따라 색상과 메뉴명 사이즈가 상이한, 워드 클라우드를 표현할 수 있다.

 

 

 

2)

카페인 합계에 따라 색상 표현, 마크의 모양은 원으로 설정한 뒤 메뉴명은 세부정보로 설정.

화면 좌측상단 분석탭의 박스플롯을 작업화면에 드래그하면 위와 같은 시각화를 표현할 수 있음.

 

 

 

3)

칼로리를 열에, 카테고리를 행에 두고

카페인 평균함량을 활용하는 계산식을 IF구문을 활용하여 생성한 뒤, 이를 색상 마크에 표현하면

평균 칼로리 함량 80mg을 기준으로 2가지 색상으로 분리되어 나타나는 시각화를 표현할 수 있음.

 

 

 

4)

매장운영시간을 열에, 매장명을 행에 두고

DATEDIFF 함수를 활용하여, 매장 운영시간을 계산식으로 구한 뒤 이를 색상마크에 넣으면

매장별 운영시간을 색상으로 분리하여 나타낼 수 있다.

'프리온보딩 > 태블로 데이터 시각화' 카테고리의 다른 글

데이터 시각화 6  (0) 2023.09.08
데이터 시각화 5  (0) 2023.09.08
데이터 시각화 4  (0) 2023.09.08
데이터 시각화 3  (0) 2023.09.08
데이터 시각화 1  (0) 2023.07.09

https://leetcode.com/problems/trips-and-users/description/

 

습득한 점:

SUM함수의 새로운 표현법을 알 수 있었다.

이제 까진 주로 숫자에 대한 계산을 위해 SUM(price*moment) 의 정도로 활용해왔지만,
 
이번 문제 풀이를 통해,
컬럼 내 존재하는 특정 문자열 데이터의 갯수를 아래 표현과 처럼 셀 수도 있었다.
 

SUM(status != 'completed')  >>> status 컬럼 중 "completed"가 아닌 데이터의 총 갯수

COUNT(status) >>> status 컬럼의 데이터 갯수 총합
 

 

 

문제:

The cancellation rate is computed by dividing the number of canceled (by client or driver) requests with unbanned users by the total number of requests with unbanned users on that day.

Write a solution to find the cancellation rate of requests with unbanned users (both client and driver must not be banned) each day between "2013-10-01" and "2013-10-03". Round Cancellation Rate to two decimal points.

Return the result table in any order.

 

 

 

문제파악:

10/1 ~ 10/3 까지 일자별 여행 취소율을 구하는 문제이다.

취소율은

dividing the number of canceled (by client or driver) requests with unbanned users by the total number of requests with unbanned users on that day.

즉 금지되지 않은 이용자와의 전체 취소요청 건수 / 당일 금지되지 않은 이용자와의 총 요청 건수 로 구한다.

쿼리 작성 중, 테스트 케이스가 통과 안되는 건이 하나 있었다.

테스트 케이스가 참 이상했다.

저 Input대로 라면, 전체 요청건수는 1개, 그중 취소 요청건수는 1개라 취소율은 100%여야 함에도 불구하고,

Expected에는 아예 값 자체가 보이지 않았다....

 

그래서 그냥 취소율이 1이 되는 경우는 아예 발생하지 않도록 쿼리를 일부 수정했다.

 

1. banned 컬럼 값이 No 인 값들의 user_id만 찾을 수 있도록 서브쿼리 작성

 

2. 만들어 둔 서브 쿼리 자체를 WHERE 조건으로 활용      

 - clinet_id, driver_id 의 컬럼 값들 중, 서브쿼리로 만들어지는 user_id와 일치하는 조건에 만족하는 SELECT 값을 찾을 수 있도록!

(Trips 테이블의 clinet_id, driver_id //// Users 테이블의 user_id는 foreign key 이다)

 

3. Between 활용하여, 문제에서 원하는 날짜 지정

 

4. 날짜 별 취소율을 구하는 것이기 때문에 Group by를 request_at기준으로 지정

 

5. 취소율을 구할 수 있도록,  완료건이 아닌 취소건의 갯수/ status의 총 갯수 를 구한 뒤, Round 활용해서 소수점 처리

 

6. 정렬조건은 필요없다고 나와있음

 

- With 구문을 활용하면 서브쿼리를 2번 적지 않을 수 있다.

- Alias 지정할 때, 중간에 빈칸이 있으면 ' ' 으로 묶어줘야 한다.

 

 

풀이:

 

SELECT request_at AS DAY
     , ROUND(SUM(status != 'completed') / COUNT(status),2) AS 'Cancellation Rate'
      
FROM Trips
WHERE client_id IN ( 
                  SELECT users_id
                  FROM Users
                  WHERE banned = 'NO'
                  )
AND driver_id IN ( 
                  SELECT users_id
                  FROM Users
                  WHERE banned = 'No'
                    )
AND request_at BETWEEN DATE("2013-10-01") AND DATE ("2013-10-03")
GROUP BY request_at

https://school.programmers.co.kr/learn/courses/30/lessons/157340

 

습득한 점:

레벨 3문제지만, 코드 길이에 관계없이 지금까지 풀었던 문제중에서 가장 까다로웠고,

깨닫게 된 점이 많았다.

 

문제를 마주하면 이해하기 좋은 코드 작성하고자,

반복적으로 필요하지 않고 해당 함수가 꼭 필요하지 않음에도, With 구문을 습관적으로 활용해서 문제를 풀곤 했었다.

 

덕분에 이해하기는 편했으나, 코드 줄 수가 너무 길어지는 단점이 있었다.

결과적으론... 이해하는 것이 코드를 작성한 나만 쉽지 않을까? 생각들었다.

DA가 sql 코드 성능을 신경 쓸 필요는 별로 없겠지만, 그래도 기존에 해왔던 파이썬이나 자바처럼 좀 더 컴팩트하게 작성하면 좋지 않을까?

 

앞으로는 가독성도 가독성이지만, 최대한 컴팩트하게 작성해보고자 한다.


- IF구문의 새로운 활용법:

* 기본적으로 IF(조건,True값,False값) 의 형태로 작성되지만,
  
IF(컬럼 IN (원하는 서브쿼리),True,False) 의 형태로도 작성될 수 있다.

 

- GROUP BY 특성:

* GROUP BY를 MAX()와 같은 함수와 사용할 때, run을 눌러서 나타나는 테이블은 컬럼들이 MAX()함수로 계산된 컬럼을 제외하고
   다른 컬럼들은 서로 관계성을 지니지 않음.

* 보통 원본 테이블에서 보여지는 그룹 별 첫번째 값들이 보여지게 되어 있음.

 

 

 

문제:

CAR_RENTAL_COMPANY_RENTAL_HISTORY 테이블에서

 

2022년 10월 16일에 대여 중인 자동차인 경우 '대여중' 이라고 표시하고,

대여 중이지 않은 자동차인 경우 '대여 가능'을 표시하는 컬럼(컬럼명: AVAILABILITY)을 추가하여

자동차 ID와 AVAILABILITY 리스트를 출력하는 쿼리 작성

 

이때 반납 날짜가 2022년 10월 16일인 경우에도 '대여중'으로 표시

결과는 자동차 ID를 기준으로 내림차순 정렬

 

 

 

문제파악:

2022-10-16일에 대여가 가능한지의 여부를 찾는 문제

CAR_ID를 고유값으로 뽑아내야 해서 많이 헤맷다.

위 예시에서, CAR_ID의 2,3번째 데이터는 대여시작일과 대여종료일 사이에 10-16가 걸쳐있어서

결과테이블에서는 '대여중'으로 표시되어야 하고 나머지 데이터는 표현되지 않아야 한다.

 

IF구문 내, 서브쿼리를 활용해서,

조건에 해당되는 값이, 서브쿼리로 인해 뽑힌 데이터 내에 단 한개라도 들어있다면, '대여중', 

존재하지 않는다면, '대여 가능'으로 표시되도록 작성 후 alias 를 지정한 뒤, 이를 SELECT절에 CAR_ID 컬럼과 함께 삽입했다.

 

이로써 조건에 맞는 CAR_ID와, 대여 여부를 판단한 AVAILABILITY를 테이블에서 확인할 수 있다.

 

 

 

 

풀이:

SELECT DISTINCT CAR_ID
              , IF(CAR_ID IN(SELECT CAR_ID
                             FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
                             WHERE '2022-10-16' BETWEEN START_DATE AND END_DATE),'대여중','대여 가능') AS AVAILABILTIY

FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
ORDER BY CAR_ID DESC

 

+ Recent posts