BigQuery 에서 PIVOT 하는 방법

  • BigQuery 에서2021년 7월 19일, PIVOT 기능이 신규 추가되었다.
  • PIVOT 이란 테이블의 열과 행을 변경하는 것을 말한다.
  • 해당 PIVOT 기능에 대한 문법과 유의점에 대해 알아보자.

1. Syntax

--PIVOT 연산자
FROM from_item[, ...] pivot_operator

pivot_operator:
    PIVOT(
        aggregate_function_call [as_alias][, ...]
        FOR input_column
        IN ( pivot_column [as_alias][, ...] )
    ) [AS alias]

as_alias:
    [AS] alias

2. Example

다음과 같은 테이블이 있다고 해보자.

productsalesquarteryear
Kale51Q12020
Kale23Q22020
Kale45Q32020
Kale3Q42020
Kale70Q12021
Kale85Q22021
Apple77Q12020
Apple0Q22020
Apple1Q12021
table_A

위 table A 를 아래 table B 와 같이 변경하고 싶다면,

productyearQ1Q2Q3Q4
Apple2020770NULLNULL
Apple20211NULLNULLNULL
Kale20205123453
Kale20217085NULLNULL
table_B

다음과 같이 쿼리를 작성하면 된다.

SELECT * FROM
 table_A
 PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))

3. 유의사항

PIVOT 하고자 하는 테이블에서 내가 참조하지 않는 속성이 있는 경우,
명확하게 PIVOT 앞의 FROM 절에서 명시를 해주어야 한다.

그렇지 않을 경우, PIVOT 결과 값이 왜곡된다.
(행 중복)

3.1. Bad Case

단순히 쿼터 별로 합계를 보고 싶다고 할 때(연도와 상관 없이),

아래와 같이 쿼리를 작성하지 말자.

SELECT product, Q1, Q2, Q3, Q4 FROM
 table_A
 PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))

이에 대한 Result 는 다음과 같다.

productQ1Q2Q3Q4
Kale5123453
Kale7085NULLNULL
Apple770NULLNULL
Apple1NULLNULLNULL
3.1. 잘못된 결과

3.2. Good Case

참조하고자 하는 속성 중 year 가 없는 경우, 다음과 같이 명시해주어야 한다.

SELECT product, Q1, Q2, Q3, Q4 FROM
(SELECT product, quarter, sales FROM table_A)
PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))

이에 대한 Result 는 다음과 같다.

productQ1Q2Q3Q4
Kale121108453
Apple780NULLNULL
3.2. 잘된 결과

BigQuery 랜덤 샘플링

RAND() 함수를 활용한 BigQuery 랜덤 샘플링

랜덤 샘플링은 RAND() 함수를 사용하면 된다.

N개의 행(row)을 랜덤 샘플링하는 쿼리는 다음과 같다.

--샘플 크기가 100개 행이라고 가정했을 때,

SELECT *
FROM `project.dataset.table`
WHERE RAND() < 100/(SELECT COUNT(*) FROM  `project.dataset.table`)
;

BigQuery 에서 TO_CHAR 사용해서 date 로부터 YYYY-MM 추출하기

BigQuery 를 사용하다보면, Oracle 이나 타 SQL 에 비교해서 가장 짜증나는 부분이
BigQuery 에서는 TO_CHAR 함수가 없다는 것이다.

즉, 특정 date 속성에서 ‘YYYY-MM’ string 을 추출하고자 한다면

Oracle 에서는

--Oracle SQL
SELECT TO_CHAR(<date>, 'YYYY-MM') AS <colname>

위와 같이 간결한 쿼리문이 BigQuery 에서는

--BigQuery
SELECT CONCAT(CAST(FORMAT_DATE("%E4Y", CAST(<date> as date)) AS string),'-',CAST(FORMAT_DATE("%m", CAST(<date> as date)) AS string)) AS <colname>

위와 같이 매우 번거롭게 쿼리를 작성해야 한다.

BigQuery ARRAY 속성 EXCEPT 적용, REPLACE

1. * EXCEPT

BigQuery 는 편리하게도 SELECT 문에서 전체 속성을 지정하는 Wildcard(*) 를 사용 시,
특정 속성을 제외할 수 있는 문법을 제공한다.

