【PostgreSQL】業務中に見かけたSQLの備忘録

## 概要
業務中に出会ったSQLやSQLのTipsを、復習がてらまとめていく記事です。
知ってる知ってる！とか、こんなのあったんだー、など共感や発見があれば嬉しい限りです。

:::note info
・動作確認環境（ select version(); 実行結果）
PostgreSQL 14.5 (Debian 14.5-2.pgdg110+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
:::

## NULLIF

### 説明
・AとBが同じであれば、NULLを返します
・AとBが異なる場合、Aを返します
```sql
NULLIF(A, B)
```
### 実行例
・NULLIFの挙動を簡単に確認する例です
```sql
-- 実行結果(AとBが異なるのでAを返す): 1
select nullif(1,3);
-- 実行結果(AとBが同じなのでNULLを返す): NULL
select nullif(3,3);
-- 実行結果(AとBが異なるのでAを返す): NULL
select nullif(null,3);
-- 実行結果(AとBが異なるのでAを返す): 3
select nullif(3,null);
-- 実行結果(AとBが同じなのでNULLを返す): NULL
select nullif(null,null);
```

### 利用シーン
実際の案件で見た利用シーンとしては、「０で割るとエラーになってしまう状況を回避したい」といった時に利用されていました
以下に理由と、実際に回避する例を記載します

・1. 以下はSQL実行時に「ERROR: division by zero」エラーになります
```sql
select 1 / 0;
```

・2. ただし、NULLで割ったり、0で割らなければエラーにはなりません
```sql
select 0 / null; -- 結果はNULL
select 1 / null; -- 結果はNULL
select null / 1; -- 結果はNULL
select null / 100; -- 結果はNULL
```

・3. 2の性質「0で割らなければエラーにならない」を利用して、IFNULLを使い0除算行わないようにしつつ客単価を取得する例を示します
```sql
with uriage_2023 as (
    -- 2023/04分の売上合計(sum_sales_amount)と、当月購入者数合計(number_of_purchasers)
    select '2023/04' AS target_month, 150000 AS sum_sales_amount, 15 AS number_of_purchasers
    union all
    -- 2023/05分の売上合計(sum_sales_amount)と、当月購入者数合計(number_of_purchasers)
    select '2023/05' AS target_month, 0 AS sum_sales_amount, 0 AS number_of_purchasers
    union all
    -- 2023/06分の売上合計(sum_sales_amount)と、当月購入者数合計(number_of_purchasers)
    select '2023/06' AS target_month, 300000 AS sum_sales_amount, 30 AS number_of_purchasers
)
select
       target_month AS "集計対象月",
       sum_sales_amount || '円' AS "当月売上合計金額",
       number_of_purchasers || '人' AS "当月購入者数合計",
       COALESCE(
            (sum_sales_amount / nullif(number_of_purchasers, 0)), 0
       ) || '円' AS "客単価"
from uriage_2023
;
```

・実行結果
| 集計対象月 | 当月売上合計金額 | 当月購入者数合計 | 客単価 |
| :--- | :--- | :--- | :--- |
| 2023/04 | 150000円 | 15人 | 10000円 |
| 2023/05 | 0円 | 0人 | 0円 |
| 2023/06 | 300000円 | 30人 | 10000円 |

※to_charを使って、金額をカンマ区切りにして表示することも可能です
※COALESCEが不明な場合は、後述していますので是非参考にしてください

※with句では以下のような集計結果を取得しています
| target\_month | sum\_sales\_amount | number\_of\_purchasers |
| :--- | :--- | :--- |
| 2023/04 | 150000 | 15 |
| 2023/05 | 0 | 0 |
| 2023/06 | 300000 | 30 |

## COALESCE
### 説明
・COALESCE(A, B, C...) 与えられた引数の中で最初に見つけたNULLではない値を返します

### 実行例
・COALESCEの挙動を簡単に確認する例です
```sql
-- 実行結果(最初に見つかるNULLではない引数が返される): 0
select COALESCE(null, 0);
-- 実行結果(最初に見つかるNULLではない引数が返される): 999
select COALESCE(999, 0);
-- 実行結果(最初に見つかるNULLではない引数が返される): 999
select COALESCE(null, 999, 0);
```

## TRANSLATEとREPLACE
### 説明
**・TRANSLATE(置換対象, 'ABC', 'DEF')**
　　AをDに、BをEに、CをFに1文字ごとに置換します

**・REPLACE(置換対象, 'ABC', 'DEF')**
　　ABCをDEFに置換します

### 実行例
・TRANSLATEの挙動を簡単に確認する例です
・TRANSLATEは1文字ずつ置換する点に留意しましょう
```sql
with prefectures AS (
    select '秋田県' AS "prefecture"
    UNION ALL
    select '福井県' AS "prefecture"
    UNION ALL
    select '東京都' AS "prefecture"
)
select
       prefecture,
       -- 「秋」を「春」に置換する
       replace(prefecture, '秋', '春') AS "REPLACE置換例1",
       -- 「秋福」という文字列を探して、「春福」に置換する（※該当箇所無しのため置換は行われない）
       replace(prefecture, '秋福', '春服') AS "REPLACE置換例2",
       -- 「秋」は「春」に、「福」は「服」に。「東」は「西」に1文字ごとに置換される
       translate(prefecture, '秋福東', '春服西') AS "TRANSLATEの置換例1",
       -- 「東」に対応する置換後文字が無い場合は、「東」は置換後に空文字となる
       translate(prefecture, '秋福東', '春服') AS "TRANSLATEの置換例2"
from prefectures;
```

## CASEで集計
### 前提
・テーブル名は`htk_users`とし、以下データがある前提でCASEを利用して集計する例です
| id | account\_number | name | sex | postcode | prefecture | city | street | room\_number |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | 00001 | Sin | 1 | 0100834 | 秋田県 | 秋田市旭川南町 | 1-1-1 | XXX101号室 |
| 2 | 00002 | Alice | 2 | 0100834 | 秋田県 | 秋田市旭川南町 | 1-1-2 | XXX102号室 |
| 3 | 00003 | Dave | 1 | 0100834 | 秋田県 | 秋田市旭川南町 | 1-1-3 | XXX103号室 |
| 4 | 00004 | Calol | 2 | 8120885 | 福岡県 | 福岡市博多区相生町 | 1-2-1 | XXX201号室 |
| 5 | 00005 | Yuji | 1 | 8120885 | 福岡県 | 福岡市博多区相生町 | 1-2-2 | XXX202号室 |
| 6 | 00006 | Serena | 2 | 8120885 | 福岡県 | 福岡市博多区相生町 | 1-2-3 | XXX203号室 |
| 7 | 00007 | Bob | 1 | 9188187 | 福岡県 | 福岡市博多区相生町 | 1-2-4 | XXX204号室 |
| 8 | 00008 | Candy | 2 | 9188187 | 福井県 | 福井市花守町 | 1-3-2 | XXX302号室 |
| 9 | 00009 | Mike | 1 | 9188187 | 福井県 | 福井市花守町 | 1-3-3 | XXX303号室 |
| 10 | 00010 | Elice | 2 | 1080072 | 東京都 | 港区白金１ | XXXXX | XXX999号室 |

### 集計例
・いわゆる縦でもっているたくさんのデータ（`htk_users`は10件しかないですが...笑）を、集計して横に並べてみます
・以下の集計例は、都道府県別にユーザー数を算出する例です
※補足ですが、countにnullを渡すと0が返るので埼玉県のユーザー件数は0になっています

```sql
select
    count(*) AS "全ユーザー件数",
    count(case when prefecture = '秋田県' then 1 end) as "秋田県のユーザー件数",
    count(case when prefecture = '福岡県' then 1 end) as "福岡県のユーザー件数",
    count(case when prefecture = '福井県' then 1 end) as "福井県のユーザー件数",
    count(case when prefecture = '東京都' then 1 end) as "東京都のユーザー件数",
    count(case when prefecture = '埼玉県' then 1 end) as "埼玉県のユーザー件数"
from htk_users;
```
・実行結果
| 全ユーザー件数 | 秋田県のユーザー件数 | 福岡県のユーザー件数 | 福井県のユーザー件数 | 東京都のユーザー件数 | 埼玉県のユーザー件数 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| 10 | 3 | 4 | 2 | 1 | 0 |



## GROUP BYで分子を、ウィンドウ関数で分母を出して比率を求める

### 前提
・テーブル名は`htk_users`とし、以下データがある前提で集計例を出します
| id | account\_number | name | sex | postcode | prefecture | city | street | room\_number |
| :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- | :--- |
| 1 | 00001 | Sin | 1 | 0100834 | 秋田県 | 秋田市旭川南町 | 1-1-1 | XXX101号室 |
| 2 | 00002 | Alice | 2 | 0100834 | 秋田県 | 秋田市旭川南町 | 1-1-2 | XXX102号室 |
| 3 | 00003 | Dave | 1 | 0100834 | 秋田県 | 秋田市旭川南町 | 1-1-3 | XXX103号室 |
| 4 | 00004 | Calol | 2 | 8120885 | 福岡県 | 福岡市博多区相生町 | 1-2-1 | XXX201号室 |
| 5 | 00005 | Yuji | 1 | 8120885 | 福岡県 | 福岡市博多区相生町 | 1-2-2 | XXX202号室 |
| 6 | 00006 | Serena | 2 | 8120885 | 福岡県 | 福岡市博多区相生町 | 1-2-3 | XXX203号室 |
| 7 | 00007 | Bob | 1 | 9188187 | 福岡県 | 福岡市博多区相生町 | 1-2-4 | XXX204号室 |
| 8 | 00008 | Candy | 2 | 9188187 | 福井県 | 福井市花守町 | 1-3-2 | XXX302号室 |
| 9 | 00009 | Mike | 1 | 9188187 | 福井県 | 福井市花守町 | 1-3-3 | XXX303号室 |
| 10 | 00010 | Elice | 2 | 1080072 | 東京都 | 港区白金１ | XXXXX | XXX999号室 |
| 11 | 00011 | Silva | 1 | 1080075 | 東京都 | 港区港南二丁目999番1号 | 999番1号 | XXXビル 8F |

### 集計例
・GROUP BY prefectureを行い、都道府県別のユーザー件数を出しつつ、全てのユーザー数の内各都道府県別にユーザー件数の比率を出してみます

```sql
select
    prefecture,
    -- A: group by prefecture による都道府県別のユーザー件数
    count(prefecture) AS "都道府県別ユーザー件数",
    -- B: 「group by prefecture による都道府県別のユーザー件数」の合算値をウィンドウ関数(OVER句)を利用して全数を各行で取得
    sum(count(id)) over() AS "全ユーザー件数",
    -- A÷B*100で、比率を出す
    round(count(prefecture) / (sum(count(prefecture)) over()) * 100, 2) || '%' AS "都道府県別の比率"
from htk_users
group by prefecture
order by prefecture
;
```

・実行結果
| prefecture | 都道府県別ユーザー件数 | 全ユーザー件数 | 都道府県別の比率 |
| :--- | :--- | :--- | :--- |
| 東京都 | 2 | 11 | 18.18% |
| 福井県 | 2 | 11 | 18.18% |
| 福岡県 | 4 | 11 | 36.36% |
| 秋田県 | 3 | 11 | 27.27% |


## UPDATE時にNULLなら上書き、NULLじゃなかったら元々の文字列に改行を加えて追記
remarksの値がNULLであれば上書きを行い、NULLじゃなければ元々の文字列に改行を加えて追記する例になります
以下申込データ（テーブル名「`entries`」）があると想定します
| id | remarks |
| :--- | :--- |
| 7436 | 担当者Aにより案内済 |
| 7614 | NULL |

元々remarksに値がある場合は、上書きしたくない状況だと仮定します
「remarksがNULLであればsetで指定した値で上書きを行い、NULLではない場合は、元々の値に改行を加えてから追記したい」ような状況だとすると、下記のように実現できます
※chr(10)は改行で、||は文字列の結合です

```sql
update entries
set remarks = (
    case
        when remarks is null then 'XXXのため直接データ修正いたしました。'
        else remarks || chr(10) || 'XXXのため直接データ修正いたしました。'
    end
)
where id in (7614, 7436);
```

・UPDATE実行後
| id | remarks |
| :--- | :--- |
| 7436 | 担当者Aにより案内済<br/>XXXのため直接データ修正いたしました。 |
| 7614 | XXXのため直接データ修正いたしました。 |

## バルクインサート
### 説明
・普通のインサートでは、えいさ、ほいさ、えいさ、ほいさとデータを作成しますが、バルクインサートは一括でズドン！とデータを作成するイメージです
・作成するデータ量が多ければ、バルクインサートの方が早いし効率が良いイメージを持ってもらえれば大丈夫です

### 書式
```sql
insert into SAMPLE_TABLE (column1, column2) 
values 
-- 追加分1行目
('column1の値', 'column2の値'),
-- 追加分2行目
('column1の値', 'column2の値'),
-- 追加分3行目
('column1の値', 'column2の値') 
...
;
```

### 実行例
```sql
insert into htk_users (id, account_number, name, sex, postcode, prefecture, city, street, room_number)
values
(1, '00001', 'Sin', 1, '0100834', '秋田県', '秋田市旭川南町', '1-1-1', 'XXX101号室'),
(2, '00002', 'Alice', 2, '0100834', '秋田県', '秋田市旭川南町', '1-1-2', 'XXX102号室'),
(3, '00003', 'Dave', 1, '0100834', '秋田県', '秋田市旭川南町', '1-1-3', 'XXX103号室'),
(4, '00004', 'Calol', 2, '8120885', '福岡県', '福岡市博多区相生町', '1-2-1', 'XXX201号室'),
(5, '00005', 'Yuji', 1, '8120885', '福岡県', '福岡市博多区相生町', '1-2-2', 'XXX202号室'),
(6, '00006', 'Serena', 2, '8120885', '福岡県', '福岡市博多区相生町', '1-2-3', 'XXX203号室'),
(7, '00007', 'Bob', 1, '9188187', '福岡県', '福岡市博多区相生町', '1-2-4', 'XXX204号室'),
(8, '00008', 'Candy', 2, '9188187', '福井県', '福井市花守町', '1-3-2', 'XXX302号室'),
(9, '00009', 'Mike', 1, '9188187', '福井県', '福井市花守町', '1-3-3', 'XXX303号室'),
(10, '00010', 'Elice', 2, '1080072', '東京都', '港区白金１', 'XXXXX', 'XXX999号室'),
(11, '00011', 'Silva', 1, '1080075', '東京都', '港区港南二丁目999番1号', '999番1号', 'XXXビル 8F')
;
```

## truncateでデータを全消ししつつシーケンスリセット
### 説明と実行例
・restart identityをつけると、データを削除しつつシーケンスをリセットします
・<font color="Red">**※データを削除する構文ですので、取り扱いにご注意ください**</font>
```sql
-- restart identity がなければシーケンスリセットは行われません
truncate table SAMPLE_TABLE restart identity;
```
※SAMPLE_TABLEの部分は、削除したいテーブル名を入れます

## 正規表現を使った抽出

### 書式
・`column ~ '正規表現'`：正規表現に一致するものを探す（大文字小文字の区別あり）
・`column !~ '正規表現'`：正規表現に一致しないものを探す（大文字小文字の区別あり）
※大文字小文字を区別したくない場合は、~の後に*をつけます

### 実行例
・以下は、先頭数値1桁の後にハイフンがくるような、変な電話番号のデータがないか確認した際の例です

```sql
select count(*)
from SAMPLE_TABLE
where tel ~ '^[0-9]{1}-';
```

## string_agg

### 説明
・縦持ちのデータ（行データ）を、第二引数で指定した文字列区切りの文字列にまとめます

### 実行例
・GROUP BY無しの例です
```sql
with tmp_venues as (
    select '会場A' as name
    union all
    select '会場B' as name
    union all
    select '会場C' as name
)
select string_agg(tmp_venues.name, ',')
from tmp_venues
;
```
・実行結果
| string\_agg |
| :--- |
| 会場A,会場B,会場C |

・GROUP BY有りの例です
```sql
with tmp_venues as (
    select '会場A' as name, 1 as is_using
    union all
    select '会場B' as name, 1 as is_using
    union all
    select '会場C' as name, 1 as is_using
)
select string_agg(tmp_venues.name, ',')
from tmp_venues
group by tmp_venues.is_using
;

```
・実行結果
| string\_agg |
| :--- |
| 会場A,会場B,会場C |

・同じ会場名があり、distinctで重複削除する例です
```sql
with tmp_venues as (
    select '会場A' as name, 1 as is_using
    union all
    select '会場B' as name, 1 as is_using
    union all
    select '会場C' as name, 1 as is_using
    union all
    select '会場C' as name, 1 as is_using
)
select string_agg(distinct tmp_venues.name, ',')
from tmp_venues
group by tmp_venues.is_using
;
```
・実行結果
| string\_agg |
| :--- |
| 会場A,会場B,会場C |

※`string_agg(distinct tmp_venues.name, ',' ORDER BY tmp_venues.name DESC)`のようにstring_agg内の並び順も制御可能です

## array_agg
### 説明
・縦持ちのデータ(行データ)を、配列にまとめます

### 実行例
・3行の会場データを、array_aggにより1つの配列として取得する例です
```sql
with tmp_venues as (
    select '会場A' as name
    union all
    select '会場B' as name
    union all
    select '会場C' as name
)
select array_agg(name)
from tmp_venues
;
```
・実行結果
| array\_agg |
| :--- |
| {会場A,会場B,会場C} |

※array_agg内で以下のようにORDER BYも利用できます
```sql
with tmp_venues as (
    select '会場A' as name
    union all
    select '会場B' as name
    union all
    select '会場C' as name
)
select array_agg(name order by name desc)
from tmp_venues
;
```
・実行結果
| array\_agg |
| :--- |
| {会場C,会場B,会場A} |

## unnest
### 説明
・配列を、縦持ちのデータ（行データ）に展開します

### 実行例
```sql
SELECT unnest(ARRAY['会場A','会場B','会場C']::VARCHAR(255)[]);
```

・実行結果
| unnest |
| :--- |
| 会場A |
| 会場B |
| 会場C |


## 配列 @> 配列
postgresqlの配列型を触っているとこやつの便利さに助けられる場面がありました。
パフォーマンスチューニングの際も有用だったので、覚えておいて損はないものです。（と自分は感じてます笑）
語ると長くなるので、使い方だけさくっと記載します。

### 説明
・書式： `左辺の配列 @> 右辺の配列`
・左辺の配列の要素に存在するものが、右辺の要素にあれば、trueを返します
・右辺が空配列の場合、trueを返します
**・左辺に存在しない要素が右辺にあるとfalseを返します**

### 実行例
・挙動をシンプルな例で伝えたいのでSELECT句で`@>`を利用していますが、HAVING句やWHERE句でも利用できます
・`'{1,2,3}'::int[]`は配列型のリテラル表現であり、`ARRAY[1,2,3]::int[]`と同じです
```sql
-- =========================================
-- 左辺の配列 @> 右辺の配列
-- ＝＞左辺の配列の要素に存在するものが、右辺の要素にあれば、true
-- ＝＞右辺が空配列の場合、true
-- ＝＞左辺に存在しない要素が右辺にあるとfalse
-- =========================================
-- true
select '{1,2,3}'::int[] @> '{3}'::int[];
-- 左辺の要素にない要素が右辺にあるので、false
select '{1,2,3}'::int[] @> '{4}'::int[];
-- 左辺の要素にない要素が右辺にあるので、false
select '{1,2,3}'::int[] @> '{2,4}'::int[];
-- true
select '{1,2,3}'::int[] @> '{2,3}'::int[];
-- true
select '{1,2,3}'::int[] @> '{}'::int[];

-- true
SELECT ARRAY['会場A','会場B','会場C'] @> ARRAY['会場B'];
-- 左辺の要素にない要素が右辺にあるので、false
SELECT ARRAY['会場A','会場B','会場C'] @> ARRAY['会場B','会場D'];
-- true
SELECT ARRAY['会場A','会場B','会場C'] @> ARRAY['会場A','会場B'];
-- true (右辺が空配列）
SELECT ARRAY['会場A','会場B','会場C']::text[] @> ARRAY[]::text[];
```

## LPAD
### 説明
・書式： `lpad(値, 全体で何桁にするか, 桁が足りない部分を左埋めする際に使う値)`
・第一引数で渡した値を、第二引数で指定した桁数となるように、第三引数の値で左埋めします

### 実行例
以下は、'90001234'が10桁になるように、'0'で左埋めする例です
```sql
select lpad('90001234', 10, '0');
```
・実行結果
| lpad |
| :--- |
| 0090001234 |


## ROUND
### 説明
**・ROUND(X::NUMERIC)**
　　Xを四捨五入して、整数にします

**・ROUND(X::NUMERIC, Z)**
　　Xを四捨五入して、小数点以下Z桁の数値にします

※小数点以下Z桁になるように0で桁を埋めません、またDOUBLE PRECISION型では動作が異なるので頭に留めておきましょう。NUMERICで使う分には普通に四捨五入してくれるので気にしなくて大丈夫です。

### 実行例
#### ・ROUND(X::NUMERIC) ※Xはプラスの値
```sql
with round_results as (
    select null AS "ORIGINAL VALUE", round(null::NUMERIC) AS "round(X::NUMERIC)"
    UNION ALL
    select 1.4, round(1.4::numeric)
    UNION ALL
    select 1.5, round(1.5::numeric)
    UNION ALL
    select 1.6, round(1.6::numeric)
    UNION ALL
    select 2.4, round(2.4::numeric)
    UNION ALL
    select 2.5, round(2.5::numeric)
    UNION ALL
    select 2.6, round(2.6::numeric)
    UNION ALL
    select 3.4, round(3.4::numeric)
    UNION ALL
    select 3.5, round(3.5::numeric)
    UNION ALL
    select 3.6, round(3.6::numeric)
    UNION ALL
    select 4.4, round(4.4::numeric)
    UNION ALL
    select 4.5, round(4.5::numeric)
    UNION ALL
    select 4.6, round(4.6::numeric)
)
select * from round_results;
```
・実行結果
| ORIGINAL VALUE | round\(X::NUMERIC\) |
| :--- | :--- |
| NULL | NULL |
| 1.4 | 1 |
| 1.5 | 2 |
| 1.6 | 2 |
| 2.4 | 2 |
| 2.5 | 3 |
| 2.6 | 3 |
| 3.4 | 3 |
| 3.5 | 4 |
| 3.6 | 4 |
| 4.4 | 4 |
| 4.5 | 5 |
| 4.6 | 5 |

#### ・ROUND(X::NUMERIC) ※Xはマイナスの値
```sql
with round_results as (
    select null AS "ORIGINAL VALUE", round(null::NUMERIC) AS "round(X::NUMERIC)"
    UNION ALL
    select -1.4, round(-1.4::numeric)
    UNION ALL
    select -1.5, round(-1.5::numeric)
    UNION ALL
    select -1.6, round(-1.6::numeric)
    UNION ALL
    select -2.4, round(-2.4::numeric)
    UNION ALL
    select -2.5, round(-2.5::numeric)
    UNION ALL
    select -2.6, round(-2.6::numeric)
    UNION ALL
    select -3.4, round(-3.4::numeric)
    UNION ALL
    select -3.5, round(-3.5::numeric)
    UNION ALL
    select -3.6, round(-3.6::numeric)
    UNION ALL
    select -4.4, round(-4.4::numeric)
    UNION ALL
    select -4.5, round(-4.5::numeric)
    UNION ALL
    select -4.6, round(-4.6::numeric)
)
select * from round_results;
```
・実行結果
| ORIGINAL VALUE | round\(X::NUMERIC\) |
| :--- | :--- |
| NULL | NULL |
| -1.4 | -1 |
| -1.5 | -2 |
| -1.6 | -2 |
| -2.4 | -2 |
| -2.5 | -3 |
| -2.6 | -3 |
| -3.4 | -3 |
| -3.5 | -4 |
| -3.6 | -4 |
| -4.4 | -4 |
| -4.5 | -5 |
| -4.6 | -5 |

#### ・ROUND(X::NUMERIC, 2) ※Xはプラスの値
```sql
with round_results as (
    select null AS "ORIGINAL VALUE", round(null::NUMERIC, 2) AS "round(X::NUMERIC, 2)"
    UNION ALL
    select 1.444, round(1.444::numeric, 2)
    UNION ALL
    select 1.555, round(1.555::numeric, 2)
    UNION ALL
    select 1.666, round(1.666::numeric, 2)
    UNION ALL
    select 2.444, round(2.444::numeric, 2)
    UNION ALL
    select 2.555, round(2.555::numeric, 2)
    UNION ALL
    select 2.666, round(2.666::numeric, 2)
    UNION ALL
    select 3.444, round(3.444::numeric, 2)
    UNION ALL
    select 3.555, round(3.555::numeric, 2)
    UNION ALL
    select 3.666, round(3.666::numeric, 2)
    UNION ALL
    select 4.444, round(4.444::numeric, 2)
    UNION ALL
    select 4.555, round(4.555::numeric, 2)
    UNION ALL
    select 4.666, round(4.666::numeric, 2)
)
select * from round_results;
```
・実行結果
| ORIGINAL VALUE | round\(X::NUMERIC, 2\) |
| :--- | :--- |
| NULL | NULL |
| 1.444 | 1.44 |
| 1.555 | 1.56 |
| 1.666 | 1.67 |
| 2.444 | 2.44 |
| 2.555 | 2.56 |
| 2.666 | 2.67 |
| 3.444 | 3.44 |
| 3.555 | 3.56 |
| 3.666 | 3.67 |
| 4.444 | 4.44 |
| 4.555 | 4.56 |
| 4.666 | 4.67 |

#### ・ROUND(X::NUMERIC, 2) ※Xはマイナスの値
```sql
with round_results as (
    select null AS "ORIGINAL VALUE", round(null::NUMERIC, 2) AS "round(X::NUMERIC, 2)"
    UNION ALL
    select -1.444, round(-1.444::numeric, 2)
    UNION ALL
    select -1.555, round(-1.555::numeric, 2)
    UNION ALL
    select -1.666, round(-1.666::numeric, 2)
    UNION ALL
    select -2.444, round(-2.444::numeric, 2)
    UNION ALL
    select -2.555, round(-2.555::numeric, 2)
    UNION ALL
    select -2.666, round(-2.666::numeric, 2)
    UNION ALL
    select -3.444, round(-3.444::numeric, 2)
    UNION ALL
    select -3.555, round(-3.555::numeric, 2)
    UNION ALL
    select -3.666, round(-3.666::numeric, 2)
    UNION ALL
    select -4.444, round(-4.444::numeric, 2)
    UNION ALL
    select -4.555, round(-4.555::numeric, 2)
    UNION ALL
    select -4.666, round(-4.666::numeric, 2)
)
select * from round_results;
```
・実行結果
| ORIGINAL VALUE | round\(X::NUMERIC, 2\) |
| :--- | :--- |
| NULL | NULL |
| -1.444 | -1.44 |
| -1.555 | -1.56 |
| -1.666 | -1.67 |
| -2.444 | -2.44 |
| -2.555 | -2.56 |
| -2.666 | -2.67 |
| -3.444 | -3.44 |
| -3.555 | -3.56 |
| -3.666 | -3.67 |
| -4.444 | -4.44 |
| -4.555 | -4.56 |
| -4.666 | -4.67 |



## 最後に
いろんなSQLがありますよねー、、日々勉強になります；