SQL１発で「数字を組み合わせて10にする」演算を解く方法

# 概要
いわゆる切符に書かれている四桁の数字を組み合わせて四則演算で10にする、という演算をSQLで解く方法について解説する。
SQL１発で解く方法もあるが非常に冗長になってしまう関係上
・テーブルにあらかじめ演算子と数列を格納する方法
も併記する。

## ざっくり纏め

・4つの数字の組み合わせは(4P,**24通り**)しかない
・4つの数字の計算で用いる演算子は3個であり、3個の演算子(+-/x)の組み合わせは(3C, **64通り** ）しかない
・以上の**4つの数字と3つの演算子**を組み合わせた数式は最大24x64= **1536通り** しかない
・その**1536通りの中から合計が10になる式**を抽出すればよい

## 今回の前提

・今回の計算すると10になる数列は　3 7 3 1　を使用するとする。（特にこの数字でなくてもこの記事にあるSQLは有効）
・演算子は　+ - / * の四つのみ使用するものとする。（これ以外の演算子を追加しても動作はするが、CASE文を適宜編集する必要あり）

## 実際にSQLで書く

実際にSQLで書く場合のSQL文について記述する。
SQL一発版は冗長になるため後半に記述する。
なお、実行環境のDBはSQLITE3だが、おそらくMySQLなどでも動作すると思われる。

### 事前にテーブルを作成する方法

・使用する演算子をまとめた演算子テーブル
・使用する数列をまとめた数列テーブル
を用意すると若干SQL文が簡単になる。


まず演算子テーブルの作成。
四則演算のみなので+ - / * を登録。

```SQL:
CREATE TABLE Operator(
    C CHAR NOT NULL UNIQUE
);
INSERT INTO Operator(C)
VALUES
       ('+'),('-'),('/'),('*')
```

次に数列テーブルを作成する。

```SQL:
CREATE TABLE NUM(
    id INTEGER NOT NULL PRIMARY KEY,
    N as TINYINTEGER
);
INSERT INTO NUM(N)
VALUES(3),(3),(7),(1)
```

準備ができたのでSQLを実行する。

```SQL:
SELECT
    C2.L as L,
    CASe C2.A3
     when "-" then C2.ABC - C2.B4
     when "+" then C2.ABC + C2.B4
     when "/" then C2.ABC / C2.B4
     when "*" then C2.ABC * C2.B4
    else 99
end as ABCD
FROM(
    Select
        C1.L as L,
        C1.AB as AB,
        C1.A3 as A3,
        C1.B4 as B4,
        CASe C1.A2
         when "-" then C1.AB - C1.B3
         when "+" then C1.AB + C1.B3
         when "/" then C1.AB / C1.B3
         when "*" then C1.AB * C1.B3
        else 99
        end as ABC
    FROM(
        Select
            N.B1||OP.A1||N.B2||OP.A2||N.B3||OP.A3||N.B4 as L,
            OP.A2 as A2,
            OP.A3 as A3,
            N.B3 as B3,
            N.B4 as B4,
            CASE OP.A1
             when "-" then N.B1 - N.B2
             when "+" then N.B1 + N.B2
             when "/" then N.B1 / N.B2
             when "*" then N.B1 * N.B2
            else 99
            end as AB
        From(
            SELECT
              A.C as A1,
              B.C as A2,
              C.C as A3
            From Operator as A
            Left join Operator as B
            Left join Operator as C
        )as OP
        Left join(
            SELECT
            cast(A.N as real) as B1,
            cast(B.N as real) as B2,
            cast(C.N as real) as B3,
            cast(D.N as real) as B4
            From NUM as A
            Left join NUM as B On A.id <> B.id
            Left join NUM as C On A.id <> C.id And B.id <> C.id
            Left join NUM as D On A.id <> D.id And B.id <> D.id and C.id <> D.id
        )as N
    ) as C1
) as C2
WHERE
 ABCD = 10.0
```

結果
（式は演算子の計算順ではなく単に左から順に演算しているので適宜括弧をつけて読み替えること）

| L | ABCD | 
|:-----------|------------:|
| 7.0/3.0+1.0*3.0      | 10      |
| 7.0/3.0+1.0*3.0      | 10      |

やったぜ。

### 解説

・同じテーブルをLEFT JOINで右結合していくと、特に結合条件を指定しなければ自動的に「組み合わせ(C)」を指定できる。
・同じテーブルをLEFT JOINで結合し、その際に右側のテーブルに左側のid **以外** を結合すると「順列(P)」を指定できる
・整数で演算すると割り算で端数が切り捨てられるなどするので、あらかじめ少数にしておく（おそらく今回の条件では浮動小数によるずれは気にしないでよいものと考えられる）
・今回はCASE文で演算子を判別して計算を行っているが、実用的には数字と演算子を結合して文字列として出力し、Excelなどで一括で計算したほうがわかりやすい（かもしれない）


## SQL一発で記述する方法

先ほどテーブルを作成した部分はSELECTとUNION SELECTとを組み合わせることでSQL文1クエリ内で完結させることができる。
ただし、同じ内容の仮テーブルを複数宣言する必要があるのでかなり冗長になる…

```SQL:
SELECT
    C2.L as L,
    CASe C2.A3
     when "-" then C2.ABC - C2.B4
     when "+" then C2.ABC + C2.B4
     when "/" then C2.ABC / C2.B4
     when "*" then C2.ABC * C2.B4
    else 99
end as ABCD
FROM(
    Select
        C1.L as L,
        C1.AB as AB,
        C1.A3 as A3,
        C1.B4 as B4,
        CASe C1.A2
         when "-" then C1.AB - C1.B3
         when "+" then C1.AB + C1.B3
         when "/" then C1.AB / C1.B3
         when "*" then C1.AB * C1.B3
        else 99
        end as ABC
    FROM(
        Select
            N.B1||OP.A1||N.B2||OP.A2||N.B3||OP.A3||N.B4 as L,
            OP.A2 as A2,
            OP.A3 as A3,
            N.B3 as B3,
            N.B4 as B4,
            CASE OP.A1
             when "-" then N.B1 - N.B2
             when "+" then N.B1 + N.B2
             when "/" then N.B1 / N.B2
             when "*" then N.B1 * N.B2
            else 99
            end as AB
        From(
            SELECT
              A.C as A1,
              B.C as A2,
              C.C as A3
            From (
                SELECT '+' as C
                UNION SELECT '/' as C
                UNION SELECT '*' as C
                UNION SELECT '-' as C
             ) as A
            Left join (
                SELECT '+' as C
                UNION SELECT '/' as C
                UNION SELECT '*' as C
                UNION SELECT '-' as C
             ) as B
            Left join (
                SELECT '+' as C
                UNION SELECT '/' as C
                UNION SELECT '*' as C
                UNION SELECT '-' as C
             ) as C
        )as OP
        Left join(
            SELECT
            cast(A.N as real) as B1,
            cast(B.N as real) as B2,
            cast(C.N as real) as B3,
            cast(D.N as real) as B4
            From (
                SELECT 1 as id ,3 as N
                UNION  SELECT 2 as id ,3 as N
                UNION  SELECT 3 as id ,7 as N
                UNION  SELECT 4 as id ,1 as N
             ) as A
            Left join (
                SELECT 1 as id ,3 as N
                UNION SELECT 2 as id ,3 as N
                UNION SELECT 3 as id ,7 as N
                UNION SELECT 4 as id ,1 as N
             ) as B On A.id <> B.id
            Left join (
                SELECT 1 as id ,3 as N
                UNION SELECT 2 as id ,3 as N
                UNION SELECT 3 as id ,7 as N
                UNION SELECT 4 as id ,1 as N
             ) as C On A.id <> C.id And B.id <> C.id
            Left join (
                SELECT 1 as id ,3 as N
                UNION SELECT 2 as id ,3 as N
                UNION SELECT 3 as id ,7 as N
                UNION SELECT 4 as id ,1 as N
             ) as D On A.id <> D.id And B.id <> D.id and C.id <> D.id
        )as N
    ) as C1
) as C2
WHERE
 ABCD = 10.0

```

結果

| L | ABCD | 
|:-----------|------------:|
| 7.0/3.0+1.0*3.0      | 10      |
| 7.0/3.0+1.0*3.0      | 10      |


以上。