当一个表不是数组值时,我似乎找不到通过数组列连接两个表的技巧,而另一个表的数组值可以包含多个值。当存在单值数组时它确实有效。
这是我所谈论的一个简单的例子。实数表在数组列FWIW上有GIN索引。这些没有,但查询行为相同。
DROP TABLE IF EXISTS eg_person;
CREATE TABLE eg_person (id INT PRIMARY KEY, name TEXT);
INSERT INTO eg_person (id, name) VALUES
(1, 'alice')
, (2, 'bob')
, (3, 'charlie');
DROP TABLE IF EXISTS eg_assoc;
CREATE TABLE eg_assoc (aid INT PRIMARY KEY, actors INT[], benefactors INT[]);
INSERT INTO eg_assoc (aid, actors, benefactors) VALUES
(1, '{1}' , '{2}')
, (2, '{1,2}', '{3}')
, (3, '{1}' , '{2,3}')
, (4, '{4}' , '{1}');
SELECT aid, actors, a_person.name, benefactors, b_person.name
FROM eg_assoc
LEFT JOIN eg_person a_person on array[a_person.id] @> eg_assoc.actors
LEFT JOIN eg_person b_person on array[b_person.id] @> eg_assoc.benefactors;
实际结果是这样的。这里的问题是,如果NULL
或actors
包含多个值,则名称列会显示benefactors
。
aid | actors | name | benefactors | name
-----+--------+-------+-------------+---------
1 | {1} | alice | {2} | bob
2 | {1,2} | | {3} | charlie
3 | {1} | alice | {2,3} |
4 | {4} | | {1} | alice
我在期待这个:
aid | actors | name | benefactors | name
-----+--------+-------+-------------+---------
1 | {1} | alice | {2} | bob
2 | {1,2} | alice | {3} | charlie
2 | {1,2} | bob | {3} | charlie
3 | {1} | alice | {2,3} | bob
3 | {1} | alice | {2,3} | charlie
4 | {4} | | {1} | alice
如果我能让它看起来像这样,那将是非常好的:
aid | actors | name | benefactors | name
-----+--------+-------------+-------------+---------
1 | {1} | {alice} | {2} | {bob}
2 | {1,2} | {alice,bob} | {3} | {charlie}
3 | {1} | {alice} | {2,3} | {bob, charlie}
4 | {4} | | {1} | {alice}
我知道这个架构是非规范化的,如果需要,我愿意去正常的表示。但是,这是一个摘要查询,它已经涉及比我更喜欢的更多连接。
答案 0 :(得分:3)
&&
运算符和索引?你是对的overlap operator &&
could use a GIN index on arrays。对于以下查询非常有用(在actor中查找人1的行):
SELECT * FROM eg_assoc WHERE actors && '{1}'::int[]
但是,查询的逻辑是相反的,寻找eg_assoc
中数组中列出的所有人。这里的GIN索引是 no 帮助。我们只需要PK person.id
的btree索引。
这不是一个小问题。首先阅读:
以下查询保留原始数组完全按照给定,包括可能的重复元素和元素的原始顺序。适用于 1维数组。其他尺寸折叠成单个尺寸。保留多个维度(但完全可能)更复杂:
对于Postgres 8.4 + (其中引入了generate_subsrcipts()
):
SELECT aid, actors
, ARRAY( SELECT name
FROM generate_subscripts(e.actors, 1) i
JOIN eg_person p ON p.id = e.actors[i]
ORDER BY i) AS act_names
, benefactors
, ARRAY( SELECT name
FROM generate_subscripts(e.benefactors, 1) i
JOIN eg_person p ON p.id = e.benefactors[i]
ORDER BY i) AS ben_names
FROM eg_assoc e;
即使在第9.3页,也可能表现最佳
使用比array_agg()
更快的array constructor。
LATERAL
查询对于PostgreSQL 9.3 + 。
SELECT e.aid, e.actors, a.act_names, e.benefactors, b.ben_names
FROM eg_assoc e
, LATERAL (
SELECT ARRAY( SELECT name
FROM generate_subscripts(e.actors, 1) i
JOIN eg_person p ON p.id = e.actors[i]
ORDER BY i)
) a(act_names)
, LATERAL (
SELECT ARRAY( SELECT name
FROM generate_subscripts(e.benefactors, 1) i
JOIN eg_person p ON p.id = e.benefactors[i]
ORDER BY i)
) b(ben_names);
SQL Fiddle有几个变体(第9.4版的版本除外)。
细微的细节:如果找不到人,就会被遗漏。如果没有找到整个阵列的人,这两个查询都会生成空数组( '{}'
)。其他查询样式将返回 NULL
。我为小提琴添加了变种。
WITH ORDINALITY
SELECT aid, actors
, ARRAY(SELECT name
FROM unnest(e.actors) WITH ORDINALITY a(id, i)
JOIN eg_person p USING (id)
ORDER BY a.i) AS act_names
, benefactors
, ARRAY(SELECT name
FROM unnest(e.benefactors) WITH ORDINALITY b(id, i)
JOIN eg_person USING (id)
ORDER BY b.i) AS ben_names
FROM eg_assoc e;
query provided by @a_horse 似乎来完成这项工作,但它不可靠,误导,可能不正确且不必要的昂贵。
由于两个不相关的连接,代理交叉连接。一种偷偷摸摸的反模式。详细说明:
在DISTINCT
中使用array_agg()
进行表面修复,以消除生成的重复项,但这确实在口红上涂上了口红。它还消除了原始中的重复项,因为此时无法区分 - 这可能是不正确的。
表达式a_person.id = any(eg_assoc.actors)
正常工作,但从结果中消除了重复(在此查询中发生了两次),除非另有说明,否则这是错误的。
不保留原始数组元素的顺序。一般来说这很棘手。但是在这个查询中它更加恶化,因为演员和恩人会成倍增加并再次变得明显,保证任意顺序。
外部SELECT
中没有列别名会导致重复的列名称,这会使某些客户端失败(不在没有别名的小提琴中工作)。
min(actors)
和min(benefactors)
没用。通常只需将列添加到GROUP BY
而不是伪造聚合它们。但eg_assoc.aid
无论如何都是PK列(涵盖GROUP BY
中的整个表格),因此甚至不需要。只需actors, benefactors
。
汇总整个结果是浪费时间和精力开始的。使用不会使基本行相乘的更智能的查询,然后您就不必将它们聚合回来。
答案 1 :(得分:1)
您需要使用= ANY()
运算符:
SELECT aid, actors, a_person.name, benefactors, b_person.name
FROM eg_assoc
LEFT JOIN eg_person a_person on a_person.id = any(eg_assoc.actors)
LEFT JOIN eg_person b_person on b_person.id = any(eg_assoc.benefactors);
如果我能让它看起来像这样,那将是非常好的。
根据aid
:
SELECT aid, min(actors), array_agg(distinct a_person.name), min(benefactors), array_agg(distinct b_person.name)
FROM eg_assoc
LEFT JOIN eg_person a_person on a_person.id = any(eg_assoc.actors)
LEFT JOIN eg_person b_person on b_person.id = any(eg_assoc.benefactors)
group by aid;