找到与我口味最接近的用户

flu*_*nis 5 mysql optimization query-performance

我试图用 SQL 编写以下要求:

每个用户都可以根据自己的喜好对电影进行评分。(1=我不喜欢,5=我喜欢它)并且系统应该向当前用户提供他尚未评分但其他用户也喜欢的电影列表。

为简化起见,我有这张表和以下数据:

create table wich (
  uid int,
  film varchar(50),
  rate int
);

insert into wich values 
(1, 'usual suspect', 5), (1, 'gataca', 4), (1, 'goldeneye', 2),
(2, 'usual suspect', 4), (2, 'gataca', 5), (2, 'i am a legend', 4), (2, 'the hobbit', 1),
(3, 'usual suspect', 1), (3, 'gataca', 5), (3, 'goldeneye', 5),
(4, 'usual suspect', 5), (4, 'goldeneye', 5),
(5, 'usual suspect', 5), (5, 'gataca', 4), (5, 'goldeneye', 5),
(6, 'usual suspect', 4), (6, 'gataca', 3), (6, 'goldeneye', 3), (6, 'shrek', 4);
Run Code Online (Sandbox Code Playgroud)

这可以理解为:用户 1 喜欢“通常的嫌疑人”和“gataca”,但不喜欢“黄金眼”。

所以我想知道的是找到与当前用户口味最接近的用户,并获取当前用户可能喜欢的电影列表。

以下是我到目前为止所做的:

Step 1:对于用户 1 评分的每部电影,如果用户 1 和用户 2 对电影的评分方式相同,则计算一个分数(如果差异大于 2,则应为 0),如果两者都喜欢电影或两者都不喜欢就很好'不喜欢电影。

select w2.uid, 1 as nb, case when abs(w1.rate - w2.rate) <= 2 THEN 5-abs(w1.rate - w2.rate) ELSE 0 END as score
        from wich w1
        inner join wich w2 on w1.uid<>w2.uid and w1.film=w2.film
        where w1.uid=1
Run Code Online (Sandbox Code Playgroud)

第 2 步:按用户计算分数

select scores.uid, SUM(scores.score) * 100 / sum(scores.nb) as score
from (
    select w2.uid, 1 as nb, case when abs(w1.rate - w2.rate) <= 2 THEN 5-abs(w1.rate - w2.rate) ELSE 0 END as score
    from wich w1
    inner join wich w2 on w1.uid<>w2.uid and w1.film=w2.film
    where w1.uid=1
) scores
group by uid
ORDER BY score desc
Run Code Online (Sandbox Code Playgroud)

第 3 步:找到用户 1 尚未评分但他可能喜欢的电影

select w3.uid, w3.film,w3.rate, matches.score
from (
    select scores.uid, SUM(scores.score) * 100 / sum(scores.nb) as score
    from (
        select w2.uid, 1 as nb, case when abs(w1.rate - w2.rate) <= 2 THEN 5-abs(w1.rate - w2.rate) ELSE 0 END as score
        from wich w1
        inner join wich w2 on w1.uid<>w2.uid and w1.film=w2.film
        where w1.uid=1
    ) scores
    group by uid
    ORDER BY score desc
) as matches
inner join wich w3 on matches.uid=w3.uid and w3.rate >= 3
left join wich w4 on w4.uid=1 and w3.film=w4.film
where w4.uid is null
order by w3.rate desc, matches.score desc;
Run Code Online (Sandbox Code Playgroud)

这似乎有效,但我不确定如果每个用户的大量电影和桌子变大,这是否仍会在短时间内响应。

你怎么认为 ?

有没有更好的方法来完成这样的事情?

一个工作小提琴:http ://sqlfiddle.com/#!2/ 89c57/1/0


编辑:

在我的真实表格中,电影列是一个整数,我有第二个表格,其中包含电影数据(标题、描述、年份……)

create table wich (
  uid int,
  film int,
  rate int
);
Run Code Online (Sandbox Code Playgroud)

这里给出的例子只是为了用一张表来简化问题。

Jos*_*ber -1

标准化它

对我来说最突出的事情是你需要使这里的关系正常化。您可以在网上找到大量有关规范化数据库和 ER 图的资料。但直接切入代码,这意味着将电影标题推出到您可以认为的“查找表”中。那么你的wich表将只是所有int代码。人类阅读起来比较困难,但机器处理起来要好得多,并且避免了大量的数据重复和潜在的数据异常。

CREATE TABLE wich (
  uid int,
  film_id int REFERENCES film(id),
  rate int,
  PRIMARY KEY (uid, film_id)
);

CREATE TABLE film (  
  id int PRIMARY KEY,
  title varchar(50)
);
Run Code Online (Sandbox Code Playgroud)

要让电影的标题显示在您的查询中,您只需加入film表即可获取标题。

论性能

除非您添加一些索引,否则随着数据大小的增长,您的数据库可能会变慢。要创建的索引很大程度上取决于您将针对数据库运行的查询。一个好的起点是创建索引:

  • 您用作 JOIN 条件一部分的列
  • 您在 WHERE 子句中引用的列