2010-07-30 13 views
8

quiero una funcionalidad de búsqueda en mi solicitud de los datos como la siguientede consulta para buscar temas en función de la etiqueta

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

Ahora cuando la búsqueda de término cricket AND football O/P debe ser

topic_id 
    1 

y cuando la búsqueda de término cricket OR football O/P debe ser

topic_id 
    1 
    2 
    3 

intento algo g como seguir

PARA Y

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

PARA O

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

Mi problema es cuando la búsqueda de usuario para el cricket AND football AND basketball AND chess mi consulta se vuelve muy patética

¿hay alguna solución sencilla para este . También probé para GROUP_CONCAT pero en vano

+0

¿por qué no tener un modelo de actividad donde hay una relación muchos a muchos entre las actividades y temas? entonces cada actividad tendría una identificación y usted podría pedir 'topic.activities.include? (" baseball ")' –

Respuesta

1

Es necesario hacer un auto unirse

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

responda por el término de búsqueda como 'grillo Y fútbol Y baloncesto Y ajedrez' – Salil

+0

¿Qué es lo que hace no entiendo sobre la solución anterior. Puede extenderlo con tantas combinaciones como desee para cada etiqueta. – bradgonesurfing

4
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 '...' 
        ) 

ACTUALIZACIÓN:

Esta tarea puede ser fácil resolver con RDBMS que apoyan las operaciones de todos los conjuntos : UNION, INTERSECT y SALVO (o MENOS)

entonces cualquier condiciones como:

  1. (Tag1 y etiqueta 2) O Entrada3 NO etiqueta 4
  2. Tag1 O Etiqueta2
  3. Tag1 y etiqueta 2 Y Entrada3
  4. (Tag1 y etiqueta 2) OR (Entrada3 Y TAG4)

se pueden transformar fácilmente en:

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 
    ) 

El problema real que MYSQL no admite INTERSECT, que debe emularse como se muestra arriba. El segundo problema es respetar los corchetes y las precedencias del operador.

Así posible solución sin utilizar paréntesis en las expresiones:

  1. Recoge todas las etiquetas que se unieron por y Condiciones y Generar consulta como primer ejemplo de respuesta.

  2. Agregue todas las etiquetas que se hayan unido a la condición O (se pueden usar IN o UNION) y utilizando el resultado de la combinación UNION.

Otro enfoque posible sólo si tiene menos cantidad de etiquetas 64. A continuación, cada etiqueta tendrá propia bits (Usted necesitará agregar 'etiquetas' de campo bigint en la tabla de temas, donde será representado etiquetas en formato binario) y por usando operaciones de bit mysql crea consulta.

Gran desventaja de que esta solución está limitada solo para 64 etiquetas.

+0

¡ohh !, gracias, funcionará, pero en la práctica quiero 'like' en lugar de' matching whole string' ex: - 'tag like '% cricket%''. 'Y creo que no está trabajando con ' – Salil

+0

@Salil - ¿Por qué no? :) Se agregó una consulta para un acercamiento similar –

+0

@ Michael Pakhantsov: - ¿No crees en tu ejemplo 'TENER Count (*) = devolverá 8', mientras que quiero resultado' 0' filas ya que no hay 'topic_id' con todo el etiquetas anteriores – Salil

0

A y B y C y 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' 

Desafortunadamente, o condición es más difícil. La combinación externa completa sería útil, pero a MySQL le falta esa función.

Sugiero asegurando que no tiene OR entre paréntesis (no (a OR b) AND c, en lugar (a AND c) OR (b AND c) y consulta haciendo así:

A o B o C o (algunos y cláusula como D y 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 

En el software db distinto a MySQL que podría utilizar consulta probablemente (no tengo medios para probarlo ahora mismo) como éste:

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' 

que creo que debería representar (a AND b) O (c AND d). Nota COALESCE, debido a la combinación externa completa t1.topic_id podría ser nula.

0

Esta es una solución de Rails que crea combinaciones autorreferenciales para el caso AND y un simple SQL para el caso OR. La solución supone un Modelo llamado TopicTag y, en consecuencia, una tabla llamada topic_tags.

El método de la clase Búsqueda espera 2 argumentos una matriz de etiquetas y una cadena que contiene ya sea "y" u "o"

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 

Con el fin de conseguir un poco más cobertura de la prueba los datos se amplió para incluir un extra registro de ajedrez. La base de datos se sembró con el código siguiente

[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')} 

El siguiente código de prueba produjo los resultados mostrados

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 

Probado en Rails 2.3.8 utilizando SqlLite

EDITAR

Si desea utilizar como entonces th La caja e OR también se vuelve un poco más compleja. También debe tener en cuenta que usar LIKE con un '%' inicial podría tener un impacto significativo en el rendimiento si la tabla que está buscando es de un tamaño no trivial.

La siguiente versión del modelo usa LIKE para ambos casos.

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

Me acabo de dar cuenta de que desea utilizar Me gusta en sus etiquetas. Volveré a visitar este –

+0

Editado para usar LIKE –

Cuestiones relacionadas