PythonとSQLite3と再帰SQLで最新の会員番号の取得

概要

[Next版]
カードの紛失や再発行などでカードが再発行された場合は新規に番号が追加され、
切替前カード番号のNextのカラムに新規のカード番号が入力されるものとします。
上記TABLEを再帰的に(再帰SQLで)処理して最新の会員番号を取得。

[Prev版]
カードの紛失や再発行などでカードが再発行された場合は新規に番号が追加され、
切替後カード番号のPrevのカラムに前回のカード番号が入力されるものとします。
上記TABLEを再帰的に(再帰SQLで)処理して最新の会員番号を取得。

下部に記載のLinkを参考にして作成。


基盤データ[Next版]

CardNo NextCardNo
1001 1002
1002 1003
1003 1004
1004 1005
1005 None
2001 2002
2002 None
3001 None

取得データ[Next版]

CardNo NextCardNo
1001 1005
1002 1005
1003 1005
1004 1005
1005 1005
2001 2002
2002 2002
3001 3001

Pythonコード[Next版]

import pprint
import sqlite3


def main():

    print("PRESS ENTER KEY TO BOOT")
    input()

    con = None
    cur = None

    try:
        con = sqlite3.connect(":memory:")
        cur = con.cursor()

        create_sql = """
            CREATE TABLE CardMember (
                CardNo     INTEGER
               ,NextCardNo INTEGER
            )
        """
        cur.execute(create_sql)

        cur.execute("INSERT INTO CardMember VALUES(1001, 1002)")
        cur.execute("INSERT INTO CardMember VALUES(1002, 1003)")
        cur.execute("INSERT INTO CardMember VALUES(1003, 1004)")
        cur.execute("INSERT INTO CardMember VALUES(1004, 1005)")
        cur.execute("INSERT INTO CardMember VALUES(1005, null)")

        cur.execute("INSERT INTO CardMember VALUES(2001, 2002)")
        cur.execute("INSERT INTO CardMember VALUES(2002, null)")

        cur.execute("INSERT INTO CardMember VALUES(3001, null)")

        # create_sql = """CREATE TABLE CardMemberInterimResult01 AS """
        # select_sql = """
        #     WITH RECURSIVE Recursions(CardNo, NextCardNo) AS (
        #         SELECT   CardMember.CardNo
        #                 ,CardMember.CardNo
        #         FROM     CardMember
        #         UNION
        #         SELECT   CardMember.CardNo
        #                 ,Recursions.NextCardNo
        #         FROM     CardMember
        #                 ,Recursions
        #         WHERE    CardMember.NextCardNo = Recursions.CardNo
        #     )
        #     SELECT   CardNo
        #             ,NextCardNo
        #     FROM     Recursions
        # """
        # cur.execute(create_sql + select_sql)

        # create_sql = """CREATE TABLE CardMemberInterimResult02 AS """
        # select_sql = """
        #     WITH RECURSIVE Recursions(Depth, CardNo, NextCardNo) AS (  --change
        #         SELECT   0                                             --change
        #                 ,CardMember.CardNo                             --insert comma
        #                 ,CardMember.CardNo
        #         FROM     CardMember
        #         UNION
        #         SELECT   Recursions.Depth + 1                          --change
        #                 ,CardMember.CardNo                             --insert comma
        #                 ,Recursions.NextCardNo
        #         FROM     CardMember
        #                 ,Recursions
        #         WHERE    CardMember.NextCardNo = Recursions.CardNo
        #     )
        #     SELECT   Depth                                             --change
        #             ,CardNo                                            --insert comma
        #             ,NextCardNo
        #     FROM     Recursions
        # """
        # cur.execute(create_sql + select_sql)

        # create_sql = """CREATE TABLE CardMemberInterimResult03 AS """
        # select_sql = """
        #     WITH RECURSIVE Recursions(Depth, CardNo, NextCardNo) AS (
        #         SELECT   0
        #                 ,CardMember.CardNo
        #                 ,CardMember.CardNo
        #         FROM     CardMember
        #         UNION
        #         SELECT   Recursions.Depth + 1
        #                 ,CardMember.CardNo
        #                 ,Recursions.NextCardNo
        #         FROM     CardMember
        #                 ,Recursions
        #         WHERE    CardMember.NextCardNo = Recursions.CardNo
        #     )
        #     SELECT   Depth
        #             ,CardNo
        #             ,NextCardNo                                                 --insert
        #     FROM (                                                              --insert
        #         SELECT   *                                                      --insert
        #                 ,max(Depth) over(partition by CardNo) as DepthMax       --insert
        #         FROM     Recursions                                             --insert space
        #     )                                                                   --insert
        #     WHERE    Depth = DepthMax                                           --insert
        #     ORDER BY Depth                                                      --insert
        #             ,CardNo                                                     --insert
        #             ,NextCardNo                                                 --insert
        # """
        # cur.execute(create_sql + select_sql)

        create_sql = """CREATE TABLE CardMemberFinallyResult AS """
        select_sql = """
            WITH RECURSIVE Recursions(Depth, CardNo, NextCardNo) AS (
                SELECT   0
                        ,CardMember.CardNo
                        ,CardMember.CardNo
                FROM     CardMember
                UNION
                SELECT   Recursions.Depth + 1
                        ,CardMember.CardNo
                        ,Recursions.NextCardNo
                FROM     CardMember
                        ,Recursions
                WHERE    CardMember.NextCardNo = Recursions.CardNo
            )
            SELECT --Depth                                             --delete
                     CardNo                                            --delete comma
                    ,NextCardNo
            FROM (
                SELECT   *
                        ,max(Depth) over(partition by CardNo) as DepthMax
                FROM     Recursions
            )
            WHERE    Depth = DepthMax
            ORDER BY CardNo                                            --change
                    ,NextCardNo                                        --change
                    ,Depth                                             --change
        """
        cur.execute(create_sql + select_sql)

        print("sqlite_master")
        pprint.pprint(tuple([row[0] for row in cur.execute("SELECT name FROM sqlite_master WHERE type='table'")]))
        print()

        def pprint_pprint_table(cur, table):
            print(f"{table}")
            pprint.pprint(tuple([row[1] for row in cur.execute(f"PRAGMA table_info({table})")]))
            pprint.pprint(tuple(cur.execute(f"SELECT * FROM {table}")))
            print()

        pprint_pprint_table(cur, "CardMember")
        # pprint_pprint_table(cur, "CardMemberInterimResult01")
        # pprint_pprint_table(cur, "CardMemberInterimResult02")
        # pprint_pprint_table(cur, "CardMemberInterimResult03")
        pprint_pprint_table(cur, "CardMemberFinallyResult")

    except Exception as e:
        print(e)
    finally:
        cur = cur.close() if cur is not None else None
        con = con.close() if con is not None else None

    print()
    print("PRESS ENTER KEY TO EXIT")
    input()


if __name__ == "__main__":
    main()
中間結果

行頭でコメントアウトしている部分を、
行頭のコメントアウトをアンコメントしてコードを有効化すると、
以下の途中経過の情報を出力可能です。

CardMemberInterimResult01
再帰SQLで総当たり(?)した情報を出力。

CardMemberInterimResult02
再帰深度(再帰回数)を保持するカラムを追加。

CardMemberInterimResult03
Nameごとの最大深度のレコードのみを取得。

終結

CardMemberFinallyResult
再帰深度のカラムを出力対象外に変更。


基盤データ[Prev版]

CardNo PrevCardNo
1001 None
1002 1001
1003 1002
1004 1003
1005 1004
2001 None
2002 2001
3001 None

取得データ[Prev版]

CardNo PrevCardNo
1005 1001
1005 1002
1005 1003
1005 1004
1005 1005
2002 2001
2002 2002
3001 3001

Pythonコード[Prev版]

import pprint
import sqlite3


def main():

    print("PRESS ENTER KEY TO BOOT")
    input()

    con = None
    cur = None

    try:
        con = sqlite3.connect(":memory:")
        cur = con.cursor()

        create_sql = """
            CREATE TABLE CardMember (
                CardNo     INTEGER
               ,PrevCardNo INTEGER
            )
        """
        cur.execute(create_sql)

        cur.execute("INSERT INTO CardMember VALUES(1001, null)")
        cur.execute("INSERT INTO CardMember VALUES(1002, 1001)")
        cur.execute("INSERT INTO CardMember VALUES(1003, 1002)")
        cur.execute("INSERT INTO CardMember VALUES(1004, 1003)")
        cur.execute("INSERT INTO CardMember VALUES(1005, 1004)")

        cur.execute("INSERT INTO CardMember VALUES(2001, null)")
        cur.execute("INSERT INTO CardMember VALUES(2002, 2001)")

        cur.execute("INSERT INTO CardMember VALUES(3001, null)")

        # create_sql = """CREATE TABLE CardMemberInterimResult01 AS """
        # select_sql = """
        #     WITH RECURSIVE Recursions(CardNo, PrevCardNo) AS (
        #         SELECT   CardMember.CardNo
        #                 ,CardMember.CardNo
        #         FROM     CardMember
        #         UNION
        #         SELECT   CardMember.CardNo
        #                 ,Recursions.PrevCardNo
        #         FROM     CardMember
        #                 ,Recursions
        #         WHERE    CardMember.PrevCardNo = Recursions.CardNo
        #     )
        #     SELECT   CardNo
        #             ,PrevCardNo
        #     FROM     Recursions
        # """
        # cur.execute(create_sql + select_sql)

        # create_sql = """CREATE TABLE CardMemberInterimResult02 AS """
        # select_sql = """
        #     WITH RECURSIVE Recursions(Depth, CardNo, PrevCardNo) AS (  --change
        #         SELECT   0                                             --change
        #                 ,CardMember.CardNo                             --insert comma
        #                 ,CardMember.CardNo
        #         FROM     CardMember
        #         UNION
        #         SELECT   Recursions.Depth + 1                          --change
        #                 ,CardMember.CardNo                             --insert comma
        #                 ,Recursions.PrevCardNo
        #         FROM     CardMember
        #                 ,Recursions
        #         WHERE    CardMember.PrevCardNo = Recursions.CardNo
        #     )
        #     SELECT   Depth                                             --change
        #             ,CardNo                                            --insert comma
        #             ,PrevCardNo
        #     FROM     Recursions
        # """
        # cur.execute(create_sql + select_sql)

        # create_sql = """CREATE TABLE CardMemberInterimResult03 AS """
        # select_sql = """
        #     WITH RECURSIVE Recursions(Depth, CardNo, PrevCardNo) AS (
        #         SELECT   0
        #                 ,CardMember.CardNo
        #                 ,CardMember.CardNo
        #         FROM     CardMember
        #         UNION
        #         SELECT   Recursions.Depth + 1
        #                 ,CardMember.CardNo
        #                 ,Recursions.PrevCardNo
        #         FROM     CardMember
        #                 ,Recursions
        #         WHERE    CardMember.PrevCardNo = Recursions.CardNo
        #     )
        #     SELECT   Depth
        #             ,CardNo
        #             ,PrevCardNo                                                 --insert
        #     FROM (                                                              --insert
        #         SELECT   *                                                      --insert
        #                 ,max(Depth) over(partition by PrevCardNo) as DepthMax   --insert
        #         FROM     Recursions                                             --insert space
        #     )                                                                   --insert
        #     WHERE    Depth = DepthMax                                           --insert
        #     ORDER BY Depth                                                      --insert
        #             ,CardNo                                                     --insert
        #             ,PrevCardNo                                                 --insert
        # """
        # cur.execute(create_sql + select_sql)

        create_sql = """CREATE TABLE CardMemberFinallyResult AS """
        select_sql = """
            WITH RECURSIVE Recursions(Depth, CardNo, PrevCardNo) AS (
                SELECT   0
                        ,CardMember.CardNo
                        ,CardMember.CardNo
                FROM     CardMember
                UNION
                SELECT   Recursions.Depth + 1
                        ,CardMember.CardNo
                        ,Recursions.PrevCardNo
                FROM     CardMember
                        ,Recursions
                WHERE    CardMember.PrevCardNo = Recursions.CardNo
            )
            SELECT --Depth                                             --delete
                     CardNo                                            --delete comma
                    ,PrevCardNo
            FROM (
                SELECT   *
                        ,max(Depth) over(partition by PrevCardNo) as DepthMax
                FROM     Recursions
            )
            WHERE    Depth = DepthMax
            ORDER BY CardNo                                            --change
                    ,PrevCardNo                                        --change
                    ,Depth                                             --change
        """
        cur.execute(create_sql + select_sql)

        print("sqlite_master")
        pprint.pprint(tuple([row[0] for row in cur.execute("SELECT name FROM sqlite_master WHERE type='table'")]))
        print()

        def pprint_pprint_table(cur, table):
            print(f"{table}")
            pprint.pprint(tuple([row[1] for row in cur.execute(f"PRAGMA table_info({table})")]))
            pprint.pprint(tuple(cur.execute(f"SELECT * FROM {table}")))
            print()

        pprint_pprint_table(cur, "CardMember")
        # pprint_pprint_table(cur, "CardMemberInterimResult01")
        # pprint_pprint_table(cur, "CardMemberInterimResult02")
        # pprint_pprint_table(cur, "CardMemberInterimResult03")
        pprint_pprint_table(cur, "CardMemberFinallyResult")

    except Exception as e:
        print(e)
    finally:
        cur = cur.close() if cur is not None else None
        con = con.close() if con is not None else None

    print()
    print("PRESS ENTER KEY TO EXIT")
    input()


if __name__ == "__main__":
    main()
中間結果

コメントアウトしている部分、
アンコメントでコードを有効化すると、
以下の途中経過の情報を出力可能です。

CardMemberInterimResult01
再帰SQLで総当たり(?)した情報を出力。

CardMemberInterimResult02
再帰深度(再帰回数)を保持するカラムを追加。

CardMemberInterimResult03
Nameごとの最大深度のレコードのみを取得。

終結

CardMemberFinallyResult
再帰深度のカラムを出力対象外に変更。


SQL再帰的に最新の会員番号をもとめて一つの会員として売上を集計したい。
https://teratail.com/questions/cae1gknhvu5uiq

再帰SQL -図解-
https://qiita.com/Shoyu_N/items/f1786f99545fa5053b75

そろそろSQLのウィンドウ関数を理解したい
https://qiita.com/w-sato-ist/items/63600a3ab84aad38e879
https://qiita.com/w-sato-ist/items/75bf6bb60ccafda82840
https://qiita.com/w-sato-ist/items/86aab7e7722ff9454ec0

SQL便利な関数
https://avinton.com/academy/sql-intermediate/

あとがき

ノンプログラマーの素人が記述をしたコードです。
狭い利用範囲と少ない利用頻度での確認ですので、
記載内容に間違いや勘違いがあるかもしれません。
上記内容を参照の際は自己責任でお願い致します。