Difficult Rails ActiveModel Query - mysql

I am building a Rails app which has a User model, joined to a Interests model through a has_many :through relationship using a UserInterests join model.
Given a set of interests, what I want to do is find all users who have selected at least one interest in that set, OR who have not selected any interests at all. I am thinking of something a bit like this:
users = users.joins(:user_interests).where(["COUNT(user_interests) = 0 OR user_interests.interest_id IN ?", interest_ids])
This raises a Mysql syntax error. Any ideas how to achieve this?
Many thanks

try this one,
users = User.joins(:user_interests).where("COUNT(user_interests) = 0 OR interest_id IN (?)", interest_ids)

class User < ActiveRecord::Base
scope :with_interests, ->(*interests) {
joins(:user_interests).where(user_interests: {interest_id: interests.flatten.compact.uniq})
}
scope :no_interests, -> {
where("not exists (select * from user_interests ui2 where ui2.user_id = users.id)")
}
end
interest = Interest.first
User.with_interests(interest) + User.no_interests

This worked for me - although it is not 100% ideal.
Add a user_interests_count field to users with a counter_cache
Search using a LEFT JOIN query:
users = User.joins("LEFT JOIN user_interests ON user_interests.user_id = users.id").where("(users.user_interests_count = ?) OR user_interests.interest_id IN (?)", 0, interest_ids)

Related

LEFT Join onto two tables in doctrine2 query builder

