▣ 문자열 함수

  • 데이터를 여러 형태로 바꾸다 보면 문자열을 조작해야 하는 경우가 많으므로 빅쿼리는 내장 문자열 함수 라이브러리를 제공한다.
WITH
  example AS(
  SELECT
    *
  FROM
    UNNEST ( ['Seatle','New York', 'Singapore'] ) AS city )
SELECT
  city,
  LENGTH(city) AS len,
  LOWER(city) AS lower,
  STRPOS(city, 'or') AS orpos
FROM
  example

  • 이 쿼리는 문자열의 길이를 계산해 문자열을 소문자로 만든 후 'city' 컬럼에서 부분 문자열의 위치를 찾는다.
  • 부분 문자열 'or'은 'new york' 및 'Singapore'에서 찾을 수 있지만 'Seattle'에서는 찾을 수 없다.
  • 문자열 조작에 특히 유용한 두 가지 기능은 SUBSTR 및 CONCAT 이다. SUBSTR은 부분 문자열을 추출하고 CONCAT는 입력값을 연결한다. 
WITH example AS (
    SELECT 'armin@abc.com' AS email, 'Annapolis, MD' AS city
    UNION ALL SELECT 'boyan@bca.com', 'Boulder, CD'
    UNION ALL SELECT 'carrie@cab.com', 'Chicago, IL'
)
SELECT 
    CONCAT(
        SUBSTR(email, 1, STRPOS(email,'@') -1), --username
        ' from ', city) AS callers
FROM example


■ 국제화

  • 빅쿼리의 문자열은 유니코드이므로 영어와 관계된 가정은 피해야 한다. 예를 들어 일본어에는 '대문자'라는 것이 없으며, 문자열 타입을 바이트 배열 타입으로 변환할 때 기본적으로 적용되는 UTF-8 인코딩은 타밀어 같은 언어를 제대로 표현하지 못한다.
WITH
  example AS(
  SELECT
    *
  FROM
    UNNEST( [ 'Seattle', 'New York', '東京' ] ) AS city )
SELECT
  city,
  UPPER(city) AS allcaps,
  CAST(city AS BYTES ) AS bytes
FROM
  example

 

내 컴퓨터에 타밀어는 없으므로 패스.

  • 빅쿼리는 유니코드 문자 배열, 바이트 배열 및 유니코드 코드 포인트 배열(int 64)로 문자열을 나타내는 3가지 방법을 지원한다.
  • 동일한 문자열에서 CHAR_LENGTH 및 BYTE_LENGTH 결과와 코드 포인트 수가 문자 수와 어떻게 다른지 확인하자.
WITH
  example AS(
  SELECT *
  FROM
    UNNEST( [ 'Seattle','New York', '東京' ] ) AS city )
SELECT
  city,
  CHAR_LENGTH(city) AS char_len,
  to_code_points(city)[ORDINAL(1)] AS first_codept , 
  ARRAY_LENGTH(to_code_points(city)) AS num_codept,
  CAST (city AS bytes) AS bytes,
  BYTE_LENGTH(city) AS byte_len
FROM
  example

 

  • 따라서 어떤 열에 다른 언어로 된 텍스트가 포함되어 있는지 파악한 후 언어의 차이를 고려해서 문자열 조작 함수를 사용해야 한다.

■ 출력 및 파싱

  • 문자열을 파싱할 때는 간단히 INT64나 FLOAT64 타입으로 변환하면 되지만, 어떤 값을 원하는 형태의 문자열로 표현하라면 FORMAT 함수를 사용해야 한다.
SELECT
  CAST(42 AS STRING),
  CAST ('42' AS INT64),
  FORMAT ('%03d',42),
  FORMAT('%6.3f', 32.457842),
  FORMAT('%5.3f', 32.4),
  FORMAT('**%s**','H'),
  FORMAT('%s-%03d','Agent',7)

  • FORMAT은 C의 printf 함수와 유사하게 작동하며 같은 형식 지정자를 사용한다. 좀 더 유용한 지정자 중 몇몇은 앞의 예에서 설명했다.
  • FORMAT 함수는 날짜나 타임스탬프 값도 지원하지만, 사용자의 로케일을 감안해서 날짜를 형식화할 수 있는 FORMAT_DATE와 FORMAT_TIMESTAMP 함수를 사용하는 것이 더 좋다.

