查看由同一个表中的两个外键引用的表

时间:2013-04-08 13:39:50

标签: sql

“简短版”

我有两个表:imageitem。项目由两个图像组成:让我们称它们为leftright(项目的GUI可以是例如具有两个并排的图像的画布)。我正在尝试设置图像报告,显示哪个项目“使用”它们。

+------------+             +------------+
| image      |             | item       |
+------------+             +------------+
| id  <------------+       | id         |
| name       |     |       | name       |
+------------+     +-------- im_left    |
                   +-------- im_right   |
                           +------------+

虽然如果一个项目只有一个图像的单一引用来弄清楚查询是微不足道的,但我无法将其扩展到“双引用”的情况,特别是当一个项目引用两次相同的图像时(完全合法)考虑到我的业务限制)。

到目前为止,我使用带有别名的两个LEFT OUTER JOIN来使用左列和右列将图像连接到项目。但是当左右使用相同的图像时,这种结构失败了(我无法真正解释结果,如下所示)。

由于这是数据库设计中非常常见的模式,在这种情况下,您如何设计一个视图,显示每个图像及其使用的每个项目,以及从哪个列引用它?

e.g。

  • image_1由item_1和item_2(左)和item_3(右)
  • 使用
  • image_2由none(左)和item_4(右)
  • 使用
  • image_3由item_5和item_6(左)和item_6(右)
  • 使用

长版本(可能过于本地化,但会显示我的试用版和特定问题)。

以下是我的2个表的定义:

CREATE TABLE image (
    id serial PRIMARY KEY,
    name text
);
INSERT INTO image(name) VALUES ('image 1');
INSERT INTO image(name) VALUES ('image 2');
INSERT INTO image(name) VALUES ('image 3');

CREATE TABLE item (
    id serial PRIMARY KEY,
    name text,
    im_left int,
    FOREIGN KEY (im_left) REFERENCES image(id),
    im_right int,
    FOREIGN KEY (im_right) REFERENCES image(id)
);
INSERT INTO item(name,im_left,im_right) VALUES ('item 1',1,2);
INSERT INTO item(name,im_left,im_right) VALUES ('item 2',1,3);
INSERT INTO item(name,im_left,im_right) VALUES ('item 3',2,3);

直到今天,我才使用此查询来构建我的视图:

CREATE VIEW imagev_v1 AS (
    SELECT image.id, image.name,
        array_agg(li.id) AS left_ids,
        array_agg(li.name) AS left_names,
        array_agg(ri.id) AS right_ids,
        array_agg(ri.name) AS right_names
    FROM image
        LEFT OUTER JOIN item AS li ON li.im_left=image.id
        LEFT OUTER JOIN item AS ri ON ri.im_right=image.id
    GROUP BY image.id, image.name
    ORDER BY name ASC
);

它运作得很好:

SELECT * FROM imagev_v1;
 id |  name   |  left_ids   |     left_names      |  right_ids  |     right_names
----+---------+-------------+---------------------+-------------+---------------------
  1 | image 1 | {1,2}       | {"item 1","item 2"} | {NULL,NULL} | {NULL,NULL}
  2 | image 2 | {3}         | {"item 3"}          | {1}         | {"item 1"}
  3 | image 3 | {NULL,NULL} | {NULL,NULL}         | {2,3}       | {"item 2","item 3"}
(3 rows)

直到我添加了一个在左右列中引用相同图像的偷偷摸摸的项目:

INSERT INTO item(name,im_left,im_right) VALUES ('item 4',3,3);

SELECT * FROM imagev_v1;
 id |  name   | left_ids |          left_names          |  right_ids  |         right_names
----+---------+----------+------------------------------+-------------+------------------------------
  1 | image 1 | {1,2}    | {"item 1","item 2"}          | {NULL,NULL} | {NULL,NULL}
  2 | image 2 | {3}      | {"item 3"}                   | {1}         | {"item 1"}
  3 | image 3 | {4,4,4}  | {"item 4","item 4","item 4"} | {2,3,4}     | {"item 2","item 3","item 4"}
(3 rows)

第三个结果线很奇怪,至少可以说,但我无法解释这种行为。

我尝试了另一个版本的视图,但是无法显示引用的来源(无论图像是由im_left还是im_right列引用):< / p>

CREATE VIEW imagev_v2 AS (
    SELECT image.id, image.name,
        array_agg(item.id) AS item_ids,
        array_agg(item.name) AS item_names
    FROM image
        LEFT OUTER JOIN item ON item.im_left=image.id OR item.im_right=image.id
    GROUP BY image.id, image.name
    ORDER BY name ASC
);

SELECT * FROM image_v2 ;
 id |  name   | item_ids |          item_names
----+---------+----------+------------------------------
  1 | image 1 | {1,2}    | {"item 1","item 2"}
  2 | image 2 | {1,3}    | {"item 1","item 3"}
  3 | image 3 | {2,3,4}  | {"item 2","item 3","item 4"}
(3 rows)

感谢您阅读到目前为止的阅读,现在您有权查看实际问题:如何撰写始终正确的imagev_v3(与image_v1不同),同时不会受到影响来自image_v2的“失去原因”问题?

请注意,我使用的是PostgreSQL 8.4,但我认为它应该是无关紧要的。

3 个答案:

答案 0 :(得分:1)

使用包含多个谓词的联接。它可以是内部联接,但如果您想要查看任何项目未使用的图像,请将其作为外部联接,如下所示。

Select i.serial imgId, i.text imgName, 
  case when t.im_left is Null and t.im_right Is Null then 'None'
       When t.im_left Is Null Then 'Right'
       When t.im_right Is Null Then 'Left' Else 'Both' End Source,
  t.serial itmId, t.text itmName
From Image i 
   Left Join item t 
     On t.im_left = i.serial Or
        t.im_right = i.serial  

答案 1 :(得分:0)

这是另一种可能对您有用的解决方案,类似于您最初尝试的方法。您只需要在左侧和右侧分开连接。

SELECT image_id, image_name,
    array_agg(li_id) AS left_ids,
    array_agg(li_name) AS left_names,
    array_agg(ri_id) AS right_ids,
    array_agg(ri_name) AS right_names
FROM
(
    SELECT 
      image.id as image_id, 
      image.name as image_name,
      li.id as li_id,
      li.name as li_name,
      NULL as ri_id,
      NULL as ri_name
    FROM image
    LEFT OUTER JOIN item AS li ON li.im_left=image.id
  UNION ALL
   SELECT 
      image.id as image_id, 
      image.name as image_name,
      NULL as li_id,
      NULL as li_name,
      ri.id as ri_id,
      ri.name as ri_name
    FROM image
    LEFT OUTER JOIN item AS ri ON ri.im_right=image.id

) image

GROUP BY image_id, image_name
ORDER BY image_name ASC

SQLFiddle Demo

答案 2 :(得分:0)

(由OP拒绝编辑@Charles Betana的答案)

以下是查理的答案,经过略微修改以反映这个问题。

CREATE VIEW imagev_v3 AS (
    SELECT i.id, i.name, 
        array_agg(CASE 
                    WHEN t.im_left<>i.id AND t.im_right<>i.id THEN 'None'
                    WHEN t.im_left=i.id  AND t.im_right<>i.id THEN 'Left'
                    WHEN t.im_left<>i.id AND t.im_right=i.id  THEN 'Right' 
                    ELSE 'Both' 
                END) AS item_sources,
        array_agg(t.id) AS item_ids,
        array_agg(t.name) AS item_names
    FROM image AS i 
        LEFT OUTER JOIN item AS t
            ON t.im_left = i.id OR t.im_right = i.id
    GROUP BY i.id, i.name
);

这会产生正确的答案:

SELECT * FROM imagev_v3 ;
 id |  name   |    item_sources    | item_ids |          item_names
----+---------+--------------------+----------+------------------------------
  1 | image 1 | {Left,Left}        | {1,2}    | {"item 1","item 2"}
  2 | image 2 | {Right,Left}       | {1,3}    | {"item 1","item 3"}
  3 | image 3 | {Right,Right,Both} | {2,3,4}  | {"item 2","item 3","item 4"}
(3 rows)