가령, 속성 a, b, c, d, e, f 중 a, b, d, e, f 만 선택하고 싶을 경우 (=c 제외)

--아래와 같이 일일히 속성을 지정하는 것 대신,
SELECT a, b, d, e
FROM table


--다음과 같이 * EXCEPT 를 적용하면 효율적이다
SELECT * EXCEPT(c)
FROM table

2. ARRAY 속성 관련 제한사항

그러나 * EXCEPT 는 그 편리함에도 불구하고,
제외할 속성으로 ARRAY 내 nested column 을 특정할 수 없다는 단점이 있다.

(ARRAY, UNNEST 에 대해 잘 모른다면 다음 포스팅 참고)

3. REPLACE 사용하기

가령, ARRAY 속성 b 를 가진 테이블을 UNNEST 하여
속성 a, b.1, b.2, b.3, c 중 a, b.2, b.3, c 만 선택하고 싶을 경우 (=b.1 제외)

--직관적으로는 아래와 같이 쿼리를 하고 싶지만 실행 오류가 뜬다.
SELECT * EXCEPT(b.1)
FROM table, UNNEST(b) AS b


--이 때는 REPLACE 를 적용해 다음과 같이 쿼리해야 한다.
SELECT * REPLACE (ARRAY(SELECT AS STRUCT * EXCEPT(1) FROM UNNEST(b)) AS b)
FROM table

BigQuery 여러 날짜 별 테이블 한 번에 쿼리

1. 테이블 구성

BigQuery 는 GA 와 연계해서 사용하는 경우,
일반적으로 GA 데이터는 일 (daily) 단위의 별개 테이블로 BigQuery에 적재된다.

events_ & events_intraday_ tables in BigQuery for GA4 (Google Analytics 4)  - Optimize Smart

출처 : Optimize Smart

가령, 위 화면 좌측 Explorer에 events_ (9) 라는 테이블은
events_20210115
events_20210114
events_20210113

events_20210107
총 9개의 일 별 테이블이 있는 것이다.

events 라는 테이블 명에 ‘_YYYYMMDD’ 형태의 Suffix 가 적용되었고,
총 테이블 개수는 괄호 안에 ‘_(9)’ 와 같이 표현된다.

2. 여러 테이블 쿼리하기

위와 같은 테이블 구성에서, 그렇다면 여러 테이블들을 어떻게 한 번에 호출할까?

여러 테이블을 일일히 UNION 하는 방식도 있겠으나,
BigQuery 에서는 보다 효율적인 문법을 제시한다.

Wildcard 와 _TABLE_SUFFIX 를 사용을 통해서다.

events 테이블 중 2021-01-09 부터 2021-01-12 까지의 테이블을 조회해야 한다고 가정해보자.

--아래와 같은 방식 대신,
SELECT *
FROM 'dbrt-ga4.analytics_207472454.events_20210109'
UNION ALL 
SELECT *
FROM 'dbrt-ga4.analytics_207472454.events_20210110'
UNION ALL 
SELECT *
FROM 'dbrt-ga4.analytics_207472454.events_20210111'
UNION ALL 
SELECT *
FROM 'dbrt-ga4.analytics_207472454.events_20210112'



-- Wildcard 와 _TABLE_SUFFIX 를 사용하는 것이 더 효율적이다.
SELECT *
FROM 'dbrt-ga4.analytics_207472454.events_*'
WHERE PARSE_DATE('%Y%m%d', _TABLE_SUFFIX) BETWEEN DATE('2021-01-09') AND DATE('2021-01-12')

참고
https://cloud.google.com/bigquery/docs/querying-wildcard-tables

UI 용어 정리 (요소 별 명칭)

UI 요소 이름, 명칭 정리

웹 관련 업무를 하다보면 UI 요소 관련해서 명칭을 정확히 알아야 커뮤니케이션이 원활하다. 관련 UI 용어를 살펴보자.

  1. Input Controls
  2. Navigational Components
  3. Information Components
  4. Containers

Input Controls