■ 문자열 조작 함수

  • 문자열 조작은 ETL 파이프라인에서 일반적으로 필요하므로, 다음 편의 함수를 잘 기억해 두면 유용한다.
SELECT
  ENDS_WITH('hello', 'o') --TRUE
 , ENDS_WITH('hello','h') --FALSE
 , STARTS_WITH('hello','h') --FALSE
 , STRPOS('hello','e') --2
 , STRPOS('hello','f') --0 FOR NOT-found
 , SUBSTR('hello',2,4) --1-based
 , CONCAT('hello','World')

  • SUBSTR( )의 작동 방식을 주목.
  • 첫 번째 파라미터는 시작 위치이며, 두 번째 파라미터는 하위 문자열에서 원하는 문자 수다.

■ 변환 함수

SELECT 
LPAD('Hello', 10, '*') AS LPAD --왼쪽에 *가 추가된다.
, RPAD('Hello', 10, '*') AS RPAD--오른쪽에 *가 추가된다.
, LPAD('Hello',10) AS LPAD  -- 왼쪽에 공백이 추가된다.
, LTRIM('Hello') AS LTRIM-- 왼쪽의 공백이 제거된다.
, RTRIM('Hello') AS RTRIM-- 오른쪽의 공백이 제거된다.
, TRIM('Hello') AS TRIM-- 양쪽의 공백이 제거된다.
, TRIM('***Hello***', '*') AS TRIM_2-- 양쪽의 *이 제거된다.
, REVERSE('Hello') AS REVERSE-- 문자열이 뒤바뀐다.


■ 정규 표현식

  • 정규 표현식은 편의 함수들보다 강력하다. 예를 들어 STRPOS 함수 등은 특정 문자만 찾을 수 있지만, REGEXP_CONTAINS를 사용하면 강력한 검색을 수행할 수 있다.
  • 예를 들어 컬럼에 미국 우편번호가 포함되어 있는지를 판별하려면 다음 쿼리를 사용하면 된다.(짧은 형식은 5자리 숫자이며, 긴 형식은 추가 4자리 숫자를 하이픈 또는 공백으로 구분한다.)
SELECT
  COLUMN,
  REGEXP_CONTAINS(COLUMN,r'\d{5}(?:[-\s]\d{4})?') has_zipcode,
  REGEXP_CONTAINS(COLUMN,r'^\d{5}(?:[-\s]\d{4})?$') is_zipcode,
  REGEXP_EXTRACT(COLUMN,r'\d{5}(?:[-\s]\d{4})?') the_zipcode,
  REGEXP_EXTRACT_ALL(COLUMN,r'\d{5}(?:[-\s]\d{4})?') all_zipcode,
  REGEXP_REPLACE(COLUMN,r'\d{5}(?:[-\s]\d{4})?', '*****') masked
FROM (
  SELECT
    *
  FROM
    UNNEST([ '12345', '1234','12345-9876', 'abc 12345 def', 'abcde-fghi', '12345 ab 34567', '12345 9876' ]) AS COLUMN )

  • 정규 표현식 \d{5}는 5개의 10진수로 구성된 문자열과 일치한다.
  • 괄호로 묶인 표현식의 두 번째 부분은 하이픈 또는 공백(\s)으로 분리되는 첫 번째 5개 10진수와 분리된 4개의 10진수(\d{4})의 선택적(괄호 끝에 ? 표시함) 그룹(?:)을 찾는다.
  • 문자열에 \d, \s 등이 있으면 문제가 발생할 수 있으므로 문자열 앞에 r(원시)을 붙여 문자열 리터럴로 만들었다.
  • 두 번째 표현식은 정확히 일치하는 경우를 보여준다. 지정된 문자열로 시작(^)하고 끝나야 ($) 한다고 보면 된다.
  • 정규식과 일치하는 문자열 부분을 추출하려면 REGEXP_EXTRACT를 사용한다. 식이 일치하지 않으면 NULL을 반환하고, 일치하는 항목이 여러 개 있으면 첫 번째로 일치하는 값만 반환한다.
  • REGEXP_EXTRACT_ALL은 모두 일치하는 항목을 반환한다. 일치하는 것이 없으면 빈 배열을 반환한다.
  • REGEXP_REPLACE는 모든 일치 항목을 대체 문자열로 바꾼다.
  • 빅쿼리의 정규 표현식은 구글의 오픈 소스인 RE2 라이브러리의 규칙을 따른다.

