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 

XY 有 2 个共同标签。对于 k = 1j = 2 XY 将被 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 = 1j = 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 = 2j = 1:
预期:

|   user_id    |  User Name   |   photo_id   |
| 0            | Bob          | 0            |

实际: 空结果。

对于 j = 2k = 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?