I have an entity called tracking. It relates to a User and a Course.
I also have an entity called credential which officially links User and Course
User <- Tracking -> Course || User <- Credential -> Course
Tracking is a joining entity of sorts, but it's not the primary join between the two. I have a query where I already have the user and the course joined, and I want to left-join the tracking if it exists. I've tried to simplify my example.
$q->select(
'user.id',
'user.firstname',
'user.lastname',
'count(t) as courses',
'count(t.completed) as completed'
);
$q->from(Credential::class, 'c');
$q->from(Course::class, 'course');
$q->from(User::class, 'user');
$q->leftJoin(Tracking::class, 't', 'WITH', 't.user = user and t.course = course');
$q->where('c.user = user and c.object = course');
$q->groupBy('user');
What I'm trying to achieve here, is a list of users who are enrolled in courses, the number of courses, and where possible the number of completed courses.
Unfortunately, doctrine can only seem to join to either the user table or the course table, but not to both. This might even be a mysql limitation. I've debugged this over and over - and I've run into the problem several times with different examples - and I just can't seem to find a solution other than using ->from(Tracking) which would eliminate students who haven't started any courses, and stats from courses they haven't started. I've googled over and over again, but It's so hard to search for this problem and not get 'How to join two tables with Doctrine'.
I get the error Column not found: 1054 Unknown column 'c1_.id' in 'on clause' which I assume means it can join on t.user = user but not t.course = course
Here is the actual code and error
$q = $this->em->createQueryBuilder();
$q->select(
'user.id',
'user.firstname',
'user.lastname',
'count(sc.id) as courses',
'count(ct.commenced) as commenced',
'count(ct.completed) as completed',
'avg(ct.scorePercent) as avgscore',
'avg(ct.totalTime) as avgtime'
);
$q->from(Security\Credential::class, 'c');
$q->from(Security\SecurableCourse::class, 'sc');
$q->from(Security\AccreditableInheritance::class, 'ai');
$q->from(Security\AccreditableUser::class, 'au');
$q->from(User::class, 'user');
$q->join(Tracking\CourseTracking::class, 'ct', 'WITH', 'ct.objectIdentity = sc and ct.user = user');
$q->where('sc = c.securable and ai.parent = c.accreditable and au = ai.child and user = au.user');
$q->andWhere('c.action = :action and sc.course in (:courses)');
$q->setParameter('action', 'study')->setParameter('courses', $courses);
$q->groupBy('user.id');
$users = $q->getQuery()->getScalarResult();
Doctrine\DBAL\Exception\InvalidFieldNameException(code: 0): An exception occurred while executing 'SELECT u0_.id AS id_0, u0_.firstname AS firstname_1, u0_.lastname AS lastname_2, count(s1_.id) AS sclr_3, count(t2_.commenced) AS sclr_4, count(t2_.completed) AS sclr_5, avg(t2_.scorePercent) AS sclr_6, avg(t2_.totalTime) AS sclr_7 FROM Credential c3_ INNER JOIN Tracking t2_ ON (t2_.objectIdentity_id = s1_.id AND t2_.user_id = u0_.id) AND t2_.dtype IN ('coursetracking') AND ((t2_.deleted IS NULL OR t2_.deleted > '2016-04-26 08:33:31')), SecurableIdentity s1_, AccreditableInheritance a4_, AccreditableIdentity a5_, User u0_ WHERE (((s1_.id = c3_.securable_id AND a4_.parent_id = c3_.accreditable_id AND a5_.id = a4_.child_id AND u0_.id = a5_.user_id) AND (c3_.action = ? AND s1_.course_id IN (?, ?, ?))) AND ((u0_.deleted IS NULL OR u0_.deleted > '2016-04-26 08:33:31'))) AND (s1_.dtype IN ('securablecourse') AND a5_.dtype IN ('accreditableuser')) GROUP BY u0_.id' with params [\"study\", \"46\", \"45\", \"160\"]:\n\nSQLSTATE[42S22]: Column not found: 1054 Unknown column 's1_.id' in 'on clause'
This is just a hint how to achieve it. I cannot give you the correct answer as you don't give enough details. But this will help you to achieve what you need.
$q->select(u, t, co, ce);
$q->from('User', 'u');
$q->leftJoin('u.tracking', 't');
$q->leftJoin('t.course', 'co');
$q->leftJoin('u.credential', 'ce');

Group by one column and sum by another

Trying to write rake task that contains a query that will group by one value on a join table and then sum another column. I'd like to do it using the query interface. Purpose of this task is to find the videos that have been the most popular over the last 5 days.
In pertinent part:
course_ids = Course.where(school_id: priority_schools).pluck(:id)
sections = Section.where(course_id: course_ids)
sections.each do |section|
users = section.users.select {|user| user.time_watched > 0}
user_ids = []
users.each { |user| user_ids << user.id }
user_videos = UserVideo.group(:video_id).
select(:id, :video_id, :time_watched).
where("created_at > ?", Date.today - 5.days).
where(user_id: user_ids).sum(:time_watched)
p "user_videos: #{user_videos.inspect}"
end
Any suggestions for the how / the best way to write this query?

CakePHP Using "Group By" in Virtual Field Not Working

I have the following virtual field on my Page model
function __construct($id = false, $table = null, $ds = null) {
$this->virtualFields['fans'] = 'SELECT COUNT(Favorite.id) FROM favorites AS Favorite WHERE Favorite.page_id = Page.id AND Favorite.status = 0';
parent::__construct($id, $table, $ds);
}
This works as expected and displays the number of users who have added the page to their favorites. The issue is that, during development, some rows have duplicate user_id to page_id pairs so it returns the incorrect number or unique users. I tried adding a group by clause like so
$this->virtualFields['fans'] = 'SELECT COUNT(Favorite.id) FROM favorites AS Favorite WHERE Favorite.page_id = Page.id AND Favorite.status = 0 GROUP BY Favorite.user_id';
But it does not work. I tried debugging the issue but I receive the error message "allowed memory size exhausted". I also tried using SELECT COUNT('Favorite.user_id') and SELECT DISTINCT('Favorite.user_id') neither of which worked either. I believe DISTINCT is further away from the answer as that would return an array (I believe?)
Is this a known CakePHP issue? Am I implementing the group by wrong? Is there another solution to do this other than afterfind?
try this
SELECT COUNT(DISTINCT Favorite.user_id)
like that :
$this->virtualFields['fans'] = 'SELECT COUNT(DISTINCT id) FROM favorites WHERE status = 0';

Adding *1 to addAttributeToSort not working

I'm working within a product collection returning products and trying to order them. The problem is one of my product attributes (I find this out at 90% of the way through my project) is a quantity, i.e. 250, 5000 etc. However, I've just found out that despite these being numbers Magento treats them as strings, so therefore the collection returns the following quantities in this example:
50,100,250,500,1000,2000,5000
However, addAttributeToSort('quantity','ASC'); does this:
100,1000,2000,250,50,500,5000
I've done a var_dump() on the collection and ascertained that the values are being treated as strings, hence why this is probably happening. Unfortunately I've got over 6000 products with a lot of custom implementations and configurable products depending on this attribute, so am reluctant to change it. Searching on here I found that adding ORDER BY 'quantity' *1 does actually perform the sort correctly, however I can't seem to implement this clause in the standard addAttributeToSort function.
If anyone could help me implement this, I've tried addAttributeToSort('quantity','*1'); but that doesn't work, just errors.
Many thanks
UPDATE:
Here's the syntax for the query which is generated from the following code:
$collection = $this->getUsedProductCollection($product)
->addAttributeToSelect('*')
->addFieldToFilter('name', array( 'like' => '%' . $stock . '%' ));
$collection->getSelect()->order(new Zend_Db_Expr('quantity' *1));
count($collection);
'SELECT 'e'.*, 'link_table'.'parent_id', IF(at_name.value_id > 0, at_name.value, at_name_default.value) AS 'name', 'price_index'.'price', 'price_index'.'tax_class_id', 'price_index'.'final_price', IF(price_index.tier_price IS NOT NULL, LEAST(price_index.min_price, price_index.tier_price), price_index.min_price) AS 'minimal_price', 'price_index'.'min_price', 'price_index'.'max_price', 'price_index'.'tier_price' FROM 'catalog_product_entity' AS 'e' INNER JOIN 'catalog_product_super_link' AS 'link_table' ON link_table.product_id = e.entity_id INNER JOIN 'catalog_product_website' AS 'product_website' ON product_website.product_id = e.entity_id AND product_website.website_id = '1' INNER JOIN 'catalog_product_entity_varchar' AS 'at_name_default' ON ('at_name_default'.'entity_id' = 'e'.'entity_id') AND ('at_name_default'.'attribute_id' = '65') AND 'at_name_default'.'store_id' = 0 LEFT JOIN 'catalog_product_entity_varchar' AS 'at_name' ON ('at_name'.'entity_id' = 'e'.'entity_id') AND ('at_name'.'attribute_id' = '65') AND ('at_name'.'store_id' = 1) INNER JOIN 'catalog_product_index_price' AS 'price_index' ON price_index.entity_id = e.entity_id AND price_index.website_id = '1' AND price_index.customer_group_id = 0 WHERE (link_table.parent_id = 3781) AND (IF(at_name.value_id > 0, at_name.value, at_name_default.value) LIKE '%PCL Labels%')'
try
$collection->getSelect()->order(new Zend_Db_Expr('quantity' *1));
In the end I achieved this in PHP via ksort(). The database model when implementing any Zend functions was being overridden somewhere and I couldn't afford the time to figure it out.

Django ORM Creates Phantom Alias in SQL Join

I am executing the following code (names changed to protect the innocent, so the model structure might seem weird):
memberships =
models.Membership.objects.filter(
degree__gt=0.0,
group=request.user.get_profile().group
)
exclude_count =
memberships.filter(
member__officerships__profile=request.user.get_profile()
).count()
if exclude_officers_with_profile:
memberships = memberships.exclude(
member__officerships__profile=request.user.get_profile()
)
total_count = memberships.count()
which at this point results in:
OperationalError at /
(1054, "Unknown column 'U1.id' in 'on clause'")
The SQL generated is:
SELECT
COUNT(*)
FROM
`membership`
WHERE (
`membership`.`group_id` = 2 AND
`membership`.`level` > 0.0 AND
NOT (
`membership`.`member_id`
IN (
SELECT
U2.`member_id`
FROM
`membership` U0 INNER JOIN `officers` U2
ON (U1.`id` = U2.`member_id`)
WHERE U2.`profile_id` = 2
)
)
)
It appears that the SQL Join's ON statement is referencing an alias that hasn't been defined. Any ideas why!? I dropped my MySQL database and re-synced the tables from my models to try and ensure that there weren't any inconsistencies there.
The structure of the models I'm using are:
class Membership(models.Model):
member = models.ForeignKey(Member, related_name='memberships')
group = models.ForeignKey(Group, related_name='memberships')
level = models.FloatField(default=0.0)
class Member(models.Model):
name = models.CharField(max_length=32)
class Officer(models.Model):
member = models.ForeignKey(Member, related_name='officerships')
profile = models.ForeignKey(UserProfile)
class UserProfile(models.Model)
group = models.ForeignKey(Group)
class Group(models.Model)
pass
I think this may be a symptom of:
http://code.djangoproject.com/ticket/9188
which was fixed as of django revision 9589, I think. Now how to figure out which revision I'm working from...
Confirmed. When I implemented the change referenced in the ticket above:
http://code.djangoproject.com/changeset/9589
my error went away.