■ 문자열 함수 정리

  • 데이터 분석에서 문자열 데이터는 매우 흔하므로 사용 가능한 함수를 폭넓게 학습하는 것이 좋다. 정확한 문법에 대해서는 빅쿼리 공식문서를 참조한다.
유형 함수 설명
문자열 표현 CHAR_LENGTH, BYTE_LENGTH, TO_CODE_POINTS, CODE_POINTS_TO_STRING, SAFE_CONVERT_BYTES_TO_STRING, TO_HEX, TO_BASE32, TO_BASE64, FROM_HEX, FROM_BASE32, FROM_BASE64, NORMALIZE 다른 유니코드 공백 문자를 같은 것으로 취급하게 하는 등의 정규화를 실행한다.
출력 및 파싱 FORMAT, REPEAT, SPLIT FROMAT 구문은 C의 printf와 비슷하다. format("%03d", 12)는 012를 반환한다.로케일 기준 변환에는 FORMAT_DATE 등을 사용한다.
편의성 ENDS_WITH, LENGTH, STARTS_WITH, STRPOS, SUBSTR, CONCAT LENGTH 함수는 문자열이면 CHAR_LENGTH, 바이트면 BYTE_LENGTH와 같다.
문자열 변형 LPAD, LOWER, LTRIM, REPLACE, REVERSE, RPAD, RTRIM, TRIM, UPPER 기본 문자열 자르기(trim) 구분자는 유니 코드 공백이지만 다른 trim 문자를 지정할 수 있다.
정규 표현식 REGEXP_CONTAINS, REGEXP_EXPTRACT. REGEXP_EXTRACT_ALL, REGEXP_REPLACE 빅쿼리에서 허용하는 구문은 깃허브 구글문서를 참조한다.

 

▣ 숫자형과 함수

  • 빅쿼리에는 하나의 정수형 타입(INT64)과 하나의 부동소수점 타입(FLOAT64)이 있다.
  • 이 두 가지 타입 모두 일반적인 사칙 연산을 지원한다. 따라서 한 컬럼을 다른 컬럼으로 나눠서 편도 자전거 대여 횟수를 구할 수 있다.
WITH
  example AS (
  SELECT 'Sat' AS day, 1451 AS numrides, 1018 AS oneways
  UNION ALL SELECT 'Sun', 2376, 936 
)
SELECT
  *, (oneways/numrides) AS frac_oneway
FROM
  example

  • 정수형 타입은 산술 연산자 외에 비트 연산자도 지원한다.
  • 데이터 타입에 대한 연산은 함수를 이용하면 된다. 함수는 입력된 값에 대한 작업을 수행한다. 다른 프로그래밍 언어와 마찬가지로 SQL의 함수는 재사용 가능한 논리를 캡슐화하고 구현의 복잡성을 추상화한다.
함수의 유형 설명 예시
스칼라 함수 하나 이상의 입력 파라미터에서 작동해 단일 값을 반환하는 함수.
반환 데이터 타입이 허용되는 모든 곳에서 사용할 수 있다.
ROUND(3.14)는 FLOAT63인 3을 반환하므로 FLOAT64가 허용되는 모든 위치에서 ROUND 함수를 사용할 수 있다.
SUBSTR("hello", 1, 2)는 'he'를 반환하며 3개의 입력 파라미터를 사용하는 스칼라 함수다.
집계 함수 값 모음에서 계산을 수행하고 단일 값을 반환하는 함수
종종 행 그룹을 계산하기 위해 GROUP BY 절과 함께 사용된다.
MAX (Tripduration)는 tripduration열의 최대 값을 계산한다.
다른 집계 함수로는 SUM( ), COUNT( ), AVG( ) 등이 있다.
분석 함수 값 모음에서 작동하지만 모음의 각 값에 대한 결과를 반환하는 함수.
윈도우 프레임을 이용하면 분석 함수가 적용될 행의 집합을 지정할 수 있다.
row_number( ), rank( ) 등이 있다.
테이블 반환 함수 FROM 절에서 사용할 수 있는 결과 집합을 반환하는 함수 배열에서 UNNEST를 호출한 다음 선택할 수 있다
사용자 정의 함수 내장 함수가 아니라 사용자가 직접 구현한 함수.
SQL로 작성할 수 있으며 앞에서 언급한 타입을 반환할 수 있다
CREATE TEMP FUNCTION
lastElement(arr ANY TYPE) AS (arr[ORDINAL(ARRAY_LENGTH(arr)) ]);

