2

次のテーブルがあります。

CREATE TABLE element (
  element_id serial PRIMARY KEY,
  local_id integer,
  name varchar,
  CONSTRAINT fk_element_local_id FOREIGN KEY (local_id)
      REFERENCES local (local_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);

CREATE TABLE local (
  local_id serial PRIMARY KEY,
  parent_id integer,
  name varchar,
  CONSTRAINT fk_local_parent_id_local_id FOREIGN KEY (parent_id)
      REFERENCES local (local_id) MATCH SIMPLE
      ON UPDATE CASCADE ON DELETE SET NULL
);

CREATE TABLE category (
  category_id serial PRIMARY KEY,
  name varchar
);

CREATE TABLE action (
  action_id serial PRIMARY KEY,
  local_id integer,
  category_id integer,
  CONSTRAINT fk_action_local_id FOREIGN KEY (local_id)
      REFERENCES local (local_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT fk_action_element_id FOREIGN KEY (element_id)
      REFERENCES element (element_id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
);

アクションからすべての要素を選択したい。要素のローカルがアクションのローカルの子孫である場合、それも表示されます。
例:

local:

|local_id | parent_id | name |
|---------+-----------+------|
|1        |NULL       |A     |
|2        |1          |B     |
|3        |1          |C     |
|4        |3          |D     |
|5        |NULL       |E     |
|6        |5          |F     |
|_________|___________|______|

category:

| category_id | name |
|-------------+------|
|1            |A     |
|2            |B     |
|2            |C     |
|_____________|______|

element:

|element_id | local_id | name | category_id |
|-----------+----------+------+-------------|
|1          |1         |A     | 1           |
|2          |2         |B     | 2           |
|3          |2         |C     | 1           |
|4          |4         |D     | 2           |
|5          |5         |E     | 2           |
|6          |6         |F     | 1           |
|7          |6         |G     | 1           |
|___________|__________|______|_____________|

action:

|action_id | local_id | category_id |
|----------+----------+-------------|
| 1        | 1        | 2           |
| 2        | 3        | 1           |
| 3        | 5        | 1           |
| 4        | 6        | 1           |
|__________|__________|_____________|

私が欲しいクエリの結果:

CASE: action_id = 1
return: element_id: 2,4

CASE: action_id = 2
return: element_id: null

CASE: action_id = 3
return: element_id: 6,7

実際のノードを含むすべての子孫を返す関数を作成しましたが、関数を何千回も呼び出すときのパフォーマンスのために苦労しています。私の関数は次のようになります。

CREATE OR REPLACE FUNCTION fn_local_get_childs(_parent_id integer)
  RETURNS SETOF integer AS
$BODY$
DECLARE
   r integer;
BEGIN
   FOR r IN SELECT local_id FROM local WHERE local_id IN ( 
      (WITH RECURSIVE parent AS
      (
         SELECT local_id , parent_id  from local WHERE local_id = _parent_id
         UNION ALL 
         SELECT t.local_id , t.parent_id FROM parent
         INNER JOIN local t ON parent.local_id =  t.parent_id
      )
      SELECT local_id FROM  parent
      ) 
   )
   LOOP
      RETURN NEXT r;
   END LOOP;
   RETURN;        
END;
$BODY$
  LANGUAGE plpgsql VOLATILE
  COST 100
  ROWS 1000;

そして、私の超低速クエリは次のようになります。

select e.element_id, a.action_id
from action a
join element e on (
                   e.local_id=any(select fn_local_get_childs(a.local_id)) AND 
                   e.category_id=a.category_id)

関数で使用される再帰を単一のクエリで組み合わせる方法はありますか?

4

1 に答える 1

1

クエリを統合する

いくつかの場所でロジックを改善することで、操作全体を 1 つのクエリに統合できます。SQL 関数へのラップはオプションです。

CREATE OR REPLACE FUNCTION f_elems(_action_id integer)
  RETURNS SETOF integer AS
$func$
   WITH RECURSIVE l AS (
      SELECT a.category_id, l.local_id
      FROM   action a
      JOIN   local  l USING (local_id)
      WHERE  a.action_id = $1

      UNION ALL 
      SELECT l.category_id, c.local_id
      FROM   l
      JOIN   local c ON c.parent_id = l.local_id  -- c for "child"
      )
   SELECT e.element_id
   FROM   l
   JOIN   element e USING (category_id, local_id);
$func$  LANGUAGE sql STABLE;

element_id指定された の同じローカルおよび子ローカルのすべてを取得しaction_idます。

電話:

SELECT * FROM f_elem(3);

element_id
-----------
6
7

db<>fiddle here
OLD sqlfiddle

これは、いくつかの理由により、すでに大幅に高速になっているはずです。最も明白なものは次のとおりです。

  • plpgsql の遅いループをピュア SQL に置き換えます。
  • 再帰クエリの開始セットを絞り込みます。
  • 不要で悪名高い遅いIN構成を削除します。

関数ヘッダーで宣言したパラメーター ( )の列名を取得するために、行には 1 つの列しかありませんが、SELECT * FROM ...だけではなく で呼び出しています。SELECTOUTelement_id

より速く、さらに

指数

上のインデックスaction.action_idは主キーによって提供されます。

しかし、 のインデックスを見逃している可能性がありますlocal.parent_id。それを行っている間、それを最初の要素と2番目の要素としてカバーする複数列のインデックス(Postgres 9.2+)にします。テーブルが大きい場合、これは大いに役立ちます。小さなテーブルの場合、それほど多くないか、まったくありません。parent_idlocal_idlocal

CREATE INDEX l_mult_idx ON local(parent_id, local_id);

なんで?見る:

最後に、tableの複数列インデックスelementがさらに役立つはずです。

CREATE INDEX e_mult_idx ON element (category_id, local_id, element_id);

3 番目の列は、それをカバリング インデックスelement_idにする場合にのみ役立ちます。クエリが table からより多くの列を取得する場合は、インデックスに列を追加するか削除することをお勧めします。どちらかが速くなります。elementelement_id

マテリアライズド ビュー

テーブルがほとんどまたはまったく更新を受信しない場合(action_id, element_id)、同じカテゴリを共有するすべてのペアの事前計算されたセットを提供する具体化されたビューにより、これが非常に高速になります。(この順序(action_id, element_id)で) 主キーを作成します。

于 2013-05-25T22:31:39.667 に答える