Vantageで様々な言語で記述されたプログラム・スクリプトを実行する

著者 [Adam Tworkiewicz](adam.tworkiewicz@teradata.com)
最終更新日 2021年9月7日

:::note warn
警告
本記事はTeradata Corporationのサイト[Getting Started](https://quickstarts.teradata.com/index.html#getting-access-to-vantage)に掲載された内容を抄訳したものです。掲載内容の正確性・完全性・信頼性・最新性を保証するものではございません。正確な内容については、原本をご参照下さい。
また、修正が必要な箇所や、ご要望についてはコメントをよろしくお願いします。
:::

# 概要
時にはSQLで簡単に表現できない複雑なロジックをデータに適用する必要があります。そのような場合、ユーザー定義関数(UDF)でロジックを関数化するという方法があります。しかし、このロジックがUDFでサポートされていない言語ですでにコーディングされている場合はどうでしょうか。スクリプト・テーブル・オペレータはVantageが持つ機能でロジックをデータに取り込みVantage上で実行することがでるものです。この方法の利点はデータを操作するために Vantage からデータを取得する必要がないことです。またVantage上でデータアプリケーションを実行することでVantageの並列性を活用することができます。アプリケーションをどのように拡張するか考える必要はありません。Vantageに任せればいいのです。

# 前提条件
Teradata Vantageインスタンスにアクセスする必要があります。

::: note info
メモ！
Vantageの新しいインスタンスが必要な場合は、[Google Cloud](https://qiita.com/ykswi/items/10a382de5fa5a4a42c50)、[Azure](https://qiita.com/ykswi/items/2c30aa1f5413b16c838c)、[AWS](https://qiita.com/ykswi/items/fba559149ad568678d58)のクラウドにVantage Expressという無料版をデプロイメントすることができます。また、[VMware](https://qiita.com/ykswi/items/ebe1d854c38db9bb0f51)、[VirtualBox](https://qiita.com/ykswi/items/ffadaa375add309f2075)、または[UTM](https://qiita.com/ykswi/items/94995954ae723185c2f7)を使用して、ローカルマシン上でVantage Expressを実行することもできます。
:::

# ハローワールド
まずは簡単なことから始めましょう。データベースに「Hello World」と表示させたいとしたらどうでしょう？

```sql:HelloWorld.sql
SELECT *
FROM
  SCRIPT(
    SCRIPT_COMMAND('echo Hello World!')
    RETURNS ('Message varchar(512)'));
```

結果は以下のように返されます。

|Message|
|:------------|
|Hello World!|
|Hello World!|

ここで何が起こったのか考察してみましょう。SQLには**echo Hello World!** というBashコマンドが含まれています。さてこれでBashコマンドを実行する方法はわかりました。しかし、なぜ返された結果は1行ではなく2行になったのでしょうか？それはこのシンプルなスクリプトが各AMPで一回ずつ実行されたからで、私はたまたま2台のAMPを持っていたのです。
※AMP：Vantageが内部アーキテクチャとして持っている仮想並列プロセッサ

```sql:CountAMP.sql
-- Teradata magic that returns the number of AMPs in a system
SELECT hashamp()+1 AS number_of_amps;
```

このSQLの結果です。

|number_of_amps|
|--------------:|
|2|

このシンプルなスクリプトは、Script Table Operator (STO)の背後にあるアイデアを示しています。スクリプトを実行するとデータベースはそれを各AMP上で1回ずつ並行して実行されます。これはスクリプトに変換ロジックがあり処理するデータが大量にある場合に魅力的なモデルとなります。このような並列処理を行うとする場合、通常はアプリケーションに並行処理を組み込む必要があります。STOにそれを任せることでVantageがデータに適した並行処理レベルを選択できるようになります。

# サポートされる言語
さて、先ほどはBashの**echo**を実行したわけですが、Bashは複雑なロジックを表現するには生産的な環境とは言えません。では他にどのような言語がサポートされているのでしょうか？Vantageのノードで実行可能なバイナリコードであればSTOで使用することができます。ただしバイナリを実行するための環境がすべての Vantage ノードにインストールされている必要があります。つまり実際には管理者がサーバー上で保守できるものに選択肢が限られることを意味していることに注意してください。そのような環境の選択肢としてPythonは非常に人気があります。

# スクリプトのアップロード
しかし既存のロジックが大きなファイルに保存されている場合、どうしたらよいでしょうか。確かに、SQLクエリにスクリプト全体とエスケープクォートを貼り付けたくはないでしょう。そこで、UIF(User Installed Files)機能を使って、スクリプトのアップロードの問題を解決します。

例えば、以下のような内容の**helloworld.py**スクリプトがあったとします。

```python:HelloWorld.py
print("Hello World!")
```
このスクリプトがローカルマシンの **/tmp/helloworld.py** にあると仮定します。
まずVantageでパーミッションを設定する必要があります。ここでは、クリーンな状態を保つために新しいデータベースを使用してこれを行います。

```sql:CreateDatabase.sql
-- Create a new database called sto
CREATE DATABASE STO
AS PERMANENT = 60e6, -- 60MB
    SPOOL = 120e6; -- 120MB

-- Allow dbc user to create scripts in database STO
GRANT CREATE EXTERNAL PROCEDURE ON STO to dbc;
```

次のプロシージャ呼び出しを使用してスクリプトをVantageにアップロードすることができます。

```sql:InstallFile.sql
call SYSUIF.install_file('helloworld',
                         'helloworld.py', 'cz!/tmp/helloworld.py');
```

アップロードされたスクリプトは次のように呼び出すことができます。

```sql:CallScript.sql
-- We switch to STO database
DATABASE STO

-- We tell Vantage where to look for the script. This can be
-- any string and it will create a symbolic link to the directory
-- where our script got uploaded. By convention, we use the
-- database name.
SET SESSION SEARCHUIFDBPATH = sto;

-- We now call the script. Note, how we use a relative path that
-- starts with `./sto/`, which is where SEARCHUIFDBPATH
-- is pointing.
SELECT *
FROM SCRIPT(
  SCRIPT_COMMAND('python3 ./sto/helloworld.py')
  RETURNS ('Message varchar(512)'));
```

最後のSELECT文は以下のような結果を返すはずです。

|Message|
|:------------|
|Hello World!|
|Hello World!|

Hello Worldはこのあたりまでにして、それでは**SCRIPT**にデータを渡してみましょう。

# Vantageに保存されているデータをSCRIPTに渡す
これまでは、**SCRIPT**演算子を使ってスタンドアローンのスクリプトを実行してきました。しかし、Vantage 上でスクリプトを実行する主な目的はVantage内にあるデータを処理することです。ここではVantageからデータを取得し**SCRIPT**に渡す方法を見ていきましょう。

まず、いくつかの行を持つテーブルを作成することから始めます。

```sql:CreateTable.sql
-- Switch to STO database.
DATABASE STO

-- Create a table with a few urls
CREATE TABLE urls(url varchar(10000));
INS urls('https://www.google.com/finance?q=NYSE:TDC');
INS urls('http://www.ebay.com/sch/i.html?_trksid=p2050601.m570.l1313.TR0.TRC0.H0.Xteradata+merchandise&_nkw=teradata+merchandise&_sacat=0&_from=R40');
INS urls('https://www.youtube.com/results?search_query=teradata%20commercial&sm=3');
INS urls('https://www.contrivedexample.com/example?mylist=1&mylist=2&mylist=...testing');
```

クエリパラメータを解析するために以下のスクリプトを使用することにします。

```python:urlparser.py
from urllib.parse import urlparse
from urllib.parse import parse_qsl
import sys

for line in sys.stdin:
    # remove leading and trailing whitespace
    url = line.strip()
    parsed_url = urlparse(url)
    query_params = parse_qsl(parsed_url.query)

    for element in query_params:
        print("\t".join(element))
```

このスクリプトでは、URLは一行ずつ**標準入力**に入力されると仮定していることに注意してください。また値の区切り文字としてタブ文字を使用し結果を一行ずつ表示することにも注意してください。

スクリプトをインストールしましょう。ここではスクリプトファイルがローカルマシンの **/tmp/urlparser.py** にあると仮定しています。

```sql:InstallScript.sql
CALL SYSUIF.install_file('urlparser',
	'urlparser.py', 'cz!/tmp/urlparser.py');
```

スクリプトをインストールした状態で今度は**urls**テーブルからデータを取得しそれをスクリプトに送り込んでクエリパラメータを取得します。

```sql:RunScript.sql
-- We inform Vantage to create a symbolic link from the UIF directory to ./sto/
SET SESSION SEARCHUIFDBPATH = sto ;

SELECT *
  FROM SCRIPT(
    ON(SELECT url FROM urls)
    SCRIPT_COMMAND('python3 ./sto/urlparser.py')
    RETURNS ('param_key varchar(512)', 'param_value varchar(512)'));
```

その結果クエリパラメータとその値を得ることができます。キーと値のペアの数だけ行があります。またスクリプトではキーと値の間にタブを挿入して出力しているのでSTOからは2列のカラムが得られます。

|param_key|param_value|
|:------------|:-----------------------------------------------------|
|q|NYSE:TDC|
|_trksid|p2050601.m570.l1313.TR0.TRC0.H0.Xteradata merchandise|
|search_query|teradata commercial|
|_nkw|teradata merchandise|
|sm|3|
|_sacat|0|
|mylist|1|
|_from|R40|
|mylist|2|
|mylist|...testing|

# スクリプト出力のテーブルへの挿入
Vantageからデータを取得しそれをスクリプトに渡して出力を得る方法を学びました。この出力を簡単にテーブルに保存する方法はありますか？もちろんあります。
上記のSELECTとCREATE TABLEステートメントを組み合わせればよいのです。

```sql:CreateAsSelect.sql
-- We inform Vantage to create a symbolic link from the UIF directory to ./sto/
SET SESSION SEARCHUIFDBPATH = sto ;

CREATE MULTISET TABLE
    url_params(param_key, param_value)
AS (
    SELECT *
    FROM SCRIPT(
      ON(SELECT url FROM urls)
      SCRIPT_COMMAND('python3 ./sto/urlparser.py')
      RETURNS ('param_key varchar(512)', 'param_value varchar(512)'))
) WITH DATA
NO PRIMARY INDEX;
```

では**url_params**テーブルの中身を調べてみましょう。

```sql:SelectUrlParam.sql
SELECT * FROM url_params;
```

以下のような出力が表示されるはずです。

|param_key|param_value|
|:------------|:-----------------------------------------------------|
|q|NYSE:TDC|
|_trksid|p2050601.m570.l1313.TR0.TRC0.H0.Xteradata merchandise|
|search_query|teradata commercial|
|_nkw|teradata merchandise|
|sm|3|
|_sacat|0|
|mylist|1|
|_from|R40|
|mylist|2|
|mylist|...testing|

# まとめ
このクイックスタートではVantage のデータに対してスクリプトを実行する方法について説明しました。スクリプト・テーブル・オペレータ (STO) を使用してスクリプトを実行しました。このオペレータを使用すると、データにロジックを適用することができます。スクリプトをAMPごとに並行して実行することで、並行処理に関する考慮事項をデータベースにオフロードすることができます。スクリプトを提供するだけでデータベースがそれを並列に実行するのです。

# さらに詳しく

・[Teradata Vantage™ - SQL Operators and User-Defined Functions - SCRIPT](https://docs.teradata.com/r/9VmItX3V2Ni9Ts70HbDzVg/CBAaRxUyOdF0t1SQIuXeug)

・[R and Python Analytics with SCRIPT Table Operator](https://docs.teradata.com/v/u/Orange-Book/R-and-Python-Analytics-with-SCRIPT-Table-Operator-Orange-Book-4.3.1)

#### Teradata Vantageへのお問合せ
[Teradata Vantage へのお問合せ](https://www.teradata.jp/About-Us/Contact)