■ 수학 함수

  • 편도 자전거 대여 비율을 계산했던 쿼리에서 최종 결과를 반올림하려면, 정수 및 부동소수점 타입을 지원하는 여러 내장 수학 함수 중 하나인 ROUND 함수를 사용하면 된다.
WITH
  example AS (
  SELECT
    'Sat' AS day, 1451 AS numrides, 1018 AS oneways
  UNION ALL SELECT 'Sun', 2376, 936 
  )
SELECT
  *, ROUND(oneways/numrides, 2) AS frac_oneway FROM example

  • ROUND(값, 소수점이하 개수)를 했을 때, 2번 째 자리가 '0'일 경우, 노출되지 않는다.

■ 표준 규격 부동소수점 분할

  • 분모가 0이거나 결과가 오버플로우면 나누기 연산이 실패한다. 따라서 사전에 값이 0인지 확인해서 나눗셈 연산을 보호하는 것보다는 분모가 0일 가능성이 있다면 앞의 예제처럼 특수 함수를 사용하는 것이 좋다. 즉 다음과 같이 쿼리를 개선할 수 있다.
WITH
  example AS (
  SELECT
    'Sat' AS day, 1451 AS numrides, 1018 AS oneways
  UNION ALL SELECT 'Sun', 2376, 936
  UNION ALL SELECT 'Wed',0, 0
  )
SELECT
  *, ROUND(IEEE_DIVIDE(oneways, numrides), 2) AS frac_oneway FROM example

  • IEEE_DIVIDE 함수는 IEEE (Institute of Eletrical and Electronics Engineers)에서 설정한 표준을 따르며 0으로 나누려고 하면 Not-a-Number(NaN)라는 특수 부동소수점 숫자를 반환한다.
  • 또한 개선하기 전의 쿼리는 표준 나누기 연산자와 SAFE_DIVIDE을 사용한다.

■ SAFE 함수

  • 스칼라 함수에 SAFE 접두사를 사용하면 오류를 발생시키지 않고 NULL을 반환한다. 예를 들어 음수의 로그는 정의되지 않으므로 다음 쿼리를 실행하면 오류가 발생한다.
SELECT LOG(10, -3), LOG(10, 3)

 

  • LOG 함수 앞에 SAFE를 덧붙여보면 LOG(10 -3)의 결과가 NULL로 반환된다.
SELECT SAFE.LOG(10, -3), LOG(10, 3)

  • SAFE 접두사는 수학, 함수, 문자열 함수 (예를 들어 SUBSTR 함수는 시작 인덱스가 음수이면 일반적으로 오류를 발생시키지만 SAFE, SUBSTR로 호출되면 NULL을 반환한다) 및 시간 함수에 사용할 수 있다. 그러나 SAFE 접두사는 스칼라 함수에만 사용할 수 있으며, 집계 함수, 분석 함수 또는 사용자 정의 함수에는 사용할 수 없다.

■ 비교

  • 비교 연산은 < , <= , > , >= , != (또는 <>) 등의 연산자를 이용해서 그 결과를 얻는다. NULL과 NaN은 정렬을 위해 유효한 숫자 (-inf 포함)보다 작은 값으로 ㅂ평가되며, NaN은 NULL보다 큰 값으로 평가된다. 그러나 NaN과의 비교는 항상 false를 반환하며, NULL과의 비교는 항상 NULL을 반환한다. 이는 역설적인 결과로 이어질 수 있다.
WITH  example AS(
    SELECT 'Sat' AS day, 1451 AS numrides, 1018 AS oneways
    UNION ALL SELECT 'Sun', 2376, 936
    UNION ALL SELECT 'Mon', NULL, NULL
    UNION ALL SELECT 'Tue', IEEE_DIVIDE(-3, 0), 0 -- 이 값은 -inf와 0이다.
)
SELECT * FROM example
ORDER BY numrides

  • 하지만 다음과 같이 대여 건수가 2,000회 미만인 행을 필터링해 보자.
WITH  example AS(
    SELECT 'Sat' AS day, 1451 AS numrides, 1018 AS oneways
    UNION ALL SELECT 'Sun', 2376, 936
    UNION ALL SELECT 'Mon', NULL, NULL
    UNION ALL SELECT 'Tue', IEEE_DIVIDE(-3, 0), 0 -- 이 값은 -inf와 0이다.
)
SELECT * FROM example
WHERE numrides < 2000

  • 그러면 3개가 아닌 2개의 결과만 나타난다.
  • 이유는 WHERE 절은 결과가 참인 행만 반환하는데, NULL을 2000과 비교하면 결과는 NULL이므로 참으로 평가되지 않기 때문이다.
  • 빅쿼리의 &와 | 연산자는 비트 연산에만 사용함에 주의한다.
  • ! 기호는 != 처럼 NOT을 의미하지만 독립적으로는 사용할 수 없다. 즉 다른 언어처럼 성별에 대한 논리적 부정을 계산하기 위해 !gender를 사용할 수 없다. 같지 않음은 <> 연산자로도 표현할 수 있지만 일관성을 위해 != 또는 <> 중 하나만 사용하는 것이 좋다.

■ NUMERIC을 사용한 정밀 소수 계산

  • INT64와 FLOAT64는 유연하고 빠르게 설계되었지만 계산에 사용될 때는 63비트 영역의 컴퓨터 메모리에 2진수 형태로 저장되므로 표현할 수 있는 값의 범위에 제한이 있다. 대부분의 애플리케이션에서는 문제없이 사용할 수 있지만, 10진법으로 표현하는 숫자를 정확하게 계산해야 하는 재무 및 회계 프로그램에서는 문제가 된다.
  • 빅쿼리의 NUMERIC 데이터 타입은 숫자를 나타내는 38자리 숫자를 제공하며 그 숫자 중 9자리는 소수점 아래의 숫자를 표시한다. 또한 16바이트를 스토리지로 사용하므로 소수점 이하 자릿수를 정확하게 표현할 수 있어 재무 계산에 적합하다.
  • 예를 들어 3번의 결제 기록의 합을 계산하는 경우를 가정해 보자. 당연히 계산 결과가 정확해야 한다. 그러나 FLOAT63 값을 사용하면 숫자가 메모리에 표시되는 방식과 숫자가 10진수로 표시되는 방식 간의 차이로 인해 작은 차이가 생길 수 았다.
WITH example AS (
    SELECT 1.23 AS payment
    UNION ALL SELECT 7.89
    UNION ALL SELECT 12.43
)
SELECT
 SUM(payment) AS total_paid,
 AVG(payment) AS average_paid
FROM example

  • 재무 및 회계 응용 프로그램에서 이런 부정확한 값 때문에 장부상의 최종 잔액이 달라질 수도 있다. 결제 데이터 타입을 NUMERIC으로 변경하면 어떻게 되는지 확인한다.
WITH example AS (
    SELECT NUMERIC '1.23' AS payment
    UNION ALL SELECT NUMERIC '7.89'
    UNION ALL SELECT NUMERIC '12.43'
)
SELECT
 SUM(payment) AS total_paid,
 AVG(payment) AS average_paid
FROM example

  • 지불의 합계가 이제 정확하게 나온다. (평균은 반복되는 소수점이므로 NUMERIC 타입으로도 정확하게 나타낼 수 없다.)
  • 한 가지 주의할 점은 NUMERIC 타입은 NUMERIC '1.23'처럼 문자열 형태로 빅쿼리에서 직접 수집해야 한다. 그렇지 않으면 부동소수점 표현은 정밀한 계산을 하지 않는다.

■ 데이터 타입, 함수 연산자

  • INT64
    • 유일한 정수형
    • 대략 10^-19 ~ 10^19 까지 숫자를 나타낸다. 실수이면 FLOAT64를 사용하고, 불리언이면 BOOL을 사용한다.
  • NUMERIC
    • MUMERIC은 38자리의 정밀도와 9자리의 10진수를 제공하며, 재무 계산이나 과학 계산 등 정확한 계산이 필요할 때 적합하다.
  • STRING
    • 일급(first class)타입이며, 가변 길이의 유니코드 문자열을 표현하다. BYTES는 가변 길이의 문자열이다.
  • TIMESTAMP
    • 시간의 절대 시점을 나타낸다.
  • DATETIME
    • 달력상의 날짜와 시간을 나타낸다. 날짜나 시간을 별도로 표현하기 위한 DATE와 TIME 타입도 지원한다.
  • GEORAPHY
    • GEORAPHY는 지구 표면의 점, 선, 폴리곤을 나타낸다.
  • STRUCT와 ARRAY
    • 2장 참조 (https://classicandprogramming.tistory.com/8?category=987599)

▣ 저장 및 공유

  • 빅쿼리는 웹 UI는 쿼리를 저장하고 공유할 수 있는 기능을 제공한다.

■ 쿼리 기록 및 캐싱

  • 빅쿼리가 감사audio  및 캐싱cashing을 위해 빅쿼리에 제출한 쿼리의 기록을 저장한다.

  • 쿼리 기록은 웹 UI 뿐만 아니라 API나 bq 명령줄 도구로 제출한 쿼리도 모두 보여준다. 쿼리를 클릭하면 편집기에서 쿼리를 열어 수정하고 다시 실행할 수 있다.
  • 이력 정보에는 쿼리에서 처리한 데이터 양과 실행 시간이 포함된다.
  • 쿼리의 실제 결과는 약 24시간 후에 만료되는 임시 테이블에 저장된다.
  • 이 임시 테이블에 저장된 쿼리는 정확히 동일한 쿼리 텍스트가 서비스에 제출되고 쿼리에 CURRENT_TIMESTAMP( ) 나 RAND ( ) 같은 동적 요소가 포함되어 있지 않으면 쿼리 캐시로도 사용된다.
  • 캐시된 쿼리 결과는 비용이 발생되지 않지만, 쿼리가 중복인지 여부를 결정하는 알고리즘은 단순히 문자열이 일치하는지 확인하다. 따라서 공백을 하나 추가하기만 해도 쿼리가 다시 실행된다.

■ 저장된 쿼리

  • 쿼리 편집기에 쿼리를 로드하고 Save Query 버튼을 클릭한 후 쿼리 이름을 지정해 저장할 수 있다. 그러면 빅쿼리는 저장한 쿼리에 대한 URL을 제공한다.

  • 이렇게 저장된 쿼리는 공유도 가능하다.
  • 쿼리를 공유할 때 실제로 공유하는 것은 쿼리의 텍스트다. 즉 데이터에 대한 액세스 권한을 공유하지는 않는다.
  • 쿼리를 실행하기 위한 데이터셋 권한은 IAM 컨트롤을 사용해 독립적으로 제공해야 한다. (10장 참조)
  • 대부분 빅쿼리 기능과 달리 쿼리 저장 및 공유 기능은 웹 UI에서만 사용할 수 있다.

■ 뷰와 공유 쿼리의 비교

  • 쿼리 링크를 공유하면 공동 작업자가 항상 쿼리의 최신 버전을 얻을 수 있도록 쿼리를 계속 편집할 수 있다는 장점이 있다. 따라서 공동 작업자도 쿼리를 살펴보고 필요에 따라 수정한 후 쿼리를 실행하는 경우에 특히 유용하다.
  • 공유하는 쿼리의 구문은 반드시 문법적으로 정확하지 않아도 무방하다.
  • 특정 부분 집합에서 쿼리해야 하거나 쿼리 결과에 다시 쿼리를 해야 할 때, 쿼리를 뷰로 저장하고 동료에게 뷰에 대한 링크를 보내는 것이 좋다. 쿼리를 공유하는 것보다 뷰를 공유하면 뷰가 데이터셋에 저장되며 세분화된 IAM 컨트롤을 제공할 수 있다는 장점이 있다.

+ Recent posts