また、再帰クエリについて

この投稿では、再帰クエリの作成方法について説明します。 このトピックは1回または2回以上提起されましたが、通常、すべては単純な「木製」の場合に限定されます。つまり、上から下へ、上から根まで登ります。 任意のグラフのより複雑なケースを扱います。



理論を繰り返すことから始めましょう(すべてが明確であるため、非常に簡単です)、実際のタスクにアプローチする方法が明確でない場合、または明確に思えるが、リクエストが頑固に機能しない場合の対処方法について説明します。



演習では、 前述のデモデータベースを使用し、リクエストを記述して、ある空港から別の空港への最短ルートを見つけます。



理論のビット



古き良きリレーショナルSQLは、乱れたセットでうまく機能します。言語自体とDBMSの "内部"の両方で、それらのセットにはツールの宝庫があります。 SQLが気に入らないのは、1つのステートメントでタスクを実行するのではなく、ループで1行ずつプルする場合です。 オプティマイザーはあきらめ、脇に置きます。あなたは生産性に直面します。



したがって、再帰クエリは、SQL内でループを作成するための方法です。 非常に頻繁に必要だったわけではありませんが、時々必要になることがあります。 そして、再帰クエリは通常のクエリとはあまり似ておらず、通常のループとはあまり似ていないため、ここから困難が始まります。



再帰クエリの一般的なスキームは次のとおりです。



WITH RECURSIVE t AS (

(1)

UNION ALL

(2)

)

SELECT * FROM t; (3)









最初に、非再帰部分(1)が実行されます。 次に、再帰部分(2)が行を返すまで実行されます。 再帰部分がそう呼ばれるのは、名前tで利用できる前の反復の結果を参照できるためです。



途中で、各反復の結果が結果のテーブルに追加され、リクエスト全体が完了した後、同じ名前tで使用できるようになります(3)。 UNION ALLの代わりにUNIONを書き込むと、各反復で重複行が削除されます。



擬似コードの形式では、これは次のように表すことができます。



res ← EMPTY;

t ← ( );

WHILE t IS NOT EMPTY LOOP

res ← res UNION ALL t;

aux ← ( );

t ← aux;

END LOOP;

t ← res;









簡単な例:



demo=# WITH RECURSIVE t(n,factorial) AS (

VALUES (0,1)

UNION ALL

SELECT t.n+1, t.factorial*(t.n+1) FROM t WHERE tn < 5

)

SELECT * FROM t;




n | factorial

---+-----------

0 | 1

1 | 1

2 | 2

3 | 6

4 | 24

5 | 120

(6 )









まあ、覚えておいてください。 この段階ですべてが明確ではない場合は、ドキュメント読む時間です。



練習する





理論を身に付ければ、上記のクエリを(理論的に)取得して作成できます。たとえば、Ust-Kut(UKX)からNeryungri(CNN)への最短ルートを検索します。



デモベース全体の中で、空港(空港)とルート(ルート)の2つのテーブルが必要です。 正式には、ルートは具体化された表現ですが、考えることはできません。 (デモベースの構造にまだ慣れていない場合は、 その説明を参照してください。)



クエリは次のようになります。



WITH RECURSIVE p(last_arrival, destination, hops, flights, found) AS (

SELECT a_from.airport_code,

a_to.airport_code,

ARRAY[a_from.airport_code],

ARRAY[]::char(6)[],

a_from.airport_code = a_to.airport_code

FROM airports a_from, airports a_to

WHERE a_from.airport_code = 'UKX'

AND a_to.airport_code = 'CNN'

UNION ALL

SELECT r.arrival_airport,

p.destination,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

bool_or(r.arrival_airport = p.destination) OVER ()

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND NOT r.arrival_airport = ANY(p.hops)

AND NOT p.found

)

SELECT hops,

flights

FROM p

WHERE p.last_arrival = p.destination;









もちろん、これは素晴らしいですが、画面上にカーソルが点滅しているだけの場合にこれに到達する方法だけですか?



カセットのこの時点で、彼はいつも誤ってビールを巻き戻しボタンに置​​いたように見えます。ダンサーはランディ自身の悪質なパロディーをやめ、突然プロとして完全に動き始めます。 動きは以前と同じように見えますが、彼が創造的なパフォーマンスでそれらを区別することができれば彼を気にします。 スムーズな移行はありません。これが、これらのビデオチュートリアルでランディを激怒させ、常に激怒させます。 どんな馬鹿でも30分で基本的な手順を学ぶことができます。 しかし、30分が経過すると、何らかの理由でコーチは、クレジットが「数年が経過した」ように点滅したかのように、ステージの周りでひらひらと動き始めることを期待します。 おそらく、人文科学は数学でも同じように感じるとランディは考えます。 ここで、教師は黒板にいくつかの簡単な方程式を書き、10分後には真空中の光の速度がすでに推測されています。



-ニール・スティーブンソン、クリプトノミコン(私の翻訳)



たとえば、頭からそのようなリクエストを書くことはできません。 したがって、順次移動します。



だから私たちは道を譲る必要があります。 ポイントAからポイントBへの道は、一連のフライトです。 最初のフライトはAから出発し、次のフライト-これからどこか他の場所から、最後のフライトがポイントBで終了するまでです。ここでは、開始点、終了点、中間点が不明な一連のフライトがあります。見つける。



そのようなチェーンをどのように想像しますか? リレーショナルの観点から見ると、彼女は、列番号がorderおよびairportのテーブルであることが論理的です 。 ただし、チェーンを1つのオブジェクトとして処理する必要があるため、最も便利なのは、チェーン[ Airportairport 、...]として配列を提示することです。 (これが難しい場合は、 配列とそれらを操作するための関数について読んでください。)



反復をどこから始めるかは明らかです。Ust-Kutの空港からです。



demo=# SELECT ARRAY[airport_code] FROM airports WHERE airport_code = 'UKX';



array

-------

{UKX}

(1 )









ARRAY ['UKX']だけではどうですか? 安全にプレイしておくと便利です。空港コードを突然間違えても、リクエストは何も返しません。



ここで、この最初の反復の結果がテーブルにあり、2番目の反復を行う必要があると想像してください。 これを行うことができます:テーブルを作成してデータを取り込み、クエリを作成します。 しかし、WITHを使用する方が簡単です:



demo=# WITH p(last_arrival, hops) AS (

SELECT airport_code,

ARRAY[airport_code]

FROM airports

WHERE airport_code = 'UKX'

)

SELECT * FROM p;




last_arrival | hops

--------------+-------

UKX | {UKX}

(1 )









混乱しないように、列ホップに名前を付けました。 さらに、将来のチェーンの最後のアイテムに別の( last_arrival )を追加しました。 代わりに、配列の最後の要素(p.hops [cardinality(p.hops)])を計算できますが、これはそれほど明白ではありません。



次に、2番目の反復:



demo=# WITH p(last_arrival, hops) AS (

SELECT airport_code,

ARRAY[airport_code]

FROM airports

WHERE airport_code = 'UKX'

)

SELECT r.arrival_airport AS last_arrival,

p.hops || ARRAY[r.arrival_airport] AS hops

FROM routes r, p

WHERE r.departure_airport = p.last_arrival;




last_arrival | hops

--------------+-----------

KJA | {UKX,KJA}

(1 )









私たちは何をしましたか? 最初の反復(テーブルp)を取得し、ルートに接続しました。 出発空港として、チェーンの最後の空港を指定し、到着空港を右側のチェーンに追加しました。 Ust-Kutからクラスノヤルスクまで一人で飛べることがわかりました。



これで、再帰クエリを組み立てる方法が多かれ少なかれ明確になりました。 マジックワードRESURSIVEを追加し、UNION ALLを使用してクエリを最初の反復と組み合わせます。 そして、メインのリクエストでは、最終的に目的地空港(CNN)につながったチェーンを選択します。



こんな感じ?



demo=# WITH RECURSIVE p(last_arrival, hops) AS (

SELECT airport_code,

ARRAY[airport_code]

FROM airports

WHERE airport_code = 'UKX'

UNION ALL

SELECT r.arrival_airport,

p.hops || r.arrival_airport

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

)

SELECT *

FROM p

WHERE p.last_arrival = (

SELECT airport_code FROM airports WHERE airport_code = 'CNN'

);




: "p" 2 character(3)[] , bpchar[]

3: ARRAY[airport_code]

^

: .









うん Postgresは、2番目の列が非再帰的用語のタイプ文字(3)[]であることに動揺していますが、一般的にタイプはbpchar []です。 bpchar型(空白が埋め込まれたchar)は、char型の内部名です。 残念ながら、配列の連結では要素の型が保持されないため、明示的な型変換が必要です。



demo=# WITH RECURSIVE p(last_arrival, hops) AS (

SELECT airport_code,

ARRAY[airport_code]

FROM airports

WHERE airport_code = 'UKX'

UNION ALL

SELECT r.arrival_airport,

( p.hops || r.arrival_airport )::char(3)[]

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

)

SELECT *

FROM p

WHERE p.last_arrival = (

SELECT airport_code FROM airports WHERE airport_code = 'CNN'

);









エラーはもうありませんが、悲しいかな-リクエストはハングしています。 そして今何をすべきか?



わかります。 「ステップバイステップ」でリクエストを実行して、各反復で何が起こるかを見てみましょう。



最初の反復をテーブルに詰め込み、徐々に新しい反復を追加することでトリックを繰り返すことができることは明らかですが、これは非常に面倒であり、間違いを犯しやすいです。 しかし、より良い方法があります。



クエリに反復番号を持つ別の列を追加しましょう(レベルと呼びましょう)。 最初の反復では、1に等しくなり、それから増加します。 これだけでは役に立ちませんが、今ではどこでもリクエストの実行を停止できます。 最初の2回の繰り返しで、3回目を見てみましょう。



demo=# WITH RECURSIVE p(last_arrival, hops, level ) AS (

SELECT airport_code,

ARRAY[airport_code],

1

FROM airports

WHERE airport_code = 'UKX'

UNION ALL

SELECT r.arrival_airport,

(p.hops || r.arrival_airport)::char(3)[],

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND p.level < 3

)

SELECT * FROM p;




last_arrival | hops | level

--------------+---------------+-------

UKX | {UKX} | 1

KJA | {UKX,KJA} | 2

UKX | {UKX,KJA,UKX} | 3

OVB | {UKX,KJA,OVB} | 3

OVB | {UKX,KJA,OVB} | 3

NOZ | {UKX,KJA,NOZ} | 3

NOZ | {UKX,KJA,NOZ} | 3

AER | {UKX,KJA,AER} | 3

SVO | {UKX,KJA,SVO} | 3

NUX | {UKX,KJA,NUX} | 3

UIK | {UKX,KJA,UIK} | 3

UIK | {UKX,KJA,UIK} | 3

BAX | {UKX,KJA,BAX} | 3

KRO | {UKX,KJA,KRO} | 3

OVS | {UKX,KJA,OVS} | 3

(15 )









予想外の何か。



最初に、いくつかの行が複製されます(たとえば、{UKX、KJA、OVB})。 クラスノヤルスク(KJA)からノボシビルスク(OVB)への2つの異なる便があるため、これは実際に正しいです。



demo=# SELECT flight_no

FROM routes

WHERE departure_airport = 'KJA'

AND arrival_airport = 'OVB';




flight_no

-----------

PG0206

PG0207

(2 )









行を区別するために、リクエストにフライト番号を追加します。 まだ必要です。



demo=# WITH RECURSIVE p(last_arrival, hops, flights, level) AS (

SELECT airport_code,

ARRAY[airport_code],

ARRAY[]::char(6)[],

1

FROM airports

WHERE airport_code = 'UKX'

UNION ALL

SELECT r.arrival_airport,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND p.level < 3

)

SELECT * FROM p;




last_arrival | hops | flights | level

--------------+---------------+-----------------+-------

UKX | {UKX} | {} | 1

KJA | {UKX,KJA} | {PG0022} | 2

UKX | {UKX,KJA,UKX} | {PG0022,PG0021} | 3

OVB | {UKX,KJA,OVB} | {PG0022,PG0206} | 3

OVB | {UKX,KJA,OVB} | {PG0022,PG0207} | 3

NOZ | {UKX,KJA,NOZ} | {PG0022,PG0351} | 3

NOZ | {UKX,KJA,NOZ} | {PG0022,PG0352} | 3

AER | {UKX,KJA,AER} | {PG0022,PG0501} | 3

SVO | {UKX,KJA,SVO} | {PG0022,PG0548} | 3

NUX | {UKX,KJA,NUX} | {PG0022,PG0623} | 3

UIK | {UKX,KJA,UIK} | {PG0022,PG0625} | 3

UIK | {UKX,KJA,UIK} | {PG0022,PG0626} | 3

BAX | {UKX,KJA,BAX} | {PG0022,PG0653} | 3

KRO | {UKX,KJA,KRO} | {PG0022,PG0673} | 3

OVS | {UKX,KJA,OVS} | {PG0022,PG0689} | 3

(15 )









しかし、別の奇妙さはさらに悪い。 行の1つは、{UKX、KJA、UKX}に戻ることができることを示しています。 そしてこれは、私たちが無限に輪になって飛ぶことを意味し、リクエストは止まりません。 これが凍結の鍵です。



それをどうしますか? 各空港に1回しかアクセスできないという条件を追加する必要があります(この場合、ルートはまだ最適ではありません)。



demo=# WITH RECURSIVE p(last_arrival, hops, flights, level) AS (

SELECT airport_code,

ARRAY[airport_code],

ARRAY[]::char(6)[],

1

FROM airports

WHERE airport_code = 'UKX'

UNION ALL

SELECT r.arrival_airport,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND NOT r.arrival_airport = ANY(p.hops)

AND p.level < 3

)

SELECT * FROM p;




last_arrival | hops | flights | level

--------------+---------------+-----------------+-------

UKX | {UKX} | {} | 1

KJA | {UKX,KJA} | {PG0022} | 2

OVB | {UKX,KJA,OVB} | {PG0022,PG0206} | 3

OVB | {UKX,KJA,OVB} | {PG0022,PG0207} | 3

NOZ | {UKX,KJA,NOZ} | {PG0022,PG0351} | 3

NOZ | {UKX,KJA,NOZ} | {PG0022,PG0352} | 3

AER | {UKX,KJA,AER} | {PG0022,PG0501} | 3

SVO | {UKX,KJA,SVO} | {PG0022,PG0548} | 3

NUX | {UKX,KJA,NUX} | {PG0022,PG0623} | 3

UIK | {UKX,KJA,UIK} | {PG0022,PG0625} | 3

UIK | {UKX,KJA,UIK} | {PG0022,PG0626} | 3

BAX | {UKX,KJA,BAX} | {PG0022,PG0653} | 3

KRO | {UKX,KJA,KRO} | {PG0022,PG0673} | 3

OVS | {UKX,KJA,OVS} | {PG0022,PG0689} | 3

(14 )









順調に進んでいるようです。 制限なしで始めますか?



demo=# WITH RECURSIVE p(last_arrival, hops, flights, level) AS (

SELECT airport_code,

ARRAY[airport_code],

ARRAY[]::char(6)[],

1

FROM airports

WHERE airport_code = 'UKX'

UNION ALL

SELECT r.arrival_airport,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND NOT r.arrival_airport = ANY(p.hops)

-- AND p.level < 3

)

SELECT *

FROM p

WHERE p.last_arrival = (

SELECT airport_code FROM airports WHERE airport_code = 'CNN'

);










正式には、すべてが正常に機能するはずです...しかし、リクエストは再びハングし、忍耐がある場合、「一時ファイル用のスペースが足りません」というエラーで失敗する可能性があります。



なぜそう そして、ポイントAから任意の長さのすべての可能なパスを構築する必要があるため、最後にのみポイントBで終わるパスを選択します。これは、控えめに言っても、最も効率的なアルゴリズムではありません。 災害の規模を理解するために、レベルの制限を変更して、各ステップで取得される行数を確認できます。



 1 1
 2 2
 3 14
 4 165
 5 1978
 6 22322
 7 249942
 8 2316063




さて、以降の各リクエストは、前のリクエストよりも大幅に遅くなります。

答えに何が表示されると思いますか? 大都市があった場合、おそらく2(モスクワの変更あり)です。 私たちの場合、大都市に到着するために、地域便に少なくとも2、3が追加されることが予想されます。 つまり、4または5前後のどこか、おそらく6です。ただし、要求は8で停止することはありません。チェーンを延長できるまで、約100に到達します(十分な強度と健康がある場合)。



同時に、アルゴリズムは「ワイド」に機能します。最初に長さ1のすべてのパスを追加 、次に長さ2のすべてのパスを追加します。 つまり、少なくともAからBへの最初のパスが見つかるとすぐに、このパスが最短になります(転送数に関して)。 ここでの質問は、時間内に検索を停止する方法です。



アイデアは、構築されたパスの少なくとも1つが宛先で終了する場合、すべてのステップで「パスが見つかりました」という記号を設定することです。 次に、停止条件を書き留めます。



クエリ自体に宛先を追加することから始めましょう(その前に、見つかったすべての結果をフィルタリングすることになると、最後にのみ表示されます)。 最初に計算し、クエリの再帰部分では変更せずにそのままにします。



demo=# WITH RECURSIVE p(last_arrival, destination, hops, flights, level) AS (

SELECT a_from.airport_code,

a_to.airport_code,

ARRAY[a_from.airport_code],

ARRAY[]::char(6)[],

1

FROM airports a_from, airports a_to

WHERE a_from.airport_code = 'UKX'

AND a_to.airport_code = 'CNN'

UNION ALL

SELECT r.arrival_airport,

p.destination,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.hops[cardinality(p.hops)]

AND NOT r.arrival_airport = ANY(p.hops)

AND p.level < 3

)

SELECT * FROM p;




last_arrival | destination | hops | flights | level

--------------+-------------+---------------+-----------------+-------

UKX | CNN | {UKX} | {} | 1

KJA | CNN | {UKX,KJA} | {PG0022} | 2

OVB | CNN | {UKX,KJA,OVB} | {PG0022,PG0206} | 3

OVB | CNN | {UKX,KJA,OVB} | {PG0022,PG0207} | 3

NOZ | CNN | {UKX,KJA,NOZ} | {PG0022,PG0351} | 3

NOZ | CNN | {UKX,KJA,NOZ} | {PG0022,PG0352} | 3

AER | CNN | {UKX,KJA,AER} | {PG0022,PG0501} | 3

SVO | CNN | {UKX,KJA,SVO} | {PG0022,PG0548} | 3

NUX | CNN | {UKX,KJA,NUX} | {PG0022,PG0623} | 3

UIK | CNN | {UKX,KJA,UIK} | {PG0022,PG0625} | 3

UIK | CNN | {UKX,KJA,UIK} | {PG0022,PG0626} | 3

BAX | CNN | {UKX,KJA,BAX} | {PG0022,PG0653} | 3

KRO | CNN | {UKX,KJA,KRO} | {PG0022,PG0673} | 3

OVS | CNN | {UKX,KJA,OVS} | {PG0022,PG0689} | 3

(14 )









これで、見つかったパスの符号は簡単に計算できます。最後のウェイポイントが少なくとも1行の目的地と一致する場合に設定する必要があります。 これを行うには、bool_orウィンドウ関数が必要です(ウィンドウ関数が新しいものである場合は、はじめにから始めてください。最後に詳細な説明へのリンクがあります)。



demo=# WITH RECURSIVE p(last_arrival, destination, hops, flights, found, level) AS (

SELECT a_from.airport_code,

a_to.airport_code,

ARRAY[a_from.airport_code],

ARRAY[]::char(6)[],

a_from.airport_code = a_to.airport_code,

1

FROM airports a_from, airports a_to

WHERE a_from.airport_code = 'UKX'

AND a_to.airport_code = 'OVB' -- CNN

UNION ALL

SELECT r.arrival_airport,

p.destination,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

bool_or(r.arrival_airport = p.destination) OVER (),

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND NOT r.arrival_airport = ANY(p.hops)

AND p.level < 3

)

SELECT * FROM p;




last_arrival | destination | hops | flights | found | level

--------------+-------------+---------------+-----------------+-------+-------

UKX | OVB | {UKX} | {} | f | 1

KJA | OVB | {UKX,KJA} | {PG0022} | f | 2

OVB | OVB | {UKX,KJA,OVB} | {PG0022,PG0206} | t | 3

OVB | OVB | {UKX,KJA,OVB} | {PG0022,PG0207} | t | 3

NOZ | OVB | {UKX,KJA,NOZ} | {PG0022,PG0351} | t | 3

NOZ | OVB | {UKX,KJA,NOZ} | {PG0022,PG0352} | t | 3

AER | OVB | {UKX,KJA,AER} | {PG0022,PG0501} | t | 3

SVO | OVB | {UKX,KJA,SVO} | {PG0022,PG0548} | t | 3

NUX | OVB | {UKX,KJA,NUX} | {PG0022,PG0623} | t | 3

UIK | OVB | {UKX,KJA,UIK} | {PG0022,PG0625} | t | 3

UIK | OVB | {UKX,KJA,UIK} | {PG0022,PG0626} | t | 3

BAX | OVB | {UKX,KJA,BAX} | {PG0022,PG0653} | t | 3

KRO | OVB | {UKX,KJA,KRO} | {PG0022,PG0673} | t | 3

OVS | OVB | {UKX,KJA,OVS} | {PG0022,PG0689} | t | 3

(14 )









ここでは、すでにわかっているように長さが3であるUst-Kut(UKX)からNovosibirsk(OVB)へのパスのリクエストがどのように機能するかを確認しました(このため、1か所でCNNをOVBに変更する必要がありました-些細なことですが、素晴らしいです。)すべてが機能します!



クエリの非再帰部分の属性を計算します。 単純にfalseを書くこともできますが、この方法ではリクエストはより普遍的になり、AからAへの転送回数を正しく決定します。



ブレーク条件を追加するために残り、実行できます:



demo=# WITH RECURSIVE p(last_arrival, destination, hops, flights, found, level) AS (

SELECT a_from.airport_code,

a_to.airport_code,

ARRAY[a_from.airport_code],

ARRAY[]::char(6)[],

a_from.airport_code = a_to.airport_code,

1

FROM airports a_from, airports a_to

WHERE a_from.airport_code = 'UKX'

AND a_to.airport_code = 'CNN'

UNION ALL

SELECT r.arrival_airport,

p.destination,

(p.hops || r.arrival_airport)::char(3)[],

(p.flights || r.flight_no)::char(6)[],

bool_or(r.arrival_airport = p.destination) OVER (),

p.level + 1

FROM routes r, p

WHERE r.departure_airport = p.last_arrival

AND NOT r.arrival_airport = ANY(p.hops)

AND NOT p.found

-- AND p.level < 3

)

SELECT hops, flights

FROM p

WHERE p.last_arrival = p.destination;




hops | flights

-----------------------+-------------------------------

{UKX,KJA,OVB,MJZ,CNN} | {PG0022,PG0206,PG0390,PG0035}

{UKX,KJA,OVB,MJZ,CNN} | {PG0022,PG0207,PG0390,PG0035}

{UKX,KJA,SVO,MJZ,CNN} | {PG0022,PG0548,PG0120,PG0035}

{UKX,KJA,OVB,MJZ,CNN} | {PG0022,PG0206,PG0390,PG0036}

{UKX,KJA,OVB,MJZ,CNN} | {PG0022,PG0207,PG0390,PG0036}

{UKX,KJA,SVO,MJZ,CNN} | {PG0022,PG0548,PG0120,PG0036}

{UKX,KJA,OVS,LED,CNN} | {PG0022,PG0689,PG0686,PG0245}

{UKX,KJA,SVO,LED,CNN} | {PG0022,PG0548,PG0472,PG0245}

{UKX,KJA,SVO,LED,CNN} | {PG0022,PG0548,PG0471,PG0245}

{UKX,KJA,SVO,LED,CNN} | {PG0022,PG0548,PG0470,PG0245}

{UKX,KJA,SVO,LED,CNN} | {PG0022,PG0548,PG0469,PG0245}

{UKX,KJA,SVO,LED,CNN} | {PG0022,PG0548,PG0468,PG0245}

{UKX,KJA,OVB,PEE,CNN} | {PG0022,PG0206,PG0186,PG0394}

{UKX,KJA,OVB,PEE,CNN} | {PG0022,PG0207,PG0186,PG0394}

{UKX,KJA,BAX,ASF,CNN} | {PG0022,PG0653,PG0595,PG0427}

{UKX,KJA,SVO,ASF,CNN} | {PG0022,PG0548,PG0128,PG0427}

{UKX,KJA,OVS,DME,CNN} | {PG0022,PG0689,PG0544,PG0709}

{UKX,KJA,OVS,DME,CNN} | {PG0022,PG0689,PG0543,PG0709}

{UKX,KJA,KRO,DME,CNN} | {PG0022,PG0673,PG0371,PG0709}

{UKX,KJA,OVB,DME,CNN} | {PG0022,PG0206,PG0223,PG0709}

{UKX,KJA,OVB,DME,CNN} | {PG0022,PG0207,PG0223,PG0709}

{UKX,KJA,NUX,DME,CNN} | {PG0022,PG0623,PG0165,PG0709}

{UKX,KJA,BAX,DME,CNN} | {PG0022,PG0653,PG0117,PG0709}

(23 )









実際、それがすべてです。 私たちは記事の最初からリクエストを受け取り、すぐにそれを実現します。 これで、「デバッグ」レベルを削除するか、そのままにしておくことができます。



便利なトリックを要約するには:





運動として



スキルを強化するために、同じトピックに対していくつかのバリエーションを実行できます。



  1. 空港から他の空港への乗り継ぎに必要な乗り継ぎの最大数はいくらですか?

    ヒント1:リクエストの最初の部分には、出発と到着の空港のすべてのペアを含める必要があります。

    ヒント2:見つかったパスのサインは、空港のペアごとに個別に考慮する必要があります。



  2. フライトの合計所要時間の観点から最適なルート(UKXからCNNへ)を見つけます(乗り継ぎを除く)。

    ヒント1:この方法は、転送回数の点で最適ではないことが判明する場合があります。

    ヒント2:さらなる検索が役に立たないという兆候を考え出す必要があります。



  3. 乗り継ぎの待ち時間を考慮して 、フライトの合計時間の観点から最適なルート(UKXからCNNへ)を見つけます 。 最初のフライトの時間をbookings.now() - interval '20 days'



    で準備してください。



3番目のタスクを完了したら、成功を共​​有してください( edu@postgrespro.ru )。 そして、あなたが好きなソリューションの著者、私たちはPgConf.Russia 2017への招待を喜んでいたします。



All Articles