我在postgresql中有一个包含一些汽车+1000000记录的表:
+----+--------+------+---------+-----------+-------------+------------+------------+
| id | price | year | mileage | fuel_type | body_type | brand | model |
+----+--------+------+---------+-----------+-------------+------------+------------+
| 1 | 4894 | 2011 | 121842 | "Benzin" | "Sedan" | "Toyota" | "Yaris" |
| 2 | 4989 | 2012 | 33901 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 3 | 4990 | 2013 | 55105 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 3 | 5290 | 2013 | 20967 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 5 | 5594 | 2008 | 121281 | "Benzin" | "Hatchback" | "Mercedes" | "A170" |
| 6 | 4690 | 2012 | 71303 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 7 | 5290 | 2013 | 58300 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 8 | 5890 | 2013 | 35732 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 9 | 5990 | 2013 | 38777 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
| 10 | 6180 | 2013 | 69491 | "Benzin" | "Hatchback" | "VW" | "up!" |
| 11 | 6490 | 2012 | 72900 | "Benzin" | "Sedan" | "Renault" | "Clio III" |
| 12 | 6790 | 2012 | 49541 | "Benzin" | "Hatchback" | "Renault" | "Clio III" |
| 13 | 6790 | 2012 | 46377 | "Benzin" | "Hatchback" | "Renault" | "Clio III" |
| 14 | 6790 | 2012 | 45200 | "Benzin" | "Hatchback" | "Renault" | "Clio III" |
| 15 | 6894 | 2007 | 108840 | "Benzin" | "Sedan" | "VW" | "Golf V" |
| 16 | 6990 | 2009 | 54200 | "Benzin" | "Sedan" | "Renault" | "Mégane" |
| 17 | 6990 | 2012 | 40652 | "Benzin" | "Hatchback" | "Renault" | "Clio III" |
| 18 | 6990 | 2012 | 38080 | "Benzin" | "Sedan" | "Renault" | "Clio III" |
| 19 | 7290 | 2012 | 28600 | "Benzin" | "Hatchback" | "Renault" | "Clio III" |
| 20 | 7290 | 2013 | 52800 | "Benzin" | "Hatchback" | "Renault" | "Twingo" |
+----+--------+------+---------+-----------+-------------+------------+------------+
我想创建一个推荐引擎,它可以根据一些不同的标准返回20个最“相似”的匹配,例如:当用户搜索:brand = 'Renault' AND price < 60000 AND year > 2010
时,我想在搜索结果之外的其他车辆中显示其他更松散的结果,这类似,但不一定完全符合所有搜索条件。< / p>
我已经尝试在ruby中制作一些基于规则的代码,这类似于:
基于此代码,我生成一个SQL查询,其中包含where和order by子句。
然而问题是,事情变得很大,因为我想根据初始标准考虑20个不同的列,我可以选择考虑。此外,我希望建议向后兼容,因为我不想只进行简单的过滤(WHERE
)查询,在这种情况下最终可能会返回零匹配。相反,我希望它做一些类似于使用文本相似度算法的东西,你可以将一个短语与所有短语进行比较,然后得到所有这些短语的比较分数,然后你可以对它们进行排序。
我对如何实现这一点感到非常困惑,在一种方法中,这不是定义1000条规则和if / then语句来生成SQL查询。是否有其他技术可以使用,或者可能是postgresql以外的其他技术?
答案 0 :(得分:6)
计算每个数值属性的加权偏差:
deviation = abs(actual_value- expected_value)* property_weight
对文本属性应用简化计算:
deviation = (actual_value <> expected_value)::int* property_weight
按偏差总和的升序推荐位置。
实施例。我们正在寻找2012年的雷诺Twingo两厢车,里程为50000,价格为6000:
select *,
abs(price- 6000)* 100+
abs(year- 2012)* 10000+
abs(mileage- 50000)* 1+
(body_type <> 'Hatchback')::int* 40000+
(brand <> 'Renault')::int* 100000+
(model <> 'Twingo')::int* 50000
as recommendation
from cars
order by recommendation
limit 10;
id | price | year | mileage | fuel_type | body_type | brand | model | recommendation
----+-------+------+---------+-----------+-----------+---------+----------+----------------
9 | 5990 | 2013 | 38777 | Benzin | Hatchback | Renault | Twingo | 22223
8 | 5890 | 2013 | 35732 | Benzin | Hatchback | Renault | Twingo | 35268
7 | 5290 | 2013 | 58300 | Benzin | Hatchback | Renault | Twingo | 89300
4 | 5290 | 2013 | 20967 | Benzin | Hatchback | Renault | Twingo | 110033
3 | 4990 | 2013 | 55105 | Benzin | Hatchback | Renault | Twingo | 116105
2 | 4989 | 2012 | 33901 | Benzin | Hatchback | Renault | Twingo | 117199
12 | 6790 | 2012 | 49541 | Benzin | Hatchback | Renault | Clio III | 129459
13 | 6790 | 2012 | 46377 | Benzin | Hatchback | Renault | Clio III | 132623
14 | 6790 | 2012 | 45200 | Benzin | Hatchback | Renault | Clio III | 133800
20 | 7290 | 2013 | 52800 | Benzin | Hatchback | Renault | Twingo | 141800
(10 rows)
您可以通过更改属性的权重轻松校准算法。
要获得更复杂的文本属性近似值,您可以将数值赋给辅助表中的属性,如下所示:
create table models(id serial primary key, model text, value integer);
insert into models (model, value) values
('Twingo', 10),
('Clio III', 11), -- Clio is more similar to Twingo than to Laguna
('Laguna', 18)
--- etc
并在主查询中将值用作数字属性,例如:
select cars.*,
abs(price- 6000)* 100+
abs(year- 2012)* 10000+
abs(mileage- 50000)* 1+
(body_type <> 'Hatchback')::int* 40000+
(brand <> 'Renault')::int* 100000+
abs(models.value- 10)* 50000 -- 10 is a numeric value for Twingo
as recommendation
from cars
join models using(model)
order by recommendation
limit 10;
关于优化的说明。如果您可以严格定义任何属性的范围,请将其放在WHERE子句中以获得更好的性能。例如,如果查询无法返回所需品牌以外的其他品牌,那么计算此属性的偏差是没有意义的:
select *,
abs(price- 6000)* 100+
abs(year- 2012)* 10000+
abs(mileage- 50000)* 1+
(body_type <> 'Hatchback')::int* 40000+
(model <> 'Twingo')::int* 50000
as recommendation
from cars
where brand = 'Renault' -- !
order by recommendation
limit 10;
答案 1 :(得分:2)
应用机器学习技巧。
MADlib http://madlib.incubator.apache.org/是Postgres的扩展,它使您能够在数据库内部使用各种机器学习算法。值得学习和尝试。
从矢量的线性回归开始,然后尝试随机森林和其他算法,并比较在您的情况下更好的效果(评估算法质量的技巧很简单:您获取所有数据,使用70- 80%用于训练,然后使用剩余部分从训练过的引擎获得估计 - 然后使用一些函数计算偏差误差,通常人们使用均方误差方法。
另外,我可以推荐一个很棒的斯坦福在线课程,在Youtube上发布在线书籍和讲座(全部免费!):http://mmds.org/。构建推荐引擎的各种现代方法在其中得到了很好的描述。
答案 2 :(得分:0)
理想情况下,您可以在类型为tsvector的列中缓存“文本部分”(与行相关的每个文本),这样您就可以执行全文搜索甚至提供“权重”每个单词,以便在执行搜索时更重要。
例如,您可以对品牌名称给予更多权重,以便对结果进行排序,并显示同一品牌的所有品牌。假设您有一个名为“fulltext”的tsvector列:'Clio:1A Renault:2B,4C Benzin:5D'::tsvector;
,您可以使用tsquery
'Renault & Clio'::tsquery;
来搜索它,它会为每个雷诺和每个可用的Clio提供结果,但它会首先放在Clio
上,然后放在Renault
上。请注意,如果存在Mercedes Clio
的任何可能性,它也会显示出来。
文档非常明确,有些例子,我建议深入研究。
话虽如此,在这种情况下,数据库不会为您完成工作。如果Clio是一个非常接近的比赛,只因为他们拥有相同的品牌(雷诺),是的,它会起作用。但是,如果您使用(精神上)其他参数,例如汽车的大小,如果它是城市汽车等等,那么您是唯一可以设计该算法的人。例如,价格范围部分不是任何全文搜索将为您执行的操作,您必须主动检查是否包含数字并最终排序(除非数字完全匹配)。
最后,您的工作就是这样,根据用户输入创建一个“智能”的功能,并定义一个可以对数据库运行的复杂查询。这是一个漫长的过程,但绝对可行。尽量聪明但不要太多,无论如何tsvector
会覆盖所有文本列,这会减少你所拥有的列数。