연습문제 메인 카테고리별로 할인 가격이 10만원 이상인 상품이 몇개 있는지를 출력해보기 (JOIN 활용 SQL 과 서브쿼리 활용 SQL 모두 작성해보기) 정답은 첫번째 코드처럼 WHERE, GROUP BY절이 쓰였는데 저는 처음 이 문제를 보고 두번째 코드처럼 GROUP BY, Having 절을 생각해서 넣었더니 오류값이 나오더라구요 HAVING절이 GROUP BY 이후에 자주 쓰이기도 하고 집계도 넣을 수 있어서 똑같은 조건문을 WHERE 대신 HAVING절에 넣어줬는데 혹시 오류가 난 이유가 있을까요? 오류메세지는 Unknown column 'items.dis_price' in 'having clause'라고 떴습니다. 제가 GROUP BY가 쓰일때는 HAVING이랑 WHERE가 같은 역할을 하는것 같다고 생각하는데 어떤 부분에서 차이가 있는지 설명해주시면 좀 더 분명하게 이해할 수 있을 것 같습니다. SELECT ranking.main_category, COUNT(*) FROM ranking JOIN items ON items.item_code = ranking.item_code WHERE items.dis_price >= 100000 GROUP BY ranking.main_category; SELECT ranking.main_category, COUNT(*) FROM ranking JOIN items ON items.item_code = ranking.item_code GROUP BY ranking.main_category Having items.dis_price >= 100000;
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. 2:40 결과를 보도록 하겠습니다 2:46 이제 SQL 서버의 기본 동작부터 보도록 하시죠 영상에서 2:40 -> 2:46 로 넘어가는 부분이 이해가 안 됩니다 ㅠㅠ 혹시 중간에 영상이 잘린 건가요??
안녕하세요 강사님! 실행 계획 type에 대해서 궁금한 점이 있습니다. ref가 UNIQUE가 아닌 컬럼의 인덱스를 사용한 경우라고 해주셨는데, 그러면 UNIQUE가 아닌 컬럼의 인덱스이지만 index나 range의 경우에 해당하면 해당 타입으로 표시되는 거고 저 둘(index, range)이 아닐 때, ref 타입으로 표시되는 걸까요? 그리고 한 가지 더 궁금한 점이 있는데, 만약 데이터를 조회한 결과들을 바로 View로 보내는 게 아니고 자바 코드를 사용한 계산을 통해 가공해야 하는 상황이라면 굳이 ORDER BY를 쓰지 않고, 데이터 조회 후, Collections.sort()와 같은 자바 코드로 정렬하는 편이 나을까요?
선생님 강의 듣기 전에 나름대로 짱구를 굴려서 풀어 봤는데 기본적으로 case when ~~~ 는 똑같지만 lag, lead 함수를 활용해서 해도 문제 풀 수 있었어서 공부하시는 분들과 같이 공유합니다. select id , case when mod(id, 2) = 1 and id in (select max(id) from seat) then student when mod(id, 2) = 0 then lag_stu when mod(id, 2) = 1 then lead_stu end as student from ( select a.id , a.student , lag(a.student) over(order by id) as lag_stu , lead(a.student) over(order by id) as lead_stu from seat a where 1=1 ) a where 1=1 order by 1
SELECT, FROM, ORDER BY, LIMIT 실습 파트 중입니다! mbtitest 테이블을 mbti_test로 다르게 설정하기는 했지만 다른 부분에 있어서는 동일하게 진행했습니다! 그런데 컬럼 추출하려니까 에러가 떠서요 그레이브를 잘 못 입력했나 싶기는 한데... 한 번 봐주시면 감사드리겠습니다! 🙏 T, F, ORDER BY, LIMIT 실습T,M, ORDER BY, LIMIT 실 습
SQL table을 생성하면 Field, Type, Null, Key, Default, Ext 순으로 있는데 Field, Type, Null, Key, Ext은 테이블 만들때 다 다뤘던거라 알겠는데 Dafult는 항상 NULL이라고 되어있는데 어떤건지 잘 모르겠더라구요. 그리고 NOT NULL이라고 데이터 형식을 적으면 NULL 에 Yes라고 뜨는데 Default에는 NULL이라고 되어있어서 가끔 헷갈리기도 하는 것 같아요. Default가 의미하는게 무엇이고, 왜 항상 NULL이라고 되어있는지 궁금합니다
카일님, 안녕하세요! 시간 데이터 3번 문제 궁금한게 있어 질문드립니다. 저는 해당 문제를 서브쿼리 없이 작성했는데, 서브쿼리 없이 작성해도 문제가 없나요? 그리고 서브쿼리는 어떤 경우에 사용해야 하는지 기준이 있을까요?? SELECT trainer_id, FORMAT_DATE("%d/%m/%Y", MIN(DATE(catch_datetime, 'Asia/Seoul'))) as first_date FROM basic.trainer_pokemon GROUP BY trainer_id ORDER BY trainer_id;
안녕하세요. 연습문제 2번을 다음과 같이 풀어보았습니다. Q: 리텐션 연습문제: Retain User를 New, Current, Resurrected, Dormant User로 나누는 쿼리를 작성하여라. 주어진 데이터에서 어떤 사람들이 리텐션이 그나마 높은지, 찾아보아라. 주차별, 전체 유저에 대한 코호트 분석 그래프를 보면, 다음과 같습니다. 문제 정의 2022년 12월 19일 이후로 리텐션이 떨어지고 있는 문제점이 발생했다. 해당 원인이 무엇일까? 가설 설정 1) 고객의 유입량이 감소하고 있다. -> 2022년 12월 19일 이전과 이후의 주차별 신규 가입자 수를 비교한다. 2) 리텐션의 변화가 특정 유저 그룹에 의해 영향을 받는가? -> 신규유저는 어떤 시점을 기준으로 분류할 것인가? 주차별 신규 가입자 수 시각화 2022년 12월 19일을 월요일로 하는 주차부터 신규 가입자 수가 감소하는 추세이다. 전반적으로 신규가입자 수는 증가하는 추세였으나, 12월 19일을 기점으로 꺾이는 현상을 보인다. 따라서, 신규 가입자 수가 리텐션에 영향을 준다는 것을 확인할 수 있다. 기존의 추세와 반하게, 떨어지는 추세를 보이기 때문에 다음과 같이 기존 유저와 신규 유저를 정의하겠다. 1) 기존 유저 => 2022년 10월 03일 ~ 2022년 12월 19일 이전 2) 신규 유저 => 2022년 12월 19일 이후 복귀유저와 이탈 유저를 판단하는 기준이 필요하다. 이를 위해, 첫 로그인 이후, 두번째로 로그인을 하기까지의 걸리는 시간을 4분위 수로 검증해서, 중위값을 기준으로 기존유저와 복귀유저를 구분해보기로 했다. 로그인 판단 기준 = click_login 이라는 이벤트가 발생했을 경우 1. 자동 로그인 여부 판단 SELECT COUNT(*) FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep WHERE ep.key = "firebase_screen" AND ep.value.string_value = 'welcome' AND user_id IS NOT NULL 자동 로그인이 된다면, click_login 이벤트가 발생하지 않을 것이다. 하지만, COUNT(*)가 0이기 때문에, 해당 앱은 자동 로그인이 되지 않음을 알 수 있다. 즉, 사용자가 앱에 접속하면, 가입을 했더라도, 무조건 로그인을 해야한다. 따라서, 사용자가 앱에 접속 시, 로그인 이벤트가 무조건 발생한다. 2.첫번째 로그인 후, 다음 로그인하기까지 걸린 시간을 4분위 수로 계산 WITH base AS (SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, user_pseudo_id, event_name, ROW_NUMBER() OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS login_rn FROM advanced.app_logs WHERE event_name = 'click_login' AND event_date >= '2022-10-03' QUALIFY login_rn < 3) , second_event_data AS (SELECT event_date, event_timestamp, user_pseudo_id, event_name, login_rn, IF(second_event_date IS NULL, '2023-01-20', second_event_date) AS second_event_date FROM (SELECT *, LEAD(event_date) OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS second_event_date FROM base)) SELECT APPROX_QUANTILES(date_diff_day, 100)[OFFSET(25)] AS percentile_25, APPROX_QUANTILES(date_diff_day, 100)[OFFSET(50)] AS percentile_50, APPROX_QUANTILES(date_diff_day, 100)[OFFSET(75)] AS percentile_75, MAX(date_diff_day) AS percentile_100, AVG(date_diff_day) AS average FROM (SELECT DATE_DIFF(second_event_date, event_date, DAY) AS date_diff_day FROM second_event_data) 유저별로 로그인을 한 시점을 ROW_NUMBER()로 카운트했다. 또한, second_event_date(다음 로그인 시점)이 NULL인 경우는 이탈 유저이다. 그런데, 4분위 수를 계산하려면, 해당 값을 채워야했다. 2099-12-31로 하려고 했으나, 너무 큰 값을 채워버리면, 4분위 수 검증이 정확하지 않게 되기 때문에 (mid와 avg값이 오른쪽으로 치우쳐버리는 현상이 발생할 수 있다.) second_event_date의 max값을 구해서, 2023-01-20을 채워주었다. Approx_quantiles 함수는 백분위로 나눌 경우, 각 근사치의 최대값을 나타내는 함수이다. 중위값에 해당하는 값들 중 가장 큰 값은 39일이다. 이번에는, 해당 범위에 속하는 값들이 몇개가 있는지 확인해보겠다. SELECT COUNTIF(date_diff_day <= 19) AS count_up_to_25, COUNTIF(date_diff_day > 19 AND date_diff_day <= 39) AS count_25_to_50, COUNTIF(date_diff_day > 39 AND date_diff_day <= 66) AS count_50_to_75, COUNTIF(date_diff_day > 66 AND date_diff_day <= 109) AS count_75_to_100 FROM temp 데이터가 1분위, 2분위에 몰려있다. 따라서, 40일 을 기준점으로 삼아야겠다. 2022년 12월 19일 이후 ~ (신규유저) 2022년 10월 3일 이후 ~ 2022년 12월 19일 이전, 10월 3일 이후부터 다음 접속 시간이 40일 이전인 경우 (기존유저) 2022년 10월 3일 이후로부터 40일 이후로 접속한 유저(복귀유저) 2022년 10월 3일 이후로부터 40일 이후로도 접속 이력이 없는 경우 (이탈 유저) 3. 다음 접속일까지의 걸린 일 수 기반, 유저 분류 -- 유저별, 첫번째 로그인 후, 다음 로그인하기 걸린 시간을 4분위 수로 계산 -- 25% | 50% | 75% | 100% -- 로그인_이벤트: click_login -- event_date, event_timestamp, event_name, -- 중간테이블 -- 유저별 로그인을 한 시점을 ROW_NUMBER()로 카운트하기 -- 유저별 첫번째 로그인을 한 시점 구하기 MIN(event_date) OVER() -- second_event_date가 NULL인 경우는 이탈 유저임. -- 그런데, 4분위 수를 계산하려면, MAX(second_event_date) 값을 구하고, NULL을 잠시 해당 값으로 채워놔야함. -- 그 이유는 너무 큰 값으로 채워버리면, 4분위 수 검증이 정확하지 않기 때문임 (mid값과 avg값이 오른쪽으로 치워쳐버리는 현상이 발생할 수 있음) max_event_date: 2023-01-20 WITH base AS (SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, user_pseudo_id, event_name, ROW_NUMBER() OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS login_rn FROM advanced.app_logs WHERE event_name = 'click_login' AND event_date > '2022-10-03' QUALIFY login_rn < 3) , second_event_data AS (SELECT DISTINCT event_date, event_timestamp, user_pseudo_id, event_name, login_rn, IF(second_event_date IS NULL, '2099-01-20', second_event_date) AS second_event_date FROM (SELECT *, LEAD(event_date) OVER (PARTITION BY user_pseudo_id ORDER BY event_date) AS second_event_date FROM base)) -- 접속 데이터 기반, 신규, 기존, 복귀, 이탈 구분 , user_type_data AS (SELECT event_date, second_event_date, user_pseudo_id, DATE_DIFF(second_event_date, event_date, DAY) AS comeback_day, CASE WHEN event_date >= DATE('2022-12-19') THEN '신규 유저' WHEN event_date >= DATE('2022-10-03') AND second_event_date <= DATE_ADD(event_date, INTERVAL 40 DAY) THEN '기존 유저' WHEN event_date >= DATE('2022-10-03') AND second_event_date > DATE_ADD(event_date, INTERVAL 40 DAY) AND second_event_date != DATE('2099-01-20') THEN '복귀 유저' WHEN event_date >= DATE('2022-10-03') AND second_event_date = DATE('2099-01-20') THEN '이탈 유저' ELSE NULL -- 예외 처리 END AS user_type FROM second_event_data) 4. 각 유저 타입별 리텐션 계산 기존 유저 -- 일자별 리텐션 계산 (기존 유저) , analysis_current AS (SELECT diff_of_day, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM (SELECT user_pseudo_id, MIN(event_date) OVER (PARTITION BY user_pseudo_id) AS first_day, event_date, comeback_day as diff_of_day FROM user_type_data WHERE user_type = '기존 유저' ORDER BY event_date, second_event_date) GROUP BY 1) SELECT diff_of_day, user_cnt, first_user_cnt, ROUND(SAFE_DIVIDE(user_cnt, first_user_cnt), 3) AS retention_day_rate FROM (SELECT diff_of_day, user_cnt, FIRST_VALUE(user_cnt) OVER (ORDER BY diff_of_day) AS first_user_cnt FROM analysis_current) 복귀 유저 신규 유저 이탈 유저 리텐션이 평평하게 안나오네요... 뭔가 단단히 잘못된것 같습니다..
안녕하세요! 형변환 관련 문의드리고자 합니다. ::text 등 더블콜론 활용한 기능이 매우 유용하다 말씀주셨는데 ::text의 경우 to_char로 작성했을 때 대비 어떤 이점 또는 차이가 있는지 궁금합니다. 감사합니다. to_char ( hiredate , 'yyyy-mm-dd' ) vs to_timestamp(hiredate, 'yyyy-mm-dd')::text
리텐션 SQL 쿼리 작성하기, Weekly와 Monthly 쿼리를 작성한 부분을 게시판에 올립니다. -- Weekly 리텐션 작성하기. 마지막 부분 , analysis AS (SELECT diff_of_week, user_cnt, FIRST_VALUE(user_cnt) OVER (ORDER BY diff_of_week) AS first_user_cnt FROM (SELECT diff_of_week, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM first_date_and_diff GROUP BY 1)) SELECT diff_of_week, user_cnt, ROUND(SAFE_DIVIDE(user_cnt, first_user_cnt), 3) AS retention_week_rate FROM analysis ORDER BY 1; -- 월별 리텐션 계산 WITH base AS ( SELECT DISTINCT user_pseudo_id, event_name, DATE(DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul')) AS event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_datetime, FROM advanced.app_logs WHERE event_date BETWEEN '2022-08-01' AND '2022-11-03' ), first_date_and_diff AS ( SELECT *, DATE_DIFf(event_month, first_month, MONTH) AS diff_of_month FROM ( SELECT DISTINCT user_pseudo_id, DATE_TRUNC(MIN(event_date) OVER(PARTITION BY user_pseudo_id), MONTH) AS first_month, DATE_TRUNC(event_date, MONTH) AS event_month FROM base ) ), analysis AS ( SELECT diff_of_month, user_cnt, FIRST_VALUE(user_cnt) OVER(ORDER BY diff_of_month) AS first_user_cnt FROM ( SELECT diff_of_month, COUNT(DISTINCT user_pseudo_id) AS user_cnt FROM first_date_and_diff GROUP BY 1 ) ) SELECT diff_of_month, user_cnt, ROUND(SAFE_DIVIDE(user_cnt, first_user_cnt), 3) AS retention_month_rate FROM analysis
인덱스에 대해서 약간의 의문을 질문드립니다. 인덱스가 조회의 성능 향상을 위한다는 점은 이해가 갑니다만..특정한 인덱스의 이름을 설정하는 부분이 약간 헷갈립니다. 가령 std_id 라는 인덱스를 학번을 저장 하는 column에 만들었을 때를 예시로 들겠습니다. 제가 학번의 데이터를 조회 하는경우 자체 프로그램에서 std_id의 인덱스만을 쉽게 찾는다는 의미이며, 이름설정은 단순 사용자들의 이해를 편하게 하기 위함인지가 궁금합니당
안녕하세요. 저는 이번에 마지막 문제를 응용해서, 퍼널별로 유저들의 새로운 세션 시작 횟수의 총합을 계산 해보았습니다. [결과 그래프] 새로운 세션 시작 기준: 이전 세션 시간 대비 20초 이상이 지나면, 새로운 세션을 활성화했다고 가정 (강의 내용과 동일) 첫번째 강의에서 배운, UNNEST 문법과 PIVOT 을 하는 방법을 활용해서, event_params에 있는 firebase_screen의 string_value 값을 새로운 열인 firebase_screen 이라고 만듭니다. 전체 데이터를 조회하면, 쿼리 비용이 많이 들기 때문에, where 조건 절로 값을 작게 명시해줍니다. WITH base AS ( SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, event_name, user_pseudo_id, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep WHERE event_date = '2022-08-18' AND user_pseudo_id = '5464646449.4088767327' GROUP BY ALL ) LAG() 함수를 사용하여, 이전 날짜 값을 가져옵니다. 이 때, 퍼널별로 구해야하기 때문에, PARTITION BY에 firebase_screen 을 써줍니다. 그 후, DATETIMEDIFF 함수를 사용해서, 현재값과 이전값의 차이를 계산합니다. WITH base AS ( SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, event_name, user_pseudo_id, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep WHERE event_date = '2022-08-18' AND user_pseudo_id = '5464646449.4088767327' GROUP BY ALL ), diff_funnel_time AS ( SELECT *, DATETIME_DIFF(event_timestamp, prev_event_timestamp, SECOND) AS second_diff FROM ( SELECT *, LAG(event_timestamp, 1) OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS prev_event_timestamp FROM base ) ) SELECT * FROM diff_funnel_time 3. ROW_NUMBER() 함수를 사용해서, 퍼널별로 순서를 매깁니다. 그 후, second_diff 의 값이 NULL인 경우는 해당 퍼널의 첫 시작 위치를 가리키므로 1을 넣어주고, rn > 1이고 second_diff >= 20인 경우는 1을 넣어줍니다. 이 때 funnel_per_session_start 컬럼은 퍼널별로 새로운 세션이 시작했는지 여부를 가리킵니다. , funnel_start AS ( SELECT *, CASE WHEN second_diff IS NULL THEN 1 WHEN rn > 1 AND second_diff >= 20 THEN 1 ELSE 0 END AS funnel_per_session_start FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS rn FROM diff_funnel_time ) ) 이제, 퍼널별로 세션 시작 여부를 SUM()하고, GROUP BY를 해줍니다. SELECT firebase_screen, SUM(funnel_per_session_start) AS funnel_per_session_start_cnt FROM funnel_start GROUP BY ALL ORDER BY 2 DESC; WHERE 조건절을 주석처리하고, 전체 데이터에 대해서, 집계해봅니다. WITH base AS ( SELECT event_date, DATETIME(TIMESTAMP_MICROS(event_timestamp), 'Asia/Seoul') AS event_timestamp, event_name, user_pseudo_id, MAX(IF(ep.key = 'firebase_screen', ep.value.string_value, NULL)) AS firebase_screen FROM advanced.app_logs CROSS JOIN UNNEST(event_params) AS ep -- WHERE -- event_date = '2022-08-18' -- AND user_pseudo_id = '5464646449.4088767327' GROUP BY ALL ), diff_funnel_time AS ( SELECT *, DATETIME_DIFF(event_timestamp, prev_event_timestamp, SECOND) AS second_diff FROM ( SELECT *, LAG(event_timestamp, 1) OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS prev_event_timestamp FROM base ) ) , funnel_start AS ( SELECT *, CASE WHEN second_diff IS NULL THEN 1 WHEN rn > 1 AND second_diff >= 20 THEN 1 ELSE 0 END AS funnel_per_session_start FROM ( SELECT *, ROW_NUMBER() OVER(PARTITION BY firebase_screen ORDER BY event_timestamp) AS rn FROM diff_funnel_time ) ) SELECT firebase_screen, SUM(funnel_per_session_start) AS funnel_per_session_start_cnt FROM funnel_start GROUP BY ALL ORDER BY 2 DESC;