Запрос, чтобы найти темы в зависимости от тега - PullRequest
8 голосов
/ 30 июля 2010

Мне нужна функция поиска в моем приложении для данных, подобных следующим

topic_id   tag
1          cricket
1          football
2          football
2          basketball
3          cricket
3          basketball
4          chess
4          basketball

Теперь, когда я ищу термин cricket AND football o / p должно быть

 topic_id
    1

и когда я ищу термин cricket OR football o / p должно быть

 topic_id
    1
    2
    3

я пытаюсь что-то вроде следующего

ДЛЯ И

  select topic_id from table_name where tag like "%cricket%" and topic_id in (select topic_id from table_name where tag like "%football%")

ЗА ИЛИ

 select topic_id from table_name where tag like "%cricket%" OR tag like "%football%"

Моя проблема в том, что пользовательский поиск для cricket AND football AND basketball AND chess моего запроса становится очень жалким

есть ли простое решение для этого. Я также пытался для GROUP_CONCAT, но тщетно

Ответы [ 4 ]

4 голосов
/ 30 июля 2010
 SELECT TopicId
 FROM Table
 WHERE Tag IN ('cricket', 'football', 'basketball', 'chess')
 GROUP By TopicId
 HAVING Count(*) = 4

  4 is magic number - its a length of your AND list.

 FOR cricket AND football

 it will be 2:

 SELECT TopicId
 FROM Table
 WHERE Tag IN ('cricket', 'football')
 GROUP By TopicId
 HAVING Count(*) = 2

 if you want use 'like' statement:

 SELECT TopicId
 FROM Table
 WHERE Tag IN (SELECT distinct Tag from Table Where Tag like '...'
                OR Tag like '...'
                OR Tag like '...'
                OR Tag like '...'
              )
 GROUP By TopicId
 HAVING Count(*) = (SELECT COUNT(distinct Tag) from Table 
                    Where Tag like '...'
                       OR Tag like '...' 
                       OR Tag like '...'
                       OR Tag like '...'
                   )

ОБНОВЛЕНИЕ:

Эта задача может быть легко решена с помощью СУБД, которая поддерживает все операции наборов: UNION , INTERSECT и ИСКЛЮЧИТЬ (или МИНУС )

Тогда любые условия, такие как:

  1. (Tag1 И Tag2) ИЛИ Tag3 НЕ Tag4
  2. Tag1 ИЛИ Tag2
  3. Tag1 И Tag2 И Tag3
  4. (Tag1 AND Tag2) ИЛИ (Tag3 AND Tag4)

могут быть легко преобразованы в:

1. (Select * ... Where Tag = Tag1
    INTERSECT
    Select * ... Where Tag = Tag2
   )
   UNION
   (Select * ... Where Tag = Tag3)
   EXCEPT
   (Select * ... Where Tag = Tag4)

2. Select * ... Where Tag = Tag1
   UNION
   Select * ... Where Tag = Tag2

3. Select * ... Where Tag = Tag1
   INTERSECT
   Select * ... Where Tag = Tag2
   INTERSECT
   Select * ... Where Tag = Tag3

 4.(Select * ... Where Tag = Tag1
    INTERSECT
    Select * ... Where Tag = Tag2
   )
   UNION
   (Select * ... Where Tag = Tag1
    INTERSECT
    Select * ... Where Tag = Tag2
   )

Реальная проблема в том, что MYSQL не поддерживает INTERSECT, который должен эмулироваться, как показано выше.Вторая проблема - соблюдение скобок и приоритетов операторов.

Возможное решение без использования скобок в выражениях:

  1. Соберите все теги, которые объединены условиями AND, и создайте запрос в качестве первого примера.в ответ.

  2. Добавьте все теги, которые присоединились к условию ИЛИ (можно использовать IN или UNION) и с помощью результата объединения UNION.

Возможен только другой подходесли у вас количество тегов меньше 64. Тогда у каждого тега будет свой собственный бит (вам нужно будет добавить поле bigint 'tags' в таблицу тем, где будут представлены теги в двоичном формате) и с помощью операций с битами mysql создать запрос.

Большой недостаток в том, что это решение ограничено только 64 тегами.

1 голос
/ 30 июля 2010

Вам нужно самостоятельно присоединиться

select distinct topic_id from 
table_name as t1
join
table_name as t2 
on 
t1.topic_id = t2.topic_id
and
t1.tag = "cricket"
and
t2.tag = "football"
0 голосов
/ 10 августа 2010

Это решение Rails, которое создает самообращающиеся соединения для случая AND и простое включение SQL для случая OR. В решении используется Модель с именем TopicTag и, следовательно, таблица с именем topic_tags.

Метод класса Search ожидает 2 аргумента: массив тегов и строку, содержащую либо «и», либо «или»

class TopicTag < ActiveRecord::Base

  def self.search(tags, andor)

    # Ensure tags are unique or you will get duplicate table names in the SQL
    tags.uniq!

    if andor.downcase == "and"
      first = true
      sql = ""

      tags.each do |tag|
        if first
          sql = "SELECT DISTINCT topic_tags.topic_id FROM topic_tags "
          first = false
        else
          sql += " JOIN topic_tags as tag_#{tag} ON tag_#{tag}.topic_id = \
                   topic_tags.topic_id AND tag_#{tag}.tag = '#{tag}'"
        end
      end
      sql += " WHERE topic_tags.tag = '#{tags[0]}'"
      TopicTag.find_by_sql(sql)

    else
      TopicTag.find(:all, :select => 'DISTINCT topic_id', 
          :conditions => { :tag => tags})
    end
  end

end

Чтобы получить больше тестового покрытия, данные были расширены, чтобы включить дополнительную запись для шахмат. База данных была заполнена следующим кодом

[1,2].each   {|i| TopicTag.create(:topic_id => i, :tag => 'football')}
[1,3].each   {|i| TopicTag.create(:topic_id => i, :tag => 'cricket')}
[2,3,4].each {|i| TopicTag.create(:topic_id => i, :tag => 'basketball')}
[4,5].each   {|i| TopicTag.create(:topic_id => i, :tag => 'chess')}

Следующий тестовый код дал показанные результаты

tests = [
  %w[football cricket],
  %w[chess],
  %w[chess cricket basketball]
]

tests.each do |test|
  %w[and or].each do |op|
    puts test.join(" #{op} ") + " = " + 
      (TopicTag.search(test, op).map(&:topic_id)).join(', ')
  end
end
football and cricket = 1
football or cricket = 1, 2, 3
chess = 4, 5
chess = 4, 5
chess and cricket and basketball = 
chess or cricket or basketball = 1, 2, 3, 4, 5

Протестировано на Rails 2.3.8 с использованием SqlLite

EDIT

Если вы хотите использовать подобное, то чехол OR также становится немного более сложным. Вы также должны знать, что использование LIKE с лидирующим символом «%» может значительно повлиять на производительность, если таблица, которую вы ищете, имеет нетривиальный размер.

Следующая версия модели использует LIKE для обоих случаев.

class TopicTag < ActiveRecord::Base

  def self.search(tags, andor)

    tags.uniq!

    if andor.downcase == "and"
      first = true
      first_name = ""
      sql = ""

      tags.each do |tag|
        if first
          sql = "SELECT DISTINCT topic_tags.topic_id FROM topic_tags "
          first = false
        else
          sql += " JOIN topic_tags as tag_#{tag} ON tag_#{tag}.topic_id = \    
                  topic_tags.topic_id AND tag_#{tag}.tag like '%#{tag}%'"
        end
      end
      sql += " WHERE topic_tags.tag like '%#{tags[0]}%'"
      TopicTag.find_by_sql(sql)

    else
      first = true
      tag_sql = ""
      tags.each do |tag| 
        if first
          tag_sql = " tag like '%#{tag}%'" 
          first = false
        else
          tag_sql += " OR tag like '%#{tag}%'" 
        end
      end
      TopicTag.find(:all, :select => 'DISTINCT topic_id', 
            :conditions => tag_sql)
    end
  end

end

tests = [
  %w[football cricket],
  %w[chess],
  %w[chess cricket basketball],
  %w[chess ll],
  %w[ll]
]

tests.each do |test|
  %w[and or].each do |op|
    result = TopicTag.search(test, op).map(&:topic_id)
    puts ( test.size == 1 ? "#{test}(#{op})" : test.join(" #{op} ") ) + 
         " = " + result.join(', ')
  end
end
football and cricket = 1
football or cricket = 1, 2, 3
chess(and) = 4, 5
chess(or) = 4, 5
chess and cricket and basketball = 
chess or cricket or basketball = 1, 2, 3, 4, 5
chess and ll = 4
chess or ll = 1, 2, 3, 4, 5
ll(and) = 1, 2, 3, 4
ll(or) = 1, 2, 3, 4
0 голосов
/ 05 августа 2010

a И b И c И d:

SELECT t1.topic_id
FROM tags_table AS t1
INNER JOIN tags_table AS t2
ON t2.topic_id = t1.topic_id AND t2.tag = 'b'
INNER JOIN tags_table AS t3
ON t3.topic_id = t1.topic_id AND t3.tag = 'c'
INNER JOIN tags_table AS t4
ON t4.topic_id = t1.topic_id AND t4.tag = 'd'
WHERE t1.tag = 'a'

К сожалению, условие ИЛИ сложнее.Полное внешнее объединение было бы удобно, но в MySQL эта функция отсутствует.

Я предлагаю убедиться, что у вас нет OR в круглых скобках (не (a OR b) AND c, а скорее (a AND c) OR (b AND c), и выполнять запрос следующим образом:

a ИЛИ b ИЛИ c ИЛИ (некоторые и такие как d и e):

SELECT DISTINCT topic_id FROM (
  SELECT topic_id FROM tags_table where tag = 'a'
  UNION ALL
  SELECT topic_id FROM tags_table where tag = 'b'
  UNION ALL
  SELECT topic_id FROM tags_table where tag = 'c'
  UNION ALL
  query_like_the_previous_one_represinting_some_AND_clause
) as union_table

В программном обеспечении БД, отличном от MySQL, вы можете использовать запрос , вероятно, (у меня нет средств дляпроверьте это прямо сейчас) вот так:

SELECT COALESCE(t1.topic_id, t2.topic_id, t3.topic_id, ...)
FROM tags_table AS t1
INNER JOIN tags_table AS t2
ON t2.topic_id = t1.topic_id AND t2.tag = 'b'
FULL OUTER JOIN tags_table AS t3
ON t3.topic_id = t1.topic_id AND t3.tag = 'c'
INNER JOIN tags_table AS t4
ON t4.topic_id = t1.topic_id AND t4.tag = 'd'
WHERE t1.tag = 'a'

, который, как я считаю, должен представлять (a AND b) ИЛИ (c AND d). Примечание COALESCE, поскольку полное внешнее объединение t1.topic_id может бытьнуль.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...