関係の深さをチェックするためにPostgreSQL関数を実行する必要があります。テーブルaからbへの循環バインディングがあり、a内の別の要素に戻り、場合によっては再びbに戻ります。これらの関係は数回可能です。これらの関係を要求する関数を作成しようとしています。
単純なSQLクエリを作成しようとしましたが、メモリオーバーフローエラーが発生し、1億行を超える結果テーブルが作成されました。 1000万行を超えるテーブル全体をチェックする場合。そこで、各ループの後にストレージを拒否するFORループを試します。
問題は、ループが関係1にカウントされるが、それ以上カウントされない最初の要素まで実行されることです。それ以降のすべてのリレーションはリレーションカウント0を取得し、bstidは単一の値のままです。
デバッグ制御を取得するために、いくつかのRETURNNEXTステートメントを配置しました。ネストされたIF-ELSEステートメントに何か問題がありますか?
CREATE OR REPLACE FUNCTION bst_ebenen() RETURNS SETOF varchar(1000) AS
$BODY$
DECLARE
--an_array varchar[bstobjid][id];
bstobjid varchar;
loopid bigint;
bstid bigint;
relation varchar;
searchsql text := '';
ebenen integer := 0;
message varchar := '';
BEGIN
searchsql = 'SELECT objid AS bstobjid FROM buchstelle';
FOR bstobjid IN EXECUTE(searchsql) LOOP
ebenen = 0;
--1. Relationsebene abfragen
loopid = (SELECT bst.id FROM buchstelle bst WHERE bst.objid = bstobjid);
IF loopid NOT IN (SELECT rid FROM buchstelle__relation LIMIT 1)
THEN ebenen = 0;
ELSE
ebenen = 1;
relation = (SELECT rel.relation FROM buchstelle__relation rel
WHERE rel.rid = loopid LIMIT 1);
RETURN NEXT relation;
--2. Relationsebene abfragen
bstid = (SELECT bst.id FROM buchstelle bst WHERE bst.objid = relation LIMIT 1);
RETURN NEXT bstid;
IF bstid NOT IN (SELECT rid FROM buchstelle__relation LIMIT 1)
THEN ebenen = ebenen;
ELSE
ebenen = 2;
relation = (SELECT rel.relation FROM buchstelle__relation rel
WHERE rel.rid = bstid LIMIT 1);
message = (ebenen, 'Ebenen');
RETURN NEXT message;
RETURN NEXT relation;
--3. Relationsebene abfragen
bstid = (SELECT bst.id FROM buchstelle bst WHERE bst.objid = relation LIMIT 1);
RETURN NEXT bstid;
IF bstid NOT IN (SELECT rid FROM buchstelle__relation LIMIT 1)
THEN ebenen = ebenen;
ELSE
ebenen = 3;
relation = (SELECT rel.relation FROM buchstelle__relation rel
WHERE rel.rid = bstid LIMIT 1);
--4. Relationsebene abfragen
bstid = (SELECT bst.id FROM buchstelle bst WHERE bst.objid = relation LIMIT 1);
RETURN NEXT bstid;
IF bstid NOT IN (SELECT rid FROM buchstelle__relation LIMIT 1)
THEN ebenen = ebenen;
ELSE
ebenen = 4;
relation = (SELECT rel.relation FROM buchstelle__relation rel
WHERE rel.rid = bstid LIMIT 1);
--5. Relationsebene abfragen
bstid = (SELECT bst.id FROM buchstelle bst WHERE bst.objid = relation LIMIT 1);
IF bstid NOT IN (SELECT rid FROM buchstelle__relation LIMIT 1)
THEN ebenen = ebenen;
ELSE
ebenen = 5;
relation = (SELECT rel.relation FROM buchstelle__relation rel
WHERE rel.rid = bstid LIMIT 1);
END IF;
END IF;
END IF;
END IF;
END IF;
-- Ausgabestring für das jeweilige Objekt
message = (bstobjid, loopid, ' mit ', ebenen, ' Relationsebenen');
RETURN NEXT message;
END LOOP;
-- Ausgabestring für die Gesamtprüfung
message = (max(ebenen), ' ist die maximale Relationstiefe');
RETURN NEXT message;
END;
$BODY$
LANGUAGE plpgsql;
[編集]
@horse_with_no_name:
はい、関数の道を歩く前に、これを試しました:
WITH RECURSIVE ebenen (objid_bst, rid_anrel, verweistauf_bst)
AS (
SELECT bst.objid AS objid_bst, anrel.rid As rid_anrel, anrel.relation AS verweistauf_bst
FROM buchstelle bst
LEFT JOIN buchstelle__relation anrel ON bst.id = anrel.rid
UNION ALL
SELECT bst.objid, anrel.rid, anrel.relation
FROM ebenen, buchstelle bst
INNER JOIN buchstelle__relation anrel ON anrel.relation = bst.objid
WHERE bst.id IN (anrel.rid)
)
SELECT
objid_bst, rid_anrel, verweistauf_bst
, count(rid_anrel) OVER (PARTITION BY objid_bst) AS relationen
FROM ebenen
ORDER BY relationen
--LIMIT 1000000;
そして最初に、いくつかのサブクエリを持つネストされたSQLクエリ。最初の2つのステップは、完全なデータセットに対してクエリを実行することによるメモリオーバーフローを伴いました。
[2014年3月7日編集]
私は自分のタスクの解決策を見つけるために循環有向グラフ関係を探しました。しかし、それらは単一のテーブル内の現実を扱います。 2つのテーブルの間の循環を処理する必要があります。ここでよりよく理解するために、発生する可能性のある単純化された関係。このパスでtab2からtab1に戻るたびに、1つの関係がマークされるため、カウントする必要があります。 CTEを関数FOR-Loopに入れることで、タスクを解決できるかもしれません。しかし、CTEが実際にどのように機能するかについてもっと理解する必要があります。
tab1
id objid
1 aaa
2 bbb
3 ccc
4 ddd
5 eee
6 fff
7 ggg
8 hhh
tab2
id rid rel2tab1
1 3 aaa
2 4 aaa
3 7 hhh
4 8 ccc
relations for each element in tab1
1
2
3 --> 1(tab2) --> 1(tab1)
4 --> 2(tag2) --> 1(tab1)
5
6
7 --> 3(tab2) --> 8(tab1) --> 4(tab2) --> 3(tab1) --> 1(tab2) --> 1(tab1)
8 --> 4(tab2) --> 3(tab1) --> 1(tab2) --> 1(tab1)
[編集:2014年8月19日]
数日後、私は再びタスクを引き受け、高性能を備えたはるかに簡単なソリューションを発見しました。たぶん、他の誰かが同様のタスクにそれを使用することができます:
SELECT CASE WHEN
(SELECT sum(t1.id)
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t2.rid IS NOT NULL) IS NULL THEN '0'
WHEN
(SELECT sum(t1.id)
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t2.rid IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL) IS NULL THEN '1'
WHEN
(SELECT sum(t1.id)
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN (
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t2.rid IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL) IS NULL THEN '2'
WHEN
(SELECT sum(t1.id)
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t2.rid IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL) IS NULL THEN '3'
WHEN
(SELECT sum(t1.id)
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t2.rid IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL) IS NULL THEN '4'
WHEN
(SELECT sum(t1.id)
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t1.objid IN
(
SELECT t2.rel_t1
FROM
table1 t1
LEFT JOIN table2 t2 ON t2.rid = t1.id
WHERE t2.rid IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL
)
AND t2.rel_t1 IS NOT NULL) IS NULL THEN '5'
ELSE 'Error: reached the maximum testing depth'
END AS relations
再帰的CTE が進むべき道のようです。
パスにサイクルなしがあると仮定します。それ以外の場合は、サイクルを検出するためにさらに作業が必要です。以下のアレイソリューションは簡単に適応できます。
この簡略化されたレイアウトに基づいて構築:
_CREATE TABLE t1 (t1_id int, objid text);
INSERT INTO t1 VALUES
(1,'aaa')
,(2,'bbb')
,(3,'ccc')
,(4,'ddd')
,(5,'eee')
,(6,'fff')
,(7,'ggg')
,(8,'hhh');
CREATE TABLE t2 (t2_id int, t1_id int, objid text);
INSERT INTO t2 VALUES
(1,3,'aaa')
,(2,4,'aaa')
,(3,7,'hhh')
,(4,8,'ccc');
_
2つの異なるソリューション:
_WITH RECURSIVE cte AS (
SELECT t.t1_id AS start_id
, t2.t2_id::text || '(t2)' || COALESCE(' ->' || t1.t1_id || '(t1)', '') AS path
, t1.t1_id
FROM t1 t
LEFT JOIN t2 USING (t1_id)
LEFT JOIN t1 ON t1.objid = t2.objid
UNION ALL
SELECT c.start_id
, c.path || ' ->' || t2.t2_id || '(t2)' || COALESCE(' ->' || t1.t1_id || '(t1)', '')
, t1.t1_id
FROM cte c
JOIN t2 USING (t1_id)
LEFT JOIN t1 USING (objid)
)
SELECT DISTINCT ON (start_id)
start_id, path
FROM cte
ORDER BY start_id, path DESC;
_
結果:
_start_id path
1
2
3 1(t2) ->1(t1)
4 2(t2) ->1(t1)
5
6
7 3(t2) ->8(t1) ->4(t2) ->3(t1) ->1(t2) ->1(t1)
8 4(t2) ->3(t1) ->1(t2) ->1(t1)
_
明らかに、テーブル名は冗長です。見栄えを良くするために追加しました。
右端の要素は最初に_t2_id
_で、右から左に交互に繰り返します。
_WITH RECURSIVE cte AS (
SELECT t.t1_id AS start_id, ARRAY[t1.t1_id, t2.t2_id] AS path
FROM t1 t
LEFT JOIN t2 USING (t1_id)
LEFT JOIN t1 ON t1.objid = t2.objid
UNION ALL
SELECT c.start_id, t1.t1_id || (t2.t2_id || path)
FROM cte c
JOIN t2 ON t2.t1_id = path[1]
LEFT JOIN t1 USING (objid)
)
SELECT DISTINCT ON (start_id)
start_id, array_remove(path, NULL) AS path
FROM cte
ORDER BY start_id, array_length(path, 1) DESC;
_
結果:
_start_id path
1 {}
2 {}
3 {1,1}
4 {1,2}
5 {}
6 {}
7 {1,1,3,4,8,3}
8 {1,1,3,4}
_
array_remove()
Postgres9.3以降が必要です。
必要な列が1つ少なくなるように配列を反転しました。最後の要素を最初に置くことで、次のステップで_path[1]
_を参照できます。それが安いかどうかわからない、テストが必要になるでしょう...
コードは短くなりますが、おそらく遅くなります。配列の処理はもっと高価だと思います。サイクルを観察する必要がある場合は、適応が容易です。
2つのテーブルを交互に使用しています。
これを再帰的にするには、ワンステップカバーする必要があります2つのホップ(_t1 -> t2
_から_t2 -> t1
_に戻る)。
最初のSELECT
は2x _LEFT JOIN
_を使用して、例の結果のようにすべての行を含めます。
一致するものが見つからない場合にループを停止する再帰部分JOIN
。ホップバックは再び_LEFT JOIN
_を使用します。