그렇기 때문에 특정 날짜의 기사들을 크롤링하고 싶다면 date 뒤에 연도+월+일을 붙이면 된다는 사실을 알 수 있습니다.
해당 부분을 코드로 변경하면 아래와 같습니다.
# 수정하고자 하는 메인 링크
link = 'https://news.naver.com/breakingnews/section/105/229?date='
# 스크랩 하고 싶은 날짜를 년도월일 나열해준다.
# 날짜를 쉽게 바꾸기 위해 date를 따로 선언해준다.
date = '20250107'
# 메인 링크는 링크에 날짜가 붙은 구조이기 때문에 이렇게 작성해준다.
main_link = link + date
# 기사의 수, 제목, 링크를 받아올 예정이기 때문에 정보를 담아줄 데이터 프레임을 생성한다.
Main_link = pd.DataFrame({'number' : [], 'title' : [], 'link' : []})
'a' 태그 : 링크가 담겨져 있는 공간이면 해당 태그를 사용합니다. 현재 버튼을 클릭하면 새로운 기사가 등장하기 때문에 해당 태그를 사용했습니다.
href: 링크의 주소를 가지고 있는 부분입니다. 여기서는 #을 사용하여, 페이지를 변동하지 않겠다는 옵션을 지정해주었습니다.
class: 태그의 속성을 나타내는 부분입니다. 크롤링을 진행할 때 많이 사용되는 부분으로 ID가 없을 경우에는 이름처럼 사용되기도 합니다.
data-* : HTML5에서 지원하는 사용자 정의 데이터 속성입니다. 여기서의 옵션은 특정 동작이나 상태를 저장하지 않도록 지시하는 것을 의미합니다.
기사 더보기버튼의 HTMl 구조를 알았으니, 오류가 발생할 때까지 버튼을 클릭하는 코드를 작성합니다.
service = Service('chromedriver.exe')
driver = webdriver.Chrome(service=service)
driver.get(main_link)
# 웹 페이지 로딩을 기다리는 코드로, 초는 더 짧아도 된다.
time.sleep(3)
# 기사 더보기 버튼
more_button = driver.find_element(By.CLASS_NAME, 'section_more_inner._CONTENT_LIST_LOAD_MORE_BUTTON')
# 기사 더보기가 몇 개가 있을지 모르기 때문에 오류가 날 때까지 누르는 것으로 한다.
# 여기서 발생하는 오류란 버튼을 찾을 수 없다 즉, 버튼이 없을 때 발생하는 오류이다.
while True :
try :
more_button.click()
time.sleep(3)
except :
break
위의 코드에서 보면, time.sleep 코드를 사용하는 것을 보실 수 있습니다.
동적 크롤링을 작성하실 때 생각보다 많이 놓치실 수 있는 부분인데요!
동적 크롤링은 코드로 크롬을 띄우고, 직접 웹 페이지에 들어가서 해당 페이지에 있는 내용을 크롤링하는 작업입니다.
그렇기 때문에 웹 페이지 로딩이 되지 않은 상태에서 접근을 시도하면 오류가 발생하는데, 이 때 발생하는 오류가 특정 HTML을 찾을 수 없다는 오류가 발생합니다.
이로 인해 초보자분들께서는 웹 페이지가 로딩이 되지 못했다고 생각을 못하고, 코드 내 오류가 있다고만 생각을 하여 한참 시간을 소모하는 경우를 많이 봤습니다!
이를 방지하기 위해서 time.sleep 명령어를 웹 페이지가 변경될 때마다 꼭 넣어주셔야 합니다.
몇 초를 기다리는지는 웹 페이지 안의 영상, 이미지 여부나 인터넷 속도에 따라서 다르지만 보통 1~5초 사이 라고 보시면 됩니다.
기사의 제목과 링크 가져오기
마지막으로 크롤링 목적인 기사의 제목과 링크를 가져오기만 하면 첫 번째 코드의 크롤링이 완료됩니다!
버튼을 확인했던 것처럼 개발자 도구에서 기사의 제목과 링크가 어떤 HTML 구조를 가지고 있는지 확인해보겠습니다.
# 기사의 제목과 링크가 모두 담긴 a태그를 모두 찾는다.
articles = driver.find_elements(By.CLASS_NAME, 'sa_text_title._NLOG_IMPRESSION')
# a태그 내 기사의 제목과 링크를 따로 저장한다.
for i in range(len(articles)) :
# 기사의 제목
# strip을 사용하여 눈으로 확인할 수 없는 양 끝의 공백을 제거한다.
title = articles[i].text.strip()
# href 부분을 가져온느 방법
# a태그 내 href를 가져온다.
link = articles[i].get_attribute('href')
# 번호는 0부터 시작하기 때문에 1을 더해준다.
li = [i+1, title, link]
Main_link.loc[i] = li
액셀 파일로 저장
이제 크롤링 작업은 완료되었고, 크롤링한 데이터프레임을 엑셀 파일로 저장하도록 하겠습니다.
# 엑셀을 잘 관리하기 위해서 크롤링 날짜를 파일 이름에 포함한다.
excel_name = 'news_' + date + '.xlsx'
with pd.ExcelWriter(excel_name) as writer :
Main_link.to_excel(writer, sheet_name='링크', index=False)
이런 과정을 통해서 특정 날짜의 모든 기사의 제목과 링크를 크롤링하는 코드가 완성되었습니다!!
[전체 코드]
import pandas as pd
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import time
from selenium.webdriver.common.by import By
from openpyxl import *
link = 'https://news.naver.com/breakingnews/section/105/229?date='
date = '20250107'
main_link = link + date
Main_link = pd.DataFrame({'number' : [], 'title' : [], 'link' : []})
service = Service('chromedriver.exe')
driver = webdriver.Chrome(service=service)
driver.get(main_link)
time.sleep(3)
more_button = driver.find_element(By.CLASS_NAME, 'section_more_inner._CONTENT_LIST_LOAD_MORE_BUTTON')
while True :
try :
more_button.click()
time.sleep(3)
except :
break
articles = driver.find_elements(By.CLASS_NAME, 'sa_text_title._NLOG_IMPRESSION')
for i in range(len(articles)) :
title = articles[i].text.strip()
link = articles[i].get_attribute('href')
li = [i+1, title, link]
Main_link.loc[i] = li
excel_name = 'news_' + date + '.xlsx'
with pd.ExcelWriter(excel_name) as writer :
Main_link.to_excel(writer, sheet_name='링크', index=False)
이전 코드와 마찬가지로 Xpath, CSS_Selector를 사용하지 않았는데, 해당 부분을 사용하면 쉽게 크롤링을 할 수 있지만 HTML 코드가 복잡하거나 크롤링을 배우고 싶은 분들에게는 좋은 방법이 아니라고 생각합니다.
C# 혹은 Python 스킬을 가진 개발자 : NAME IN (‘C#’, ‘Python’) → 이름이 아니고 각 스킬에 해당하는 CODE 값으로 진행해도 됩니다. (CODE IN (1024, 256))
총 1개인 것을 확인할 수 있습니다.
다음으로 두 개의 테이블을 JOIN 해야하는 부분은
DEVELOPERS 테이블의 SKILL_CODE 이진수 값 안에 SKILLCODES 테이블의 CODE 값이 들어있는 경우 : JOIN SKILLCODES b ON a.SKILL_CODES&b.CODE=b.CODE → 여기서 a는 DEVELOPERS 테이블의 별칭입니다. → 문제 설명처럼 개발자의 스킬 코드는 각 스킬 코드의 합으로 이루어져 있기 때문에, 어떤 합으로 이루어졌는지 알기 위해서는 비트 연산 &를 진행해야 합니다.
※ & 연산자는 이진수로 변경했을 때, 각 자릿수를 비교하여 동일하면 1, 다르면 0을 반환하는 연산자 입니다. → 문제 설명의 예시를 보시면 400(110010000)은 256(100000000)+128(10000000)+16(10000) 이고, 이를 각각 400과 &연산자를 진행하면 다음과 같은 결과물을 얻을 수 있습니다.
① 400&256 = 110010000&100000000 = 100000000 = 256 ② 400&128 = 110010000&10000000 = 10000000 = 128 ③ 400&16 = 110010000&10000 = 10000 = 16
즉, DEVELOPERS의 SKILL_CODE와 CODE의 & 연산의 값이 CODE로 나오는 값이 개발자가 가진 스킬이라고 볼 수 있습니다.
위와 같은 것을 확인할 수 있습니다.
마지막으로 저희가 문제에서 확인해야 하는 부분은
개발자 ID로 오름차순 : ORDER BY ID
총 1개인 것을 확인할 수 있습니다.
쿼리 완성하기
이제 위에서 생각한 쿼리를 SQL 실행순서에 맞게 배치해주시면 됩니다. 그렇게 완성된 쿼리는 아래와 같습니다.
-- C#과 Python 모두를 가진 개발자는 두 번 출력이 되기 때문에 DISTINCT를 해주어 중복값을 제거해줍니다.
SELECT DISTINCT
ID,
EMAIL,
FIRST_NAME,
LAST_NAME
FROM DEVELOPERS a
JOIN SKILLCODES b ON a.SKILL_CODE&b.CODE=b.CODE
WHERE NAME IN ('C#', 'Python')
ORDER BY 1
월 : MONTH(START_TIME) AS MONTH → 문제에서 날짜의 기준을 대여 시작일로 하고 있기 때문에 START_TIME을 해줘야 합니다. → MONTH() 는 DATE 타입에서 월을 가지고 오는 명령어 입니다. → MONTH는 문제 조건에 맞게 별칭으로 지정한 것 입니다.
자동차 ID : CAR_ID
월별 대여 횟수 : COUNT(*) AS RECORDS → 대여 횟수를 구하기 위해서 COUNT 집계 함수를 사용합니다. → RECORDS는 문제 조건에 맞게 별칭으로 지정한 것 입니다.
총 3개인 것을 확인할 수 있습니다.
다음으로 저희가 WHERE 조건으로 확인해야 하는 부분은
2022년 8월부터 2022년 10월까지 총 대여 횟수가 5회 이상인 자동차 → 조건이 복잡하기 때문에 서브 쿼리를 사용해야 합니다.
2022년 8월부터 2022년 10월 동안 : START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
총 2개인 것을 확인할 수 있습니다.
이번 문제에서는 WHERE 절 조건이 복잡하기 때문에 서브 쿼리를 사용해야 합니다.
서브 쿼리에서 확인해야 하는 부분은
자동차 : WHERE CAR_ID IN (SELECT CAR_ID → WHERE 절에 서브 쿼리를 쓰실 때 주의해야 할 점은 SELECT 절에 반드시 비교하고자 하는 하나의 컬럼을 사용해야 하는 것입니다. → 문제에서는 특정 기간 동안 대여 횟수 5회 이상인 자동차라고 했기 때문에 WHERE 절에 CAR_ID가 와야 하고 이에 맞게 서브 쿼리 안에 SELECT 안에도 CAR_ID만 올 수 있습니다.
2022년 8월부터 2022년 10월 : WHERE START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
대여 횟수 5회 이상 :GROUP BY CAR_ID HAVING COUNT(*) >= 5) → 대여 횟수를 구하기 위해서 COUNT 집계 함수를 사용합니다. → 집계 함수 사용한 결과를 조건으로 걸어야 하기 때문에 HAVING을 사용합니다. → 집계 함수의 결과물을 사용할 수 없을 때, HAVING에서 바로 집계 함수를 사용하는 것이 가능합니다.
위와 같은 것을 확인할 수 있습니다.
다음으로 저희가 GROUP BY 해야하는 부분은
월별 자동차 대여 횟수 : GROUP BY MONTH(START_DATE), CAR_ID → 월별 대여 횟수이기 때문에 반드시 MONTH(START_DATE)를 해주셔야 합니다.
특정 월의 총 대여 횟수가 0인 경우 제외 : HAVING RECORDS != 0 → 집계 함수의 결과를 조건으로 걸어야 하기 때문에 HAVING을 사용합니다.
위와 같은 것을 확인할 수 있습니다.
마지막으로 저희가 문제에서 확인해야 하는 부분은
월 기준 오름차순, 자동차 ID 기준 내림차순 : ORDER MONTH, CAR_ID DESC
총 1개인 것을 확인할 수 있습니다.
쿼리 완성하기
이제 위에서 생각한 쿼리를 SQL 실행 순서에 맞게 배치해주시면 됩니다.
그렇게 완성된 쿼리는 아래와 같습니다.
SELECT MONTH(START_DATE) AS MONTH,
CAR_ID,
COUNT(*) AS RECORDS
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE CAR_ID IN (
SELECT CAR_ID
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
WHERE START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
GROUP BY 1
HAVING COUNT(*) >= 5
)
AND START_DATE BETWEEN '2022-08-01' AND '2022-10-31'
GROUP BY 1,2
HAVING RECORDS != 0
ORDER BY 1, 2 DESC
REST_INFO 와 REST_REVIEW를 REST_ID를 기준으로 동시 만족 : JOIN REST_REVIEW b ON a.REST_ID=b.REST_ID → a는 REST_INFO 의 별칭입니다. → REST_INFO와 REST_REVIEW을 둘 다 만족해야 하기 때문에 내부 조인을 사용해야 합니다. → 내부 조인이기 때문에 REST_REVIEW에 REST_INFO를 JOIN하셔도 괜찮습니다.
위와 같은 것을 확인할 수 있습니다.
다음으로 저희가 WHERE 조건으로 확인해야 하는 부분은
서울에 위치한 식당 : ADDRESS LIKE '서울%' → 서울에 위치한 식당의 주소는 '서울시 ~~' 혹은 '서울특별시 ~~' 로 시작하기 때문에 주소에서 서울로 시작하는 문자열을 모두 가지고 와줍니다.
총 1개인 것을 확인할 수 있습니다.
마지막으로 저희가 문제에서 확인해야 하는 부분은
리뷰 평균 점수는 소수점 세 번째 자리애서 반올림 : ROUND(AVG(REVIEW_SCORE), 2) → ROUND(x, y)는 x를 소수점 y자리 까지 반올림해서 보여주는 함수입니다. → 참고로 올림은 CEILING, 내림은 FLOOR을 사용해주시면 됩니다.
평균 점수 기준 내림차순, 즐겨찾기 수 내림차순 : ORDER BYAVG(REVIEW_SCORE) DESC, FAVORITES DESC → ORDER BY는 SELECT에서 진행한 별칭을 사용해도 되기 때문에, AVG(REVIEW_SCORE) 대신 별칭을 사용하시면 좋습니다. → ORDER BY는 SELECT에 나온 것을 기준으로 순서를 정렬하기 때문에 SELECT에 있는 컬럼의 순서로 적으셔도 됩니다.
총 2개인 것을 확인할 수 있습니다.
쿼리 완성하기
이제 위에서 생각한 쿼리를 SQL 실행 순서에 맞게 배치해주시면 됩니다.
그렇게 완성된 쿼리는 아래와 같습니다.
SELECT a.REST_ID,
REST_NAME,
FOOD_TYPE,
FAVORITES,
ADDRESS,
ROUND(AVG(REVIEW_SCORE), 2) AS SCORE
FROM REST_INFO a
JOIN REST_REVIEW b ON a.REST_ID=b.REST_ID
WHERE ADDRESS LIKE '서울%'
GROUP BY 1, 2, 3, 4, 5
ORDER BY 6 DESC, 4 DESC
저자 ID : AUTHOR_ID → AUTHOR_ID가 소속되어 있는 BOOK / AUTHOR 두 개의 테이블 중에서 아무 테이블에서 가져오시면 됩니다.
저자 이름 : AUTHOR_NAME
장르 : CATEGORY
총 매출 : SUM(PRICE*SALES)TOTAL_SALES → 각 책에 대한 금액과 판매 권수만 나와있기 때문에 금액*판매 권수를 하면 각 책의 매출 금액을 알 수 있습니다. → 저자별, 카테고리 별 총 매출액을 알고 싶기 때문에 각 책의 매출 금액을 합쳐야 최종 매출액을 구할 수 있습니다. → TOTAL_SALES는 총 매출액의 컬럼을 별칭으로 지정한 것 입니다.
총 4개인 것을 확인할 수 있습니다.
728x90
다음으로 테이블을 JOIN 해야하는 부분은
BOOK 테이블과 AUTHOR 테이블을 AUTHOR_ID 기준으로 동시 만족 : JOIN AUTHOR b ON a. AUTHOR_ID=b. AUTHOR_ID → 여기서 a는 BOOK 테이블의 별칭이라고 생각하시면 됩니다.
BOOK 테이블과 BOOK_SALES 테이블을 BOOK_ID를 기준으로 동시 만족 : JOIN BOOK_SALSE c ON a.BOOK_ID=c.BOOK_ID
위와 같은 것을 확인할 수 있습니다.
다음으로 저희가 WHERE 조건으로 확인해야 하는 부분은
2022년 1월 기준 : SALES_DATE LIKE '2022-01%' → 날짜가 '연도-월-일'의 형태로 들어오고 있고, 연도와 월만 생각하면 되기 때문에 일에 어떤 값이 와도 상관없다는 뜻으로 %를 붙여주시면 됩니다. → 만약 1월을 기준으로 한다면 SALES_DATE LIKE '%01%' 조건이 됩니다.
총 1개 인 것을 확인할 수 있습니다.
다음으로 저희가 GROUP BY 해야하는 부부은
저자 별 카테고리 별 : AUTHOR_ID, AUTHOR_NAME, CATEGORY, → AUTHOR_NAME 는 AUTHOR_ID에 귀속되어져 있기 때문에 함께 GROUP BY 해주셔도 되고, 안해주셔도 됩니다만, 집계 함수를 제외한 나머지 컬럼은 최대한 모두 그룹화해주시는 것이 좋습니다. → AUTHOR_ID가 아닌 AUTHOR_NAME으로 그룹화를 하게되면, 동명이인 즉 이름은 동일하지만 다른 사람이 동일한 사람으로 집계가 되지 때문에 반드시 AUTHOR_ID가 기준이 되어야 합니다.
위와 같은 것을 확인할 수 있습니다.
마지막으로 저희가 문제에서 확인해야 하는 부분은
저자 ID 오름차순, 카테고리 내림차순 정렬 : ORDER BY AUTHOR_ID, CATEGORY DESC
총 1개인 것을 확인할 수 있습니다.
쿼리 완성하기
이제 위에서 생각한 쿼리를 SQL 실행순서에 맞게 배치해주시면 됩니다.
그렇게 완성된 쿼리는 아래와 같습니다.
SELECT b.AUTHOR_ID,
AUTHOR_NAME,
CATEGORY,
SUM(PRICE*SALES) AS TOTAL_SALES
FROM BOOK a
JOIN AUTHOR b ON b.AUTHOR_ID=a.AUTHOR_ID
JOIN BOOK_SALES c ON a.BOOK_ID=c.BOOK_ID
WHERE SALES_DATE LIKE '2022-01%'
-- 숫자에 직접 컬럼 이름을 넣어주셔도 됩니다.
-- 각 숫자는 컬럼의 순서를 의미합니다.
GROUP BY 1,2,3
ORDER BY 1, 3 DESC
대용량 결과물 저장 빅쿼리에서는 기본적으로 결과물을 csv 파일로 저장하거나, 스프레드 시트로 저장하는 기능을 제공하고 있습니다. 하지만 데이터 결과물의 용량이 너무 클 경우에는일부 데이터를 소실할 수 있는데, 데이터 분석의 특성 상 대용량의 데이터를 다루는 경우가 많기 때문에 생각보다 데이터가 소실되는 경우를 자주 경험할 수 있습니다. 이런 경우 빅쿼리와 파이썬을 연동하여 로컬 PC에 바로 csv 혹은 xlsx 파일로 결과물을 저장할 수 있습니다!
쿼리로 해결하기 힘든 데이터 처리 쿼리에서는 데이터의 직접적인 비교 등 진행하기 어려운 작업들이 있기 때문에 파이썬과 연동하여 데이터를 원하는 목적에 맞게 정제할 수 있습니다.
데이터 시각화 데이터 분석은 쿼리를 통해 데이터를 집계하는 것 뿐만 아니라, 보고서 등을 위해서 데이터를 시각화 하는 작업이 빈번하게 발생합니다. 요즘은 파이썬에서 데이터 시각화를 많이 진행하기 때문에 연동하여 진행하면, 시각화 작업을 수월하기 진행할 수 있습니다.
필요한 라이브러리 설치하기
빅쿼리와 파이썬을 연결하기 위해서는 먼저 pandas-gbq 라는 라이브러리를 설치해야 합니다.
일반 라이브러리 설치하시는 것처럼 설치해주시면 됩니다!
pip install pandas-gbq
코드 작성
파이썬 코드를 실행할 수 있는 실행기에서 아래 코드처럼 빅쿼리에서 실행이 가능한 쿼리와 함께 코드를 작성해주시면 손쉽게 빅쿼리 결과물을 로컬에 저장할 수 있습니다!
query="""
빅쿼리에서 실행할 쿼리
"""
purchase_log = pd.read_gbq(query=query, dialect='standard', project_id='빅쿼리 프로젝트 이름', auth_local_webserver=True)
purchase_log.to_csv(f"저장하고자 하는 파일 경로", sep=",", index=False, encoding='cp949')
※주의 ※
데어터의 양이 많을 경우 코드가 실행되는 시간이 길기 때문에 반드시 쿼리를 먼저 빅쿼리에서 확인 후에 파이썬에서 실행해주세요!
한글 데이터가 포함되는 경우 encoding='cp949'를 포함하지 않으면, 한글이 깨질 수 있습니다.
데이터의 양이 많다면 파일의 경로는 D드라이브로 하는 것을 추천합니다!
계정 선택
해당 코드를 처음 실행을 할 경우에는 크롬이 자동으로 뜨면서, 빅쿼리와 연결된 계정 선택 및 엑세스 요청 허가를 확인합니다.
알맞은 계정과 엑세스 허용을 하시면, 정상적으로 코드가 실행됩니다!
계정 선택의 과정은 컴퓨터마다 한 번만 진행이 됩니다.
실행이 완료되면, pandas를 이용해서 데이터를 정상적으로 로드할 수 있습니다.
1500만 정도 되는 데이터도 아주 잘 로드가 되는 것을 볼 수 있습니다!
빅쿼리는 파이썬과 연동하지 않으면 대용량 데이터를 저장하는 방법이 빅쿼리 자체에 테이블로 저장하는 방법 밖에 없기 때문에 파이썬에서 실행하는 방법을 익혀두시는 걸 추천드립니다!
이름: A.NAME → A는 ANIMAL_INS 테이블, B는 ANIMAL_OUTS 테이블을 의미합니다. → ID와 이름은 각 테이블에서 같이 포함되어 있기 때문에 어느 테이블에 존재하는지 꼭 명시를 해야합니다. → 여기서는 A, B 어느 테이블에 있는 값을 가지고 와도 동일한 값을 SELECT 합니다.
총 2개인 것을 확인할 수 있습니다.
다음으로 두 개의 테이블을 JOIN 해야하는 부분은
ANIMAL_INS 테이블과 ANIMAL_OUTS 테이블을 동시에 만족: JOIN ANIMAL_OUTS B → 두 테이블을 동시에 만족해야 하기 때문에 내부 조인을 사용하는데, JOIN 혹은 INNER JOIN 으로 표기하시면 됩니다. → ANIMAL_OUTS을 FROM에 두고, ANIMAL_INS 테이블로 JOIN을 해도 상관 없습니다.
두 테이블에서 값이 동일한 컬럼이 기준: ON A.ANIMAL_ID=B.ANIMAL_ID → 두 테이블에서 동일한 값이 여러 개 있으나, 문제에서 ANIMAL_OUTS의 ANIMAL_ID는 ANIMAL_INS의 ANIMAL_ID의 외래 키라고 하였기 때문에 ANIMAL_ID로 JOIN하는 것이 좋습니다. → NAME, ANIMAL_TYPE 과 같은 값으로 JOIN을 할 경우, 테이블 내에 중복 값으로 인해 원하는 대로 JOIN이 되지 않을 가능성이 높습니다.
위와 같은 것을 확인할 수 있습니다.
다음으로 저희가 WHERE 조건으로 확인해야 하는 부분은
보호 시작일보다 입양일이 더 빠른 동물: A.DATETIME > B.DATETIME
총 1개인 것을 확인할 수 있습니다.
마지막으로 저희가 문제에서 확인해야 하는 부분은
결과는 보호 시작일이 빠른 순으로 조회: ORDER BY A.DATETIME
총 1개인 것을 확인할 수 있습니다.
쿼리 완성하기
이제 위에서 생각한 쿼리를 SQL 실행순서에 맞게 배치해주시면 됩니다.
그렇게 완성된 쿼리는 아래와 같습니다.
SELECT
A.ANIMAL_ID, A.NAME
FROM
ANIMAL_INS A
JOIN
ANIMAL_OUTS B ON A.ANIMAL_ID = B.ANIMAL_ID
WHERE
A.DATETIME > B.DATETIME
ORDER BY
A.DATETIME