ElementExamples
CheckboxesExample of checkboxes
Radio buttonsExample of radio buttons
Dropdown listsExample of a dropdown
List boxesExample of a list box
ButtonsExample of buttons
Dropdown ButtonExample of dropdown button
TogglesExample of toggles
Text fieldsExample of text field
Date and time pickersExamples of date and time pickers
정보 입력

Navigational Components

ElementExamples
CaratWhat are the little arrows called that hide additional details? - User  Experience Stack Exchange
화살표 모양의 클릭 가능한 아이콘 (펼칠 때 사용)
Search FieldExample of search boxes with various functions
BreadcrumbExample of breadcrumb
전체 체계에서 사용자의 현 위치를 알려줌
PaginationExamples of pagination
TagsExample of tags
SlidersExample of sliders
IconsExamples of icons
Image CarouselExample of an image carousel
네비게이션

Information Components

ElementExamples
NotificationsExample of a notification
Progress BarsExamples of progress bars
Tool TipsExamples of tool tips
Message BoxesExample of message boxes
Modal Window (pop-up)Example of a modal window
정보 전달

Containers

ElementExamples
AccordionExample of an accordion
항목 별 펼처보기, 숨기기 기능
컨테이너

Source : https://www.usability.gov/how-to-and-tools/methods/user-interface-elements.html

BigQuery UNNEST 친절한 설명

1. ARRAY 란?

Oracle 과 같이 기존에 전통적인 RDBMS 문법을 기반으로 쿼리를 배웠을 경우,
BigQuery 에서 이런 저런 데이터를 다루다 보면 간혹 낯선 풍경과 조우하는 경우가 있다.

그 중 하나가 BigQuery 의 데이터형(data type) 중 하나인 ARRAY 이다.

BigQuery에서 배열은 데이터 유형이 동일한 0개 이상의 값으로 구성된 순서가 지정된 목록을 의미합니다.

Google BigQuery Doc

별로 와닿지 않는 정의다…
풀어서 설명을 해보도록 하겠다.

일반적으로 기업에서 BigQuery 를 사용한다고 하면,
Google Analytics (이하 GA) 와 연동해서 사용하는 경우가 많으므로
GA 데이터를 샘플 삼아 설명하도록 하겠다.

GA 는 Web 에서 고객이 남기는 행동들을 Log 형태로 수집을 하고,
수집된 raw data는 JSON 형식에서 대략 다음과 같다.

[
  {
    "visitId": "123456789",
    "visitStartTime": "1622124798",
    "totals": {
      "visits": "1",
      "hits": "1",
      "pageviews": "1"
    }
  }
]

(참고) visitStartTime 값이 왜 1622124798 인지 궁금하신 분들은 관련 포스팅을 참고

JSON 문법을 잘 모르더라도,
위 내용을 아래와 같이 직관적으로 파악할 수 있을 것이다.

👨 visitId 가 123456789 이고
🕒 1622124798 UNIX time 에 들어와서
🔢 총 방문 수를 1회, hit 수는 1회, 페이지 조회 수는 1회 기록했구나.

자, 그러면 위 JSON 데이터를 쿼리가 가능하도록 table 형태로 보면 어떨까?

2. Why UNNEST?

위 데이터를 표로 만들 경우, 결과는 다음과 같을 것이다.

visitIdvisitStartTimetotals
1234567891622124798“visits”: “1”,
“hits”: “1”,
“pageviews”: “1”
table1. UNNEST 전

자, 한 번 보자.
딱 봐도 문제가 있지 않은가? (불편 +1)

table1. 불편한 부분

totals 컬럼의 값을 펼쳐서 추가적인 컬럼으로 만들고 싶은 욕구가 생긴다.
마치 R 에서의 reshape 패키지 cast 함수처럼.

visitIdvisitStartTimetotals.visitstotals.hitstotals.pageviews
1234567891622124798111
table2. UNNEST 후


table1 에서 table2 로 변경을 해주는 작업.
이것이 바로 UNNEST 함수다.

3. UNNEST Syntax

UNNEST 의 사용 문법은 다음과 같다.

--visitId 별 페이지 조회 수

SELECT visitId, t.pageviews
FROM table1, UNNEST(totals) AS t

