【Golang】SQLite3 でテーブル名一覧と DB の総レコード数を取得する

> Go 言語（以下 Golang）の [database/sql 標準パッケージ](https://pkg.go.dev/database/sql)と、SQLite3 ドライバで DB を作成したものの、動的に作成されたテーブルのため、**作成されたテーブル名の一覧を取得する必要がある**。
>
> しかも、何件登録されているか、**DB 内の総レコード数を後からでも確認する方法**がないものか。

Go 1.18 以降、[ドライバーは、もちろん天下の mattn さんの "go-sqlite3"](https://github.com/mattn/go-sqlite3)。

[「"golang" "sqlite3" テーブル名 一覧 取得」でググって](https://www.google.com/search?q=%22golang%22+%22sqlite3%22+%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E5%90%8D+%E4%B8%80%E8%A6%A7+%E5%8F%96%E5%BE%97)も、SQLite コマンドの `.table` で確認する方法しか出てこなかったり、[「"golang" "sqlite3" 総レコード数」でググって](https://www.google.com/search?q=%22golang%22+%22sqlite3%22+%E7%B7%8F%E3%83%AC%E3%82%B3%E3%83%BC%E3%83%89%E6%95%B0)も、データベース内の全件のレコード数が欲しいのに `COUNT()` を使った特定のテーブルのレコード数しか情報がなかったので、自分のググラビリティとして。

## TL; DR <sub><sup>（今北産業）</sup></sub>

1. SQLite3 では <strong>`count()` を呼び出すたびにテーブルのフルスキャンを行う</strong>ため遅いです。
1. SQLite3 は[<strong>仕様によりテーブルのレコード数を保持していません</strong>](https://qiita.com/KEINOS/questions/2601695188ca8d78c219)。キャッシュもしません。
1. そのため、**レコード数を保持するカウント用のテーブルを作成し更新するのが現実的**です。
    * 更新するパターン
        1. すべてのレコードを挿入し終わった後に、カウントテーブルを更新する。
        1. `INSERT` `DELETE` に[トリガー](https://www.sqlite.org/lang_createtrigger.html)を設定しておき、随時カウントテーブルを増減更新する。（全体のパフォーマンスに影響がでます）
    * 更新方法
        1. `WHERE type='table'` でテーブル名一覧を取得する。
        1. 各々のテーブルで `SELECT count()` によりレコード数を取得する。

- 参考文献: [【Golang】SQLite3 の DB で総レコード数をコードから取得したい](https://qiita.com/KEINOS/questions/2601695188ca8d78c219) | Q&A @ Qiita 

## TS; DR <sub><sup>（サンプル）</sup></sub>

1. テーブル名一覧を取得し、各々のレコード数を取得する SQL クエリ (for SQLite3)
    ```sql:テーブル名一覧を取得するSQLクエリ
    SELECT name FROM sqlite_master WHERE type = 'table';
    ```
    ```sql:各々のテーブルのレコード数を取得するSQLクエリ
    SELECT 'SELECT count(*), "' || name || '" FROM ' || name || ';'  FROM sqlite_master WHERE type = 'table';
    ```
2. Golang でテーブル名一覧を取得するサンプル。
    ```go
    // GetTable は db のテーブル名一覧を文字列のスライスで返します。
    func GetTable(db *sql.DB) ([]string, error){
        result := []string{}
        q := `SELECT name FROM sqlite_master WHERE type = 'table';`

        res, err := db.Query(q)
        if err != nil {
            return nil, errors.Wrap(err, "failed to query database")
        }
    
        var table string

        for res.Next() {
            if err := res.Scan(&table); err != nil {
                return nil, errors.Wrap(err, "failed to scan table name")
            }

            result = append(result, table)
        }

        sort.Slice(result, func(i, j int) bool {
            return result[i] < result[j]
        })

        return result, nil
    }
    ```
3. Golang でデータベース内（全テーブル）の総レコード数を取得するサンプル。

    ```go
    func GetTotalRecords(db *sql.DB) int {
        talbes, err := Tables(db)
        if err != nil {
            return -1
        }

        const queryTPL = `SELECT COUNT(*) FROM %s;`

        result := int(0)

        for _, table := range talbes {
            queryTMP := fmt.Sprintf(queryTPL, table)

            // ここはPrepareでステートメント化した方が速い
            res, err := k.db.Query(queryTMP)
            if err != nil {
                return -1
            }

            if res.Next() {
                var count int

                res.Scan(&count)
                result += count
            }
        }

        return result
    }
    ```