ChatGPT APIでSQLを生成してみた

## 背景・目的

+ 社内におけるデータ活用はビジネス成功のために重要である
+ 非エンジニアがデータマートを利用するハードルとして、社内のデータのスキーマ理解がある
+ OpenAIのchat APIを利用して、自然言語を与えるとSQLを生成するプログラムを作成する
+ 社内のChatBotに搭載すれば、非エンジニアがSQLを書くハードルを下げられて、エンジニアがSQLの書き方を教える手間を省けるのでは

## 結論

社内のデータモデルを使って、SQLを生成することができた。

+ データモデルだけなら、データ流出にならないのは良い
+ 複数テーブルのJOINもできそう
+ `CREATE TABLE`文を文脈として入れるほどコストはかかるが、大体1回1円ぐらいに収まりそう
+ ネストされたフィールドを使う必要があったりWindow関数やUDFなどを使う必要がある場合などの複雑な場合は未検証


## 仮説

[Chat2Query](https://pingcap.co.jp/chat2query-ai-powered-sql-generator/)のように、自然言語からSQLを生成するサービスが出てきているなら、APIでできるのでは？

とはいえ、データモデルをどうやって伝えるのか？が悩ましいところ。ChatGPTは学習に使われたデータ量に比例して理解度が上がると考えると、データモデルを記述する一般的な方法としてMySQLなどのDDLを使えば認識してくれるのでは？

## 実験1

ということで、下記のようなプロンプトで試してみました。

````
prompt = """
Your task is to create SQL to realize the input order.
Output the code after understanding the following DDL as a premise.
Output format should be a SQL statement.

```
{DDL}
```
Input Order: <{order}>
"""
````

たとえば、AWSのWAFのlogに対してSQLを作りたい場合は、

````python
Your task is to create SQL to realize the input order.
Output the code after understanding the following DDL as a premise.
Output format should be a SQL statement.

```
CREATE TABLE `sample`.`waf_logs` (
    `timestamp` bigint, 
    `formatversion` int,
    `webaclid` string,
    `terminatingruleid` string,
    `terminatingruletype` string, 
    `action` string, 
    `terminatingrulematchdetails` array<struct<conditiontype:string,location:string,matcheddata:array<string>>>, 
    `httpsourcename` string, 
    `httpsourceid` string, 
    `rulegrouplist` array<struct<rulegroupid:string,terminatingrule:struct<ruleid:string,action:string,rulematchdetails:string>,nonterminatingmatchingrules:array<struct<ruleid:string,action:string,rulematchdetails:array<struct<conditiontype:string,location:string,matcheddata:array<string>>>>>,excludedrules:array<struct<ruleid:string,exclusiontype:string>>>>, 
    `ratebasedrulelist` array<struct<ratebasedruleid:string,limitkey:string,maxrateallowed:int>>, 
    `nonterminatingmatchingrules` array<struct<ruleid:string,action:string>>, 
    `requestheadersinserted` string, 
    `responsecodesent` string, 
    `httprequest` struct<clientip:string,country:string,headers:array<struct<name:string,value:string>>,uri:string,args:string,httpversion:string,httpmethod:string,requestid:string>, 
    `labels` array<struct<name:string>>
)
```

Input Order: <WAFのログから直近30分の400 エラー一覧を取得>
````

となり、結果は下記のようになりました。

```sql
SELECT *
FROM `sample`.`waf_logs`
WHERE `timestamp` >= UNIX_TIMESTAMP() - 1800
AND `httpresponsecode` = 400
```

いい感じですね。

## 実験2

複数テーブルのJoinが必要になる場合はどうでしょうか。
たとえば、Youtubeの動画に対してコメントを格納するデータがあったとして、`CREATE TABLE文`が3つあるケースで試してみます。今回は、列名からjoin先を推測してくれるのか？を確かめるために、あえてindexやforeign key制約は抜いてみました。

````python
Your task is to create SQL to realize the input order.
Output the code after understanding the following DDL as a premise.
Output format should be a SQL statement.

```
CREATE TABLE videos (
    video_id INT PRIMARY KEY,
    title VARCHAR(255),
    description TEXT,
    upload_date TIMESTAMP,
    duration INT,
    thumbnail_url VARCHAR(255)
);

CREATE TABLE users (
    user_id INT PRIMARY KEY,
    username VARCHAR(255),
    email VARCHAR(255),
    password_hash VARCHAR(255),
    registration_date TIMESTAMP
);

CREATE TABLE comments (
    comment_id INT PRIMARY KEY,
    video_id INT,
    user_id INT,
    comment TEXT,
    comment_date TIMESTAMP
);
```
Input Order: <コメント数が多いユーザー数のランキングTop20を知りたい>
````

結果は下記のようになりました。(トークン数: 230)

```sql
SELECT users.user_id, users.username, COUNT(comments.comment_id) AS comment_count
FROM users
JOIN comments ON users.user_id = comments.user_id
GROUP BY users.user_id, users.username
ORDER BY comment_count DESC
LIMIT 20;
```

列名が一致していることもあり、joinもいい感じにやってくれていますね！


## コード

コードとしては以下のようになりました。モデルは`gpt-3.5-turbo`を使っています。
コスト面は、現時点で$0.002/1K tokens（詳細は[Pricing](https://openai.com/pricing))です。
先ほどの例だと300Tokensぐらいなので、1回1円行かないぐらいかなと思います。
[こちら](https://platform.openai.com/docs/models/gpt-3-5)によると、`gpt-3.5-turbo`の場合1回の問合せでリクエスト・レスポンス合わせて4096Tokensまで大丈夫なので、DDLはもうちょっと増やせそうですね。


````python
prompt = """
Your task is to create SQL to realize the input order.
Output the code after understanding the following DDL as a premise.
Output format should be a SQL statement.

```
{schema}
```

Input Order: <{order}>
"""

def create_sql(order, context_name):
    """
    Generate SQL
    """
    schema = get_schema_from_context_name(context_name)
    res = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        temperature=0,
        max_tokens=1024,
        messages=[
            {"role": "user", "content": prompt.format(schema=schema, order=order)}
        ],
    )
````

スクリプトにしたものを[GitHub](https://github.com/hiko2msp/llm_for_sql)に置いています。

## まとめ

雑ではありましたが、自然言語からSQLを生成するやり方について試してみました。
参考になれば幸いです。


## 備考

コスト面、もっと安くできないかな？と思ってgpt-3のadaとかでもやってみたのですが、レスポンスが空文字列で返ってきてしまったので、もうちょっと試行錯誤が必要かもしれません。
`gpt-3.5-turbo`だとオーバーキルである可能性もあるので、良いやり方を見つけられたらコメントで教えていだだけますと喜びます。