테이블 내 값을 컬럼으로 펼치는 과정에서 CROSS JOIN 이 들어가기 때문에,
FROM table1 뒤에 쉼표(,) 가 붙는다.

UNNEST(totals) 에 대한 alias 가 필수는 아니지만,
복잡한 JOIN 이 들어간 쿼리에서 해당 속성을 특정할 수 있어 사용을 권고한다.

외 참고 링크

Google 에서 직접 GA 샘플 데이터셋을 제공하기도 하니,
실무적인 연습이 필요한 분은 아래 링크를 참고.
Google Analytics sample dataset for BigQuery

R 과 BigQuery 연계 (bigrquery, DBI)

본 포스팅에서는 R 과 BigQuery를 연계하는 방법을 다룬다.

구체적으로, R 에서 함수를 호출해 BigQuery에 쿼리를 날리고
해당 쿼리 결과 값을 로컬에 있는 R 로 가져와
dataframe object로 저장하기까지의 과정을 다룬다.

이를 위해 사용되는 R 패키지는 DBI, bigrquery, dbplyr 총 3가지다.
각 패키지의 역할은 간략하게 다음과 같다.

1. DBI
BigQuery를 비롯한 포괄적인 R/DBMS database 와의 인터페이스를 제공하는 패키지

2. bigrquery
BigQuery에 특정된 인터페이스를 취급하는 패키지

3. dbplyr
dplyr를 SQL 로 번역해주는 역할을 하는 패키지. 데이터 조작을 위해 필요하다.

아래 코드에서는 이해를 목적으로, 함수 별로 앞에 ‘패키지명’:: 을 명시함으로써 구분을 해 놓았다.
예외 적으로, dbplyr 의 경우는 명시하지 않았고 다만 ‘%>%’ 가 포함된 코드는 dbplyr 가 사용된 것이다.

#패키지 설치 및 로드

install.packages("bigrquery")
install.packages("DBI")
install.packages("dbplyr")

library(bigrquery)
library(DBI)
library(dbplyr)

##연결 셋팅

#요금 청구 대상이 되는 프로젝트 ID 입력
billing <- "project id"
#쿼리하고자 하는 프로젝트 ID 입력
projectName <- "project id"
#쿼리하고자 하는 데이터셋 입력
datasetName <- "dataset"

con <- DBI::dbConnect(
  bigrquery::bigquery(),
  project = "project id",
  dataset = "dataset",
  billing = billing
)

ds <- bigrquery::bq_dataset(projectName , datasetName)

#인증
bigrquery::bq_auth()

#사용하고자 하는 sql문 입력, 이때 syntax는 BigQuery
sql_tmp <- paste0("SELECT column
                  FROM `project id.`.dataset.table;")

#sql 호출
tb <- bigrquery::bq_dataset_query(ds,
                       query = sql_tmp,
                       billing = billing,
                       quiet = NA)

#테이블을 dataframe 형태로 다운로드
tb_df <- bigrquery::bq_table_download(tb) %>% as.data.frame()

Reference
https://cloud.google.com/architecture/data-science-with-r-on-gcp-eda

R 과 GA 연계하기 (googleAnalyticsR)

본 글은 Google Analytics 데이터 가져오기의 후속 포스팅이다.

앞서 언급된 대로 Google 측에서는
GA 데이터 연계를 목적으로 Core Reporting API 라는 인터페이스를 제공한다.

R 에서도 해당 API 를 통해 GA 데이터를 가져올 수 있는데,
그 패키지가 바로 googleAnalyticsR 패키지다.

가령, Google 측이 제공하는 Affinity 세그먼트 별로 제품 조회 수를 알고 싶은 경우,
관련된 샘플 R 코드는 다음과 같다.

코드 별 상세 설명은 주석을 참고 바란다.

# googleAnalyticsR 패키지를 설치 및 로드

install.packages("googleAnalyticsR")
library("googleAnalyticsR")

# R 과 GA를 연동. 별도 브라우저 탭이 뜨고 계정 인증을 하면 된다.

ga_auth()

# GA ID 입력을 위해 해당 목록 조회. Property가 여러 개로 나눠져 있는 경우 유의해서 입력해야 한다.

ga_accounts <- ga_account_list()
View(ga_accounts)

# GA ID 입력
ga_id <- 12345678

# View ID 입력. View ID 는 GA 화면에서 Admin > View > View Settings 에서 조회 가능하다.
view_id <- 98765432

#스키마: https://ga-dev-tools.appspot.com/dimensions-metrics-explorer/

#데이터 호출

Affinity_PV <- google_analytics (view_id,
                                      date_range = c("2020-01-01","2021-05-31"),
                                      metrics = c("ga:pageviews"),
                                      dimensions = c("ga:productDetailViews","ga:interestAffinityCategory"),
                                      anti_sample = TRUE,
                                      max = -1 )

UNIX time 이란? (= Epoch = POSIX ) & 쿼리 시 주의 사항

1. UNIX time 이란?

UNIX time 이란, 1970년 1월 1일 00:00:00 UTC 로부터 현재까지의 누적된 초(seconds) 값을 의미한다.

가령,
‘2021-05-23 09:00:00 KST’
위 시간을 UNIX time으로 표현한다면

① KST(local time)를 UTC 로 변환해서, ‘2021-05-23 00:00:00 UTC’
② UNIX time 의 기준 시간인 ‘1970-01-01 00:00:00 UTC’ 에서부터 누적 초를 계산

아래 숫자 값을 도출하게 된다.
‘1621728000’

2. 왜 UNIX time 인가?

UNIX time 은 UNIX 운영체제를 개발한 벨 연구소에서 정의한 개념이다.

Date/Timestamp 데이터형을 Numeric 데이터형으로 표현 시,
기존에 Date/Timestamp가 갖는 한계점을 해결할 수 있어 UNIX time 의 개념이 도입되었다.

Date/Timestamp 의 한계점
1. 로컬 시간대(ex. KST) 명시 필요
2. 비연속적, 비선형적인 값이므로 계산 시 변환 필요

하필 기준 시간이 1970년 1월 1일 00시 00분 00초 UTC 인 이유는,
UNIX 운영체제의 최초 출시년도가 1971년이어서 그렇다.
근방에 그럴싸한 시간을 임의로 잡은 것이다.

UNIX time 을 epoch time 또는 POSIX time 이라고도 부르는데,
이는 epoch 라는 단어가 특정 시대를 구분짓는 기준점(reference point from which time is measured = reference epoch) 이라는 의미를 가졌기 때문이고,
POSIX 는 UNIX 운영체제를 기반으로 둔 운영체제 인터페이스여서 같은 의미로 사용된다.

3. 쿼리 시 주의사항

UNIX time 을 취급할 때 쿼리 시 간과하기 쉬운 부분은, 다름 아닌 UTC 기준이라는 것이다.

하여 당신이 영국이나 스페인 같이 UTC +00:00 timezone 국가에 거주하지 않는 이상,
UNIX time 을 취급할 때는 항상 로컬 시간대로 변환하는 추가 작업을 해야 한다.
(이게 은근 까먹기 쉽다)

가령, Google Analytics 에서 수집한 고객 행동 데이터를 기반으로
접속 시간(hour)을 추출하는 쿼리를 작성한다고 하면,
다음과 같이 9시간에 해당되는 초(32,400)를 더해주어야 한다.

–BigQuery 기준
EXTRACT(HOUR FROM TIMESTAMP_SECONDS(CAST(visitStartTime+32400 AS INT64))) hh

(참고) BigQuery 의 Google Analytics 스키마에서는 시간을 UNIX time 으로 제공한다.

그외. 흥미로운 점

UNIX time 관련해서 2038년 문제라고 해서,
2038년이 되면 UNIX time 값이 32bit 시스템에서는
0 또는 음수로 표현되버리는 사태가 벌어질 것이라고 한다.
(2038년 이후에는 32bit 시스템의 허용된 숫자 표현 범위를 넘어버리게 되므로)

2038년이면 필자가 아직 살아있을 때일 것 같은데,
어떤 일이 일어날 지가 궁금하다.
해결책은 64bit 시스템으로 변경하는 것이라고 하는데 과연 시스템 교체가 수월할지 의문…