Как сделать мой метод запросов к БД в Rails более эффективным - PullRequest
0 голосов
/ 28 сентября 2018

Я делаю запрос к моей базе данных POSTGRESQL.В моем приложении есть статьи, а в статьях может быть несколько хэштегов.Эти отношения сохраняются в объединенной таблице хэштегов и статей.

У меня есть рабочий метод, который возвращает мне статьи, имеющие определенные хэштеги, или возвращает все статьи, которые не содержат определенные хэштеги

  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)

И это дало бы мне все статьи, у которых есть один из тех хэштегов, связанных с

Или я мог бы так назвать

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 или 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

Мне все еще не хватает статей, у которых вообще нет НИКАКИХ хэштегов, если, к сожалению, incude неверно

Ответы [ 2 ]

0 голосов
/ 28 сентября 2018

Вы правы насчет

Не думаю, что это очень эффективно, так как я много делаю в 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).

Простая и простая: мы берем данные из таблицы articles, используя 2 внутренних соединения с условиями 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

Приведенный выше метод вернет массив статей, соответствующих вашим условиям, пустым или нет.

0 голосов
/ 28 сентября 2018

Попробуйте это:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...