2010-07-30 17 views
8

chcę funkcji wyszukiwania w mojej aplikacji dla danych jak poZapytanie znaleźć tematy w zależności od znacznika

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

Teraz kiedy szukać terminu cricket AND football O/P powinny być

topic_id 
    1 

i kiedy szukać termin cricket OR football o/P powinny być

topic_id 
    1 
    2 
    3 

próbuję coś g jak po

za i

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

za lub

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

Mój problem jest, gdy wyszukiwania użytkownika dla cricket AND football AND basketball AND chess moje pytanie staje się bardzo żałosne

jest jakieś proste rozwiązanie tego problemu . Próbowałem również dla GROUP_CONCAT ale na próżno

+0

dlaczego nie masz model działalności, w których istnieje wiele do wielu relacji między działaniami i tematy? wtedy każda czynność miałaby identyfikator i mógłbyś poprosić o numer 'topic.activities.include? (" baseball ")' –

Odpowiedz

1

Trzeba zrobić self dołączyć

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

proszę odpowiedzieć dla wyszukiwanego hasła, takiego jak 'cricket ORAZ piłka nożna i koszykówka ORAZ gra w szachy' – Salil

+0

Co takiego robisz? nie rozumiem powyższego rozwiązania. Możesz po prostu rozszerzyć go o tyle złączeń, ile chcesz dla każdego tagu. – 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 '...' 
        ) 

UPDATE:

Zadanie to może być łatwo rozwiązany z RDBMS, które wspierają operacje Wszystkie zestawy : UNION, INTERSECT i Z WYJĄTKIEM (lub MINUS)

Wtedy wszelkie warunki, takie jak:

  1. (Tag1 I tag2) OR TAG3 NOT Tag4
  2. tag1 LUB tag2
  3. Tag1 I tag2 I TAG3
  4. (tag1 I tag2) LUB (Tag3 AND Tag4)

można łatwo przekształcić w:

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 
    ) 

Prawdziwy problem, że MYSQL nie obsługuje INTERSECT, który powinien być emulowany, jak pokazano powyżej. Drugim problemem jest przestrzeganie nawiasów i priorytetów operatorów.

więc możliwe rozwiązanie bez użycia nawiasów w wyrażeniach:

  1. Zbierz wszystkie znaczniki, które przystąpiły przez warunki oraz zbudować kwerendę jako pierwszy przykład w odpowiedzi.

  2. Dodaj wszystkie tagi, które dołączyły do ​​warunku LUB (można użyć w IN lub UNION) i używając wyniku łączenia UNION.

innym podejściu możliwe tylko jeśli masz mniej ilość tag tag 64. Wtedy każdy będzie miał własny kawałek (Trzeba dodać BigInt pole „tagów” ​​do tematów stole, gdzie będzie reprezentował znaczniki w formacie binarnym) oraz użycie operacji bitowych mysql tworzy zapytanie.

Duża wada, że ​​to rozwiązanie jest ograniczone tylko do 64 znaczników.

+0

ohh! Dziękuję, że to zadziała, ale w praktyce chcę 'like' zamiast' matching cały ciąg' ex: - 'tag jak '% cricket%''. 'I myślę, że nie działa z' – Salil

+0

@Salil - Dlaczego nie? :) Dodano zapytanie do podobnego podejścia: –

+0

@ Michael Pakhantsov: - nie myślisz w swoim przykładzie 'HAVING Count (*) = zwróci 8' podczas gdy ja chcę otrzymać' 0' wierszy, ponieważ nie ma 'topic_id' z wszystkimi powyżej tagów – Salil

0

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

Niestety, lub stan jest trudniejsze. Pełne sprzężenie zewnętrzne byłoby przydatne, ale MySQL brakuje tej funkcji.

Proponuję zapewnienie, że nie masz RNO w nawiasach (nie (a OR b) AND c raczej (a AND c) OR (b AND c) i robi kwerend tak:

A lub B lub C lub (niektóre i klauzula jak D i 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 

W oprogramowaniu db inne niż MySQL można wykorzystać zapytanie prawdopodobnie (I nie mają możliwości, aby przetestować go teraz) jak ten:

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' 

, które moim zdaniem powinny reprezentować (aI b) LUB (cD). Uwaga: COALESCE, ponieważ pełne sprzężenie zewnętrzne t1.topic_id może mieć wartość null.

0

To jest rozwiązanie Railsowe, które tworzy samoczynne sprzężenia dla przypadku AND i proste zapytanie SQL dla przypadku OR. Rozwiązanie zakłada Model o nazwie TopicTag, a co za tym idzie tabelę o nazwie topic_tags.

Metoda klasy Szukaj spodziewa 2 argumenty tablicę Etykiety i łańcuch zawierający albo „i” albo „lub”

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 

Aby uzyskać trochę więcej pokrycia testowego danych została rozszerzona o dodatkowy rekord na szachy. Baza zaszczepiono Następujący kod

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

Następujący kod z badań uzyskanych wyników przedstawiono

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 

Testowane na szynach 2.3.8 wykorzystaniem SqlLite

EDIT

Jeśli chcesz użyć, jak to e OR sprawa również staje się nieco bardziej złożona. Należy również pamiętać, że używanie LIKE z wiodącym "%" może mieć znaczący wpływ na wydajność, jeśli szukana tabela ma niebagatelny rozmiar.

Następująca wersja Model używa LIKE dla obu przypadków.

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

Właśnie zdałem sobie sprawę, że chcesz używać Like na twoich tagach. Powrócę do tego –

+0

Edytowany w celu użycia LIKE –

Powiązane problemy