1

Ich habe drei Tabellen offers, sports und die Join-Tabelle offers_sports.SQL, wo verbundener Satz alle Werte enthalten muss, aber mehr enthalten kann

class Offer < ActiveRecord::Base 
    has_and_belongs_to_many :sports 
end 

class Sport < ActiveRecord::Base 
    has_and_belongs_to_many :offers 
end 

ich wählen möchten bietet das eine gegebene Reihe von Sportnamen enthalten. Sie müssen enthalten alle sports aber können mehr haben.

Lets sagen, ich habe diese drei Angebote:

light: 
    - "Yoga" 
    - "Bodyboarding" 
medium: 
    - "Yoga" 
    - "Bodyboarding" 
    - "Surfing" 
all: 
    - "Yoga" 
    - "Bodyboarding" 
    - "Surfing" 
    - "Parasailing" 
    - "Skydiving" 

Da das Array ["Bodyboarding", "Surfing"] Ich möchte medium und all aber nicht light zu bekommen.

Ich habe etwas entlang der Linien von this answer versucht, aber ich bekomme keine Zeilen im Ergebnis:

Offer.joins(:sports) 
    .where(sports: { name: ["Bodyboarding", "Surfing"] }) 
    .group("sports.name") 
    .having("COUNT(distinct sports.name) = 2") 

zu SQL übersetzt:

SELECT "offers".* 
FROM "offers" 
INNER JOIN "offers_sports" ON "offers_sports"."offer_id" = "offers"."id"  
INNER JOIN "sports" ON "sports"."id" = "offers_sports"."sport_id" 
    WHERE "sports"."name" IN ('Bodyboarding', 'Surfing') 
GROUP BY sports.name 
HAVING COUNT(distinct sports.name) = 2; 

Eine Antwort Active wäre schön, aber ich werde Begleichen Sie nur SQL, vorzugsweise Postgres-kompatibel.

Daten:

offers 
====================== 
id | name 
---------------------- 
1 | light 
2 | medium 
3 | all 
4 | extreme 

sports 
====================== 
id | name 
---------------------- 
1 | "Yoga" 
2 | "Bodyboarding" 
3 | "Surfing" 
4 | "Parasailing" 
5 | "Skydiving" 

offers_sports 
====================== 
offer_id | sport_id 
---------------------- 
1  | 1 
1  | 2 
2  | 1 
2  | 2 
2  | 3 
3  | 1 
3  | 2 
3  | 3 
3  | 4 
3  | 5 
4  | 3 
4  | 4 
4  | 5 
+0

Funktioniert die SQL? Es sieht korrekt aus. –

+0

Es gibt 0 Zeilen zurück. @GordonLinoff – max

+0

Sie sind fast da: gruppieren Sie nach den "Angeboten" -Feldern (ersetzen Sie die * durch explizite Feldnamen und wiederholen Sie diese in der Gruppe durch). Sie gruppieren jetzt nach sports.name und natürlich ist die eindeutige Anzahl von sports.name pro sport.name immer 1. –

Antwort

2

Gruppe von offer.id, nicht von sports.name (oder sports.id):

SELECT o.* 
FROM sports  s 
JOIN offers_sports os ON os.sport_id = s.id 
JOIN offers  o ON os.offer_id = o.id 
WHERE s.name IN ('Bodyboarding', 'Surfing') 
GROUP BY o.id -- !! 
HAVING count(*) = 2; 

die typische Implementierung Unter der Annahme:

  • offer.id und sports.id werden als primäre definiert Schlüssel.
  • sports.name ist eindeutig definiert.
  • (sport_id, offer_id) in offers_sports ist eindeutig definiert (oder PK).

Sie brauchen nicht DISTINCT in der Zählung. Und count(*) ist sogar noch ein bisschen billiger.

Verwandte Antwort mit einem Arsenal von möglichen Techniken:


Hinzugefügt von @Max (die OP) - das ist die obige Abfrage rollte in Active:

class Offer < ActiveRecord::Base 
    has_and_belongs_to_many :sports 
    def self.includes_sports(*sport_names) 
    joins(:sports) 
     .where(sports: { name: sport_names }) 
     .group('offers.id') 
     .having("count(*) = ?", sport_names.size) 
    end 
end 
+0

Brilliant. Funktioniert genau wie angekündigt! – max

+0

Guter Fang. . . –

1

Eine Möglichkeit ist die Verwendung von Arrays und array_agg Aggregatfunktion.

SELECT "offers".*, array_agg("sports"."name") as spnames 
FROM "offers" 
INNER JOIN "offers_sports" ON "offers_sports"."offer_id" = "offers"."id"  
INNER JOIN "sports" ON "sports"."id" = "offers_sports"."sport_id" 
GROUP BY "offers"."id" HAVING array_agg("sports"."name")::text[] @> ARRAY['Bodyboarding','Surfing']::text[]; 

Rückkehr:

id | name |      spnames      
----+--------+--------------------------------------------------- 
    2 | medium | {Yoga,Bodyboarding,Surfing} 
    3 | all | {Yoga,Bodyboarding,Surfing,Parasailing,Skydiving} 
(2 rows) 

Die @> Operator bedeutet, dass die Anordnung auf der linken Seite auf der rechten Seite alle Elemente von der einen enthalten muß, aber mehr enthalten. Die Spalte ist nur zur Show, aber Sie können es sicher entfernen.

Es gibt zwei Dinge, auf die Sie achten müssen.

  1. Auch bei Postgres 9.4 (ich habe 9.5 noch nicht ausprobiert) Typ Umwandlung Arrays für den Vergleich ist schlampig und oft Fehler heraus, Sie sagen, es kann nicht einen Weg finden, um sie zu vergleichbaren Werten zu konvertieren, so wie Sie können im Beispiel sehen, dass ich beide Seiten manuell mit ::text[] gegossen habe.

  2. Ich habe keine Ahnung, was die Unterstützung für Array-Parameter ist Ruby, noch das RoR-Framework, so dass Sie am Ende müssen manuell die Strings (bei Eingabe durch den Benutzer) und bilden das Array mit der ARRAY[] Syntax.

+0

Es funktioniert und es ist keine schlechte Antwort - aber Erwins war viel einfacher zu integrieren. Sie müssten im Grunde die having-Klausel als Zeichenfolge generieren und die Werte binden, um sie zu umgehen. Nicht wirklich schwer, aber es ist immer noch schwierig, die Assoziation zu bekommen. Vielen Dank! – max

Verwandte Themen