如何在Rails中提高数据库查询方法的效率

时间:2018-09-28 13:12:00

标签: ruby-on-rails regex postgresql

我正在通过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)

不仅会给我所有带有标签politicschina的文章,还会给我所有带有包含标签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 如果是假的,仍然没有给我完全没有标签的文章

2 个答案:

答案 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_hashtagshashtagswhere条件,这些条件过滤了我们希望看到的主题标签;最终将所有带该标签的文章带给我们。无论我们要过滤多少个标签,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