我正在通过POSTGRESQL DB进行查询。我的应用程序包含文章,并且文章可以具有多个标签。这些关系保存在“标签”和“文章”的联合表中。
我有一种工作方法,可以将具有某些标签的文章退还给我,或者将不包含某些标签的所有文章退还给我
def test(hashtags, include = true)
articles= []
hashtags.split(' ').each do |h|
articles+= Article.joins(:hashtags).where('LOWER(hashtags.value) LIKE LOWER(?)', "#{h}")
end
if include
articles.uniq
else
(Article.all.to_set - articles.uniq.to_set).to_a
end
end
我可以这样称呼它:
test("politics people china", true)
这会给我所有具有与#h相关的标签之一的文章
或者我可以这样称呼
test("politics people china", false)
除了那些具有这些标签之一的人之外,这将给我所有文章
它运行良好,但是我认为这不是非常有效,因为我在Ruby中做了很多事情,而不是在数据库级别。
我尝试过:
def test2(hashtags, include = true)
articles= []
pattern = ''
hashtags.split(' ').each do |h|
pattern += "#{h}|"
end
pattern = '(' + pattern[0...-1] + ')'
if include
articles = Article.joins(:hashtags).where('hashtags.value ~* ?', "#{pattern}")
else
articles = Article.joins(:hashtags).where('hashtags.value !~* ?', "#{pattern}")
end
articles.uniq
end
但是它的行为不像我想的那样。首先,如果我这样称呼它:
test2("politics china", true)
不仅会给我所有带有标签politics
或china
的文章,还会给我所有带有包含标签politics
或{{ 1}}就像这样:
china
但是它实际上应该检查一下,并且模式实际上看起来像这样,我可以在控制台中看到:
(p|o|l|i|t|c|s|h|n|a)
这不是我发现的奇怪的东西……
还有
(politics|china)
它只给我提供与一个或多个标签相关联的文章,而忽略那些根本没有标签的文章
有人可以帮助我提高工作效率吗?
编辑: 这是我的更新代码,就像答案中建议的那样
test2("politics", false)
不幸的是,def test2(hashtags, include = false)
hashtags =
if include
Hashtag.where("LOWER(value) iLIKE ANY ( array[?] )", hashtags)
else
Hashtag.where("LOWER(value) NOT iLIKE ANY ( array[?] )", hashtags)
end
Slot.joins(:hashtags).merge(hashtags).distinct
end
如果是假的,仍然没有给我完全没有标签的文章
答案 0 :(得分:2)
您是正确的
我认为这不是非常有效,因为我在Ruby中做了很多事情,而不是在数据库级别。
ActiveRecord适用于简单查询,但是当事情变得复杂时,使用纯SQL是合理的。因此,让我们尝试构建一个与您的测试用例相匹配的查询:
1)对于此调用test("politics people china", true)
,查询可能类似于:
SELECT DISTINCT ON (AR.id) AR.*
FROM articles AR
JOIN articles_hashtags AHSH ON AHSH.article_id = AR.id
JOIN hashtags HSH ON HSH.id = AHSH.hashtag_id
WHERE LOWER(HSH.value) IN ('politics', 'people', 'china')
ORDER BY AR.id;
(我不确定您的联接表如何命名,因此假设它是articles_hashtags
)。
简单明了:我们使用2个内部联接从articles
表中获取数据,这些联接具有articles_hashtags
和hashtags
和where
条件,这些条件过滤了我们希望看到的主题标签;最终将所有带该标签的文章带给我们。无论我们要过滤多少个标签,IN
语句都可以很好地工作,即使列表中只有一个标签也是如此。
请注意DISTINCT ON:有必要从结果集中删除重复的文章,以防同一篇文章在给定的主题标签列表中有多个主题标签。
2)对于呼叫test("politics people china", false)
,查询有点复杂。它需要排除具有标签的文章。因此,它应该返回带有不同标签的文章以及完全没有标签的文章。为了使事情简单,我们可以使用上一个查询:
SELECT A.*
FROM articles A
WHERE A.id NOT IN (
SELECT DISTINCT ON (AR.id) AR.id
FROM articles AR
JOIN articles_hashtags AHSH ON AHSH.article_id = AR.id
JOIN hashtags HSH ON HSH.id = AHSH.hashtag_id
WHERE LOWER(HSH.value) IN ('politics', 'people', 'china')
ORDER BY AR.id
);
在这里,我们将获取所有文章,但包含所有给定标签的文章。
3)将这些查询转换为Ruby方法可得到以下内容:
def test3(hashtags, include = true)
# code guard to prevent SQL-error when there are no hashtags given
if hashtags.nil? || hashtags.strip.blank?
return include ? [] : Article.all.to_a
end
basic_query = "
SELECT DISTINCT ON (AR.id) AR.*
FROM #{Article.table_name} AR
JOIN articles_hashtags AHSH ON AHSH.article_id = AR.id
JOIN #{Hashtag.table_name} HSH ON HSH.id = AHSH.hashtag_id
WHERE LOWER(HSH.value) IN (:hashtags)
ORDER BY AR.id"
query = if include
basic_query
else
"SELECT A.*
FROM #{Article.table_name} A
WHERE A.id NOT IN (#{basic_query.sub('AR.*', 'AR.id')})"
end
hashtag_arr = hashtags.split(' ').map(&:downcase) # to convert hashtags string into a list
Article.find_by_sql [query, { hashtags: hashtag_arr }]
end
以上方法将返回符合您条件的文章数组,无论是否为空。
答案 1 :(得分:1)
尝试一下:
def test(hashtags, include = true)
hashtags =
if include
Hashtag.where("LOWER(value) iLIKE ANY ( array[?] )", hashtags)
else
Hashtag.where("LOWER(value) NOT iLIKE ANY ( array[?] )", hashtags)
end
Article.joins(:hashtags).merge(hashtags).distinct
end