Select 行具有最相似的一组属性
Select rows with most similar set of attributes
我有一个 PostgreSQL 8.3.4 数据库来保存有关照片标记的信息。
首先,我的 table 定义:
create table photos (
id integer
, user_id integer
, primary key (id, user_id)
);
create table tags (
photo_id integer
, user_id integer
, tag text
, primary key (user_id, photo_id, tag)
);
我想做什么+简单的例子:
我正在尝试 return 所有至少有 k 其他照片至少有 j 个共同标签的照片.
我。例如,如果照片 X 有这些标签(标签 table 中的信息字段):
gold
clock
family
照片 Y 有下一个标签:
gold
sun
family
flower
X 和 Y 有 2 个共同标签。对于 k = 1
和 j = 2
X 和 Y 将被 returned.
我试过的
SELECT tags1.user_id , users.name, tags1.photo_id
FROM users, tags tags1, tags tags2
WHERE ((tags1.info = tags2.info) AND (tags1.photo_id != tags2.photo_id)
AND (users.id = tags1.user_id))
GROUP BY tags1.user_id, tags1.photo_id, tags2.user_id, tags2.photo_id, users.name
HAVING ((count(tags1.info) = <j>) and (count(*) >= <k>))
ORDER BY user_id asc, photo_id asc
我的失败结果:
当我尝试 运行 它在那些 table 上时:
photos
photo_id user_id
0 0
1 0
2 0
20 1
23 1
10 3
tags
photo_id user_id tag
0 0 Car
0 0 Bridge
0 0 Sky
20 1 Car
20 1 Bridge
10 3 Sky
k = 1
和 j = 1
的结果:
预期:
| user_id | User Name | photo_id |
| 0 | Bob | 0 |
| 1 | Ben | 20 |
| 3 | Lev | 10 |
实际:
| user_id | User Name | photo_id |
| 0 | Bob | 0 |
| 3 | Lev | 10 |
对于 k = 2
和 j = 1
:
预期:
| user_id | User Name | photo_id |
| 0 | Bob | 0 |
实际: 空结果。
对于 j = 2
和 k = 2
:
预期: 空结果。
实际:
| user_id | User Name | Photo ID |
| 0 | Bob | 0 |
| 1 | Ben | 20 |
如何正确解决这个问题?
如果我没理解错的话,你想通过公共标签计算所有用户的所有照片之间的相似度。
我想你需要这个:
SELECT
A.user_id,
A.photo_id,
B.user_id,
B.photo_id,
(
SELECT COUNT(*)
FROM
tags TA
JOIN tags TB ON TA.tag = TB.tag
WHERE
A.user_id = TA.user_id
AND A.photo_id = TA.photo_id
AND B.user_id = TB.user_id
AND B.photo_id = TB.photo_id
) AS common_tags
FROM
users A
,users B
WHERE
-- Exclude results to self
A.user_id <> B.User_id
AND A.photo_id <> B.photo_id
使用您的当前设计,这仅使用基本的SQL功能并且也应该适用于 Postgres 8.3(未经测试):
SELECT *
FROM photos p
WHERE (
SELECT count(*) >= 1 -- k other photos
FROM (
SELECT 1
FROM tags t1
JOIN tags t2 USING (tag)
WHERE t1.photo_id = p.id
AND t1.user_id = p.user_id
AND (t2.photo_id <> p.id OR
t2.user_id <> p.user_id)
GROUP BY t2.photo_id, t2.user_id
HAVING count(*) >= 1 -- j common tags
) t1
);
或:
SELECT *
FROM (
SELECT id, user_id
FROM (
SELECT t1.photo_id AS id, t1.user_id
FROM tags t1
JOIN tags t2 USING (tag)
WHERE (t2.photo_id <> t1.photo_id OR
t2.user_id <> t1.user_id)
GROUP BY t1.photo_id, t1.user_id, t2.photo_id, t2.user_id
HAVING count(*) >= 1 -- j common tags
) sub1
GROUP BY 1, 2
HAVING count(*) >= 1 -- k other photos
) sub2
JOIN photos p USING (id, user_id);
在 Postgres 9.3 或更高版本中,您可以使用具有 LATERAL
连接的相关子查询...
以上可能比我的第一个查询还要快:
SELECT *
FROM (
SELECT photo_id, user_id
FROM tags t
GROUP BY 1, 2
HAVING (
SELECT count(*) >= 1
FROM (
SELECT photo_id, user_id
FROM tags
WHERE tag = ANY(array_agg(t.tag))
AND (photo_id <> t.photo_id OR
user_id <> t.user_id)
GROUP BY 1, 2
HAVING count(*) >= 2
) t1
)
) t
JOIN photos p ON p.id = t.photo_id
AND p.user_id = t.user_id;
SQL Fiddle 在 Postgres 9.3 上显示两者。
第一个查询只需要正确的基本索引。
对于第二次,我将构建一个带有整数数组的物化视图,安装 intarray 模块,整数数组列上的 GIN 索引以获得更好的性能...
相关:
合理的设计
photos
的单列序列 PK 并且只存储每张照片的标签 ID 会更有效率......:[=20=]
CREATE TABLE photo (
photo_id serial PRIMARY KEY
, user_id int NOT NULL
);
CREATE TABLE tag (
tag_id serial PRIMARY KEY
, tag text UNIQUE NOT NULL
);
CREATE TABLE photo_tag (
photo_id int REFERENCES (photo)
, tag_id int REFERENCES (tag)
, PRIMARY KEY (photo_id, tag_id)
);
也会使查询更简单、更快。
- How to implement a many-to-many relationship in PostgreSQL?
我有一个 PostgreSQL 8.3.4 数据库来保存有关照片标记的信息。
首先,我的 table 定义:
create table photos (
id integer
, user_id integer
, primary key (id, user_id)
);
create table tags (
photo_id integer
, user_id integer
, tag text
, primary key (user_id, photo_id, tag)
);
我想做什么+简单的例子:
我正在尝试 return 所有至少有 k 其他照片至少有 j 个共同标签的照片.
我。例如,如果照片 X 有这些标签(标签 table 中的信息字段):
gold
clock
family
照片 Y 有下一个标签:
gold
sun
family
flower
X 和 Y 有 2 个共同标签。对于 k = 1
和 j = 2
X 和 Y 将被 returned.
我试过的
SELECT tags1.user_id , users.name, tags1.photo_id
FROM users, tags tags1, tags tags2
WHERE ((tags1.info = tags2.info) AND (tags1.photo_id != tags2.photo_id)
AND (users.id = tags1.user_id))
GROUP BY tags1.user_id, tags1.photo_id, tags2.user_id, tags2.photo_id, users.name
HAVING ((count(tags1.info) = <j>) and (count(*) >= <k>))
ORDER BY user_id asc, photo_id asc
我的失败结果:
当我尝试 运行 它在那些 table 上时:
photos
photo_id user_id
0 0
1 0
2 0
20 1
23 1
10 3
tags
photo_id user_id tag
0 0 Car
0 0 Bridge
0 0 Sky
20 1 Car
20 1 Bridge
10 3 Sky
k = 1
和 j = 1
的结果:
预期:
| user_id | User Name | photo_id |
| 0 | Bob | 0 |
| 1 | Ben | 20 |
| 3 | Lev | 10 |
实际:
| user_id | User Name | photo_id |
| 0 | Bob | 0 |
| 3 | Lev | 10 |
对于 k = 2
和 j = 1
:
预期:
| user_id | User Name | photo_id |
| 0 | Bob | 0 |
实际: 空结果。
对于 j = 2
和 k = 2
:
预期: 空结果。
实际:
| user_id | User Name | Photo ID |
| 0 | Bob | 0 |
| 1 | Ben | 20 |
如何正确解决这个问题?
如果我没理解错的话,你想通过公共标签计算所有用户的所有照片之间的相似度。
我想你需要这个:
SELECT
A.user_id,
A.photo_id,
B.user_id,
B.photo_id,
(
SELECT COUNT(*)
FROM
tags TA
JOIN tags TB ON TA.tag = TB.tag
WHERE
A.user_id = TA.user_id
AND A.photo_id = TA.photo_id
AND B.user_id = TB.user_id
AND B.photo_id = TB.photo_id
) AS common_tags
FROM
users A
,users B
WHERE
-- Exclude results to self
A.user_id <> B.User_id
AND A.photo_id <> B.photo_id
使用您的当前设计,这仅使用基本的SQL功能并且也应该适用于 Postgres 8.3(未经测试):
SELECT *
FROM photos p
WHERE (
SELECT count(*) >= 1 -- k other photos
FROM (
SELECT 1
FROM tags t1
JOIN tags t2 USING (tag)
WHERE t1.photo_id = p.id
AND t1.user_id = p.user_id
AND (t2.photo_id <> p.id OR
t2.user_id <> p.user_id)
GROUP BY t2.photo_id, t2.user_id
HAVING count(*) >= 1 -- j common tags
) t1
);
或:
SELECT *
FROM (
SELECT id, user_id
FROM (
SELECT t1.photo_id AS id, t1.user_id
FROM tags t1
JOIN tags t2 USING (tag)
WHERE (t2.photo_id <> t1.photo_id OR
t2.user_id <> t1.user_id)
GROUP BY t1.photo_id, t1.user_id, t2.photo_id, t2.user_id
HAVING count(*) >= 1 -- j common tags
) sub1
GROUP BY 1, 2
HAVING count(*) >= 1 -- k other photos
) sub2
JOIN photos p USING (id, user_id);
在 Postgres 9.3 或更高版本中,您可以使用具有 LATERAL
连接的相关子查询...
以上可能比我的第一个查询还要快:
SELECT *
FROM (
SELECT photo_id, user_id
FROM tags t
GROUP BY 1, 2
HAVING (
SELECT count(*) >= 1
FROM (
SELECT photo_id, user_id
FROM tags
WHERE tag = ANY(array_agg(t.tag))
AND (photo_id <> t.photo_id OR
user_id <> t.user_id)
GROUP BY 1, 2
HAVING count(*) >= 2
) t1
)
) t
JOIN photos p ON p.id = t.photo_id
AND p.user_id = t.user_id;
SQL Fiddle 在 Postgres 9.3 上显示两者。
第一个查询只需要正确的基本索引。
对于第二次,我将构建一个带有整数数组的物化视图,安装 intarray 模块,整数数组列上的 GIN 索引以获得更好的性能...
相关:
合理的设计
photos
的单列序列 PK 并且只存储每张照片的标签 ID 会更有效率......:[=20=]
CREATE TABLE photo (
photo_id serial PRIMARY KEY
, user_id int NOT NULL
);
CREATE TABLE tag (
tag_id serial PRIMARY KEY
, tag text UNIQUE NOT NULL
);
CREATE TABLE photo_tag (
photo_id int REFERENCES (photo)
, tag_id int REFERENCES (tag)
, PRIMARY KEY (photo_id, tag_id)
);
也会使查询更简单、更快。
- How to implement a many-to-many relationship in PostgreSQL?