2 votes

Rails + MySQL, Intersection de la relation Many to Many

J'ai des sites et des étiquettes dans une relation many to many, reliés par la table de jointure SitesTags :

Site
  has_and_belogs_to_many :tags
  id name
  1  siteA
  2  siteB

Tag
  # has_and_belogs_to_many :sites
  id name
  1  tagA
  2  tagB
  3  tagC

SitesTags
  site_id tag_id
  1       1
  1       2
  2       2
  2       3

Je voudrais obtenir le nombre de balises que deux sites ont en commun. Dans cet exemple, il y aurait un tag commun au siteA et au siteB (tagB).

Idéalement, je voudrais une solution au niveau des bases de données, mais j'utilise MySQL. J'ai essayé (Site.find(1).tags & Site.find(2).tags).count mais je peux voir que cela fait des requêtes multiples, et il n'utilise pas COUNT(*) mais récupère toutes les données :

Site Load (0.3ms)  SELECT  `sites`.* FROM `sites` WHERE `sites`.`id` = 1 LIMIT 1
Site Load (0.3ms)  SELECT  `sites`.* FROM `sites` WHERE `sites`.`id` = 2 LIMIT 1
Tag Load (0.3ms)  SELECT `tags`.* FROM `tags` INNER JOIN `sites_tags` ON `tags`.`id` = `sites_tags`.`tag_id` WHERE `sites_tags`.`site_id` = 1
Tag Load (0.4ms)  SELECT `tags`.* FROM `tags` INNER JOIN `sites_tags` ON `tags`.`id` = `sites_tags`.`tag_id` WHERE `sites_tags`.`site_id` = 2

Une autre chose que j'ai essayé est

Site.find(1).tags.where("`sites_tags`.`site_id` = 2")

qui génère

SELECT `tags`.* FROM `tags` INNER JOIN `sites_tags` ON `tags`.`id` = `sites_tags`.`tag_id` WHERE `sites_tags`.`site_id` = 1 AND (`sites_tags`.`site_id` = 2)

Cela ne fonctionne pas, je pense qu'il essaie de trouver un seul enregistrement où le site_id est 1 ET 2.

0voto

puneet18 Points 3600

Pour obtenir le compte, essayez ceci :

Site.find(1).tags.count

Pour obtenir le nombre de tags communs dans site1 y site2 :

s1 = Site.find(1).tags.map(&:name)

s2 = Site.find(2).tags.map(&:name)

common_tags s1 & s2

0voto

Gerry Points 8271

Pour une solution au niveau de la base de données, vous pourriez utiliser le langage SQL brut, par exemple, pour obtenir le nombre de balises communes :

sql = <<~SQL
  SELECT COUNT(DISTINCT(a.tag_id))
  FROM sites_tags a JOIN sites_tags b ON a.tag_id = b.tag_id
  WHERE a.site_id != b.site_id
  AND a.site_id IN (1, 2);
SQL

count = ActiveRecord::Base.connection.select_rows(sql).flatten
#=> [1]

Ou, si vous souhaitez obtenir un tableau contenant le nom de toutes les balises communes (et les compter plus tard), vous pouvez utiliser cette requête :

sql = <<~SQL
  SELECT DISTINCT(c.name)
  FROM sites_tags a
    JOIN sites_tags b ON a.tag_id = b.tag_id
    JOIN tags c ON a.tag_id = c.id
  WHERE a.site_id != b.site_id
  AND a.site_id IN (1, 2);
SQL

tags = ActiveRecord::Base.connection.select_rows(sql).flatten
#=> ["tagB"]

Les deux fonctionneront avec MySQL.

0voto

s1mpl3 Points 1241

Utiliser la fusion

Site.find(1).tags.merge(Site.find(2).tags).count

Il le fera en 3 requêtes efficaces

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X