sql >> データベース >  >> RDS >> PostgreSQL

OracleからPostgreSQLへ:START WITH / CONNECT BY

    そして今、OracleからPostgreSQLシリーズへの移行の2番目の記事に到達しました。今回は、START WITH/CONNECT BYを見ていきます。 構築します。

    Oracleでは、START WITH/CONNECT BY 特定の番兵行から始まる単一リンクリスト構造を作成するために使用されます。リンクリストはツリーの形式をとることができ、バランスの要件はありません。

    説明のために、クエリから始めて、テーブルに5行あると仮定します。

    SELECT * FROM person;
     last_name  | first_name | id | parent_id
    ------------+------------+----+-----------
     Dunstan    | Andrew     |  1 |    (null)
     Roybal     | Kirk       |  2 |         1
     Riggs      | Simon      |  3 |         1
     Eisentraut | Peter      |  4 |         1
     Thomas     | Shaun      |  5 |         3
    (5 rows)

    Oracle構文を使用したテーブルの階層クエリは次のとおりです。

    select id, parent_id
    from person
    start with parent_id IS NULL
    connect by prior id = parent_id;
     id | parent_id
    ----+-----------
      1 |    (null)
      4 |         1
      3 |         1
      2 |         1
      5 |         3

    そしてここでもPostgreSQLを使用しています。

    WITH RECURSIVE a AS (
    SELECT id, parent_id
    FROM person
    WHERE parent_id IS NULL
    UNION ALL
    SELECT d.id, d.parent_id
    FROM person d
    JOIN a ON a.id = d.parent_id )
    SELECT id, parent_id FROM a;
     id | parent_id
    ----+-----------
      1 |    (null)
      4 |         1
      3 |         1
      2 |         1
      5 |         3
    (5 rows)

    このクエリは多くのPostgreSQL機能を利用しているので、ゆっくりと見ていきましょう。

    WITH RECURSIVE

    これは「共通テーブル式」(CTE)です。これは、同じトランザクションだけでなく、同じステートメントで実行される一連のクエリを定義します。括弧で囲まれた式はいくつでもあり、最後のステートメントがあります。この使用法では、1つだけ必要です。そのステートメントをRECURSIVEであると宣言する 、行が返されなくなるまで繰り返し実行されます。

    SELECT
    UNION ALL
    SELECT

    これは、再帰クエリの規定のフレーズです。これは、開始点と再帰アルゴリズムを区別するための方法としてドキュメントで定義されています。 Oracleの用語では、これらはCONNECTBY句に結合されたSTARTWITH句と考えることができます。

    JOIN a ON a.id = d.parent_id

    これは、前の行データを次の反復に提供するCTEステートメントへの自己結合です。

    これがどのように機能するかを説明するために、クエリに反復インジケーターを追加しましょう。

    WITH RECURSIVE a AS (
    SELECT id, parent_id, 1::integer recursion_level
    FROM person
    WHERE parent_id IS NULL
    UNION ALL
    SELECT d.id, d.parent_id, a.recursion_level +1
    FROM person d
    JOIN a ON a.id = d.parent_id )
    SELECT * FROM a;
    
     id | parent_id | recursion_level
    ----+-----------+-----------------
      1 |    (null) |               1
      4 |         1 |               2
      3 |         1 |               2
      2 |         1 |               2
      5 |         3 |               3
    (5 rows)

    再帰レベルインジケーターを値で初期化します。返される行では、最初の再帰レベルが1回だけ発生することに注意してください。これは、最初の句が1回だけ実行されるためです。

    2番目の節は、反復的な魔法が発生する場所です。ここでは、現在の行データとともに、前の行データを表示できます。これにより、再帰的な計算を実行できます。

    Simon Riggsが、この機能をグラフデータベースの設計に使用する方法についての非常に優れたビデオを公開しています。非常に有益なので、ご覧になる必要があります。

    このクエリが循環状態につながる可能性があることに気付いたかもしれません。それは正しいです。この無限の再帰を防ぐために、2番目のクエリに制限句を追加するのは開発者の責任です。たとえば、あきらめる前に4レベルだけ繰り返します。

    WITH RECURSIVE a AS (
    SELECT id, parent_id, 1::integer recursion_level  --<-- initialize it here
    FROM person
    WHERE parent_id IS NULL
    UNION ALL
    SELECT d.id, d.parent_id, a.recursion_level +1    --<-- iteration increment
    FROM person d
    JOIN a ON a.id = d.parent_id
    WHERE d.recursion_level <= 4  --<-- bail out here
    ) SELECT * FROM a;

    列名とデータ型は、最初の句によって決定されます。この例では、再帰レベルにキャスト演算子を使用していることに注意してください。非常に深いグラフでは、このデータ型は1::bigint recursion_levelとして定義することもできます。 。

    このグラフは、小さなシェルスクリプトとgraphvizユーティリティを使用して非常に簡単に視覚化できます。

    #!/bin/bash -
    #===============================================================================
    #
    #          FILE: pggraph
    #
    #         USAGE: ./pggraph
    #
    #   DESCRIPTION:
    #
    #       OPTIONS: ---
    #  REQUIREMENTS: ---
    #          BUGS: ---
    #         NOTES: ---
    #        AUTHOR: Kirk Roybal (), [email protected]
    #  ORGANIZATION:
    #       CREATED: 04/21/2020 14:09
    #      REVISION:  ---
    #===============================================================================
    
    set -o nounset                              # Treat unset variables as an error
    
    dbhost=localhost
    dbport=5432
    dbuser=$USER
    dbname=$USER
    ScriptVersion="1.0"
    output=$(basename $0).dot
    
    #===  FUNCTION  ================================================================
    #         NAME:  usage
    #  DESCRIPTION:  Display usage information.
    #===============================================================================
    function usage ()
    {
    cat <<- EOT
    
      Usage :  ${0##/*/} [options] [--]
    
      Options:
      -h|host     name Database Host Name default:localhost
      -n|name     name Database Name      default:$USER
      -o|output   file Output file        default:$output.dot
      -p|port   number TCP/IP port        default:5432
      -u|user     name User name          default:$USER
      -v|version    Display script version
    
    EOT
    }    # ----------  end of function usage  ----------
    
    #-----------------------------------------------------------------------
    #  Handle command line arguments
    #-----------------------------------------------------------------------
    
    while getopts ":dh:n:o:p:u:v" opt
    do
      case $opt in
    
        d|debug    )  set -x ;;
    
        h|host     )  dbhost="$OPTARG" ;;
    
        n|name     )  dbname="$OPTARG" ;;
    
        o|output   )  output="$OPTARG" ;;
    
        p|port     )  dbport=$OPTARG ;;
    
        u|user     )  dbuser=$OPTARG ;;
    
        v|version  )  echo "$0 -- Version $ScriptVersion"; exit 0   ;;
    
        \? )  echo -e "\n  Option does not exist : $OPTARG\n"
              usage; exit 1   ;;
    
      esac    # --- end of case ---
    done
    shift $(($OPTIND-1))
    
    [[ -f "$output" ]] && rm "$output"
    
    tee "$output" <<eof< span="">
    digraph g {
        node [shape=rectangle]
        rankdir=LR
    EOF
    
    psql -h $dbhost -U $dbuser -d $dbname -p $dbport -qtAf cte.sql |
        sed -e 's/^/node/' -e 's/.*(null)|/node/' -e 's/^/\t/' -e 's/|[[:digit:]]*$//' |
        sed -e 's/|/ -> node/' | tee -a "$output"
    
    tee -a "$output" <<eof< span="">
    }
    EOF
    
    dot -Tpng "$output" > "${output/dot/png}"
    
    [[ -f "$output" ]] && rm "$output"
    
    open "${output/dot/png}"</eof<></eof<>

    このスクリプトでは、cte.sql

    というファイルにこのSQLステートメントが必要です。
    WITH RECURSIVE a AS (
    SELECT id, parent_id, 1::integer recursion_level
    FROM person
    WHERE parent_id IS NULL
    UNION ALL
    SELECT d.id, d.parent_id, a.recursion_level +1
    FROM person d
    JOIN a ON a.id = d.parent_id )
    SELECT parent_id, id, recursion_level FROM a;

    次に、次のように呼び出します:

    chmod +x pggraph
    ./pggraph

    そして、結果のグラフが表示されます。

    INSERT INTO person (id, parent_id) VALUES (6,2);

    ユーティリティを再度実行して、有向グラフへの即時の変更を確認します。

    さて、それは今ではそれほど難しくありませんでしたね?


    1. SQL Server DataTypesと同等のC#

    2. 指定された名前と引数のタイプに一致する演算子はありません。明示的な型キャストを追加する必要があるかもしれません。 --Netbeans、Postgresql 8.4、Glassfish

    3. postgreSQL-in vs any

    4. sqlalchemy.exc.NoSuchModuleError:プラグインを読み込めません:sqlalchemy.dialects:postgres