I have Sites and Tags in a many to many relationship, connected by join table 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
I would like to get the COUNT of tags that two sites have in common. In this example, there would be one common tag of siteA and siteB (tagB).
Ideally I would like a solution on the Databases level, but I'm using MySQL. I have tried (Site.find(1).tags & Site.find(2).tags).count
but I can see that this is doing multiple queries, and it's not using COUNT(*) but fetching all Data:
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
Another thing I have tried is
Site.find(1).tags.where("`sites_tags`.`site_id` = 2")
which is generating
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)
This doesn't work, I think it's trying to find a single record where the site_id is 1 AND 2
To get count try this:
Site.find(1).tags.count
To get tags count common in site1
and site2
:
s1 = Site.find(1).tags.map(&:name)
s2 = Site.find(2).tags.map(&:name)
common_tags s1 & s2
For a solution on the Database level you could use raw sql, for example, to get count of common tags:
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]
Or, if you would like an array with the name of all common tags (and count them later), you could use this query:
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"]
Both will work with MySQL.
Use merge
Site.find(1).tags.merge(Site.find(2).tags).count
It will do it in 3 efficient queries
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.