Doctrine ORM - Calculate non-mapped property in query - doctrine-orm

I have two relevant entities - Thread and Reply, with a user able to post replies to threads.
When I return a list of thread entities using the ORM QueryBuilder I'd like to also include a boolean flag indicating whether the current user has posted a reply to that thread. Initially I thought about adding a property to the Thread entity and somehow setting that in a query, but it doesn't feel like the Thread should need to be aware of users posting replies. What is the best way to deal with this, ideally preventing the need to perform a secondary query per Thread returned?

You could use left join with:
$qb = $em->getRepository(Thread::class)->createQueryBuilder('a');
$qb->addSelect('count(r)');
$qb->leftJoin('a.replies', 'r' , 'WITH', 'r.user = :userId');
$qb->groupBy('a');
$qb->setParameter('userId', 8);
$result = $qb->getQuery()->getResult();
Using count(r) you will get how many replies the user has posted per thread. You will have to loop through the result to check whether the count is > 0.
foreach($result as $row) {
$thread = $row[0];
$hasReplied = ($row[1] > 0 ? true: false);
}

Related

web2py update_or_insert to increment a counter

I have a web2py-defined database table which is used to count the number of times a request has been made by a particular user - so it has integer columns 'user_id' and 'n_req'. I don't want to populate this with a zero for each possible user. Instead, I want to check if a row with the user_id exists and if so, increment the 'n_req' by one, otherwise create it with an initial value of 'n_req' of 1. To avoid race conditions, I'd like to do this using a single update_or_insert call, e.g.
db.count_table.update(db.count_table.user_id == uid, user_id = uid, n_req = n_req + 1)
I presume I can't do this, however, as it is using the pre-existing value of n_req when incrementing. So how do I tell the DAL the initial value for n_req. Can I do, for example, n_req = (n_req || 0) + 1?
I don't think you'll be able to use the .update_or_insert method in this case because the value of n_rec is conditional on whether a record is found. Instead, you can do something like this:
db.define_table('count_table',
Field('user_id', 'integer', unique=True),
Field('n_rec', 'integer', default=1))
def update_count(user_id):
return db(db.count_table.user_id == user_id).update(n_rec=db.count_table.n_rec + 1)
if not update_count(uid):
try:
db.count_table.insert(user_id=uid)
except db._adapter.driver.IntegrityError:
update_count(uid)
Note that the value of n_rec in .update is set to db.count_table.n_rec + 1, which will translate to a SQL statement that will let the database increment the existing value rather than explicitly providing the final value yourself. This should avoid race conditions in case two requests are updating the count at the same time.
Also, note there is a unique constraint on the user_id field. This will prevent a race condition from allowing two records to be created for the same user. In that case, the try/except will catch the resulting IntegrityError, and an update will be made on the existing record.

Populate Model from a Query with a One-to-Many Relationship

I have two tables:
surveyTemplateHeader
surveyTemplateQuestions
What I was looking to do was get the information from both tables:
var q = dao.getSurveyTemplateDetails( surveyTemplateID = ARGUMENTS.surveyTemplateID );
Then use Coldbox to populate my model, surveyTemplate, with the query data:
var surveyTemplateObj = populator.populateFromQuery( beanFactory.getInstance("surveyTemplate"), q );
This works, however, the model is only populated with one question. The surveyTemplate I am testing has three questions.
I have tried to set up a property name correctly in my model to use fieldtype="one-to-many" but that does not appear to have made a difference.
property name="surveyTemplateQuestionID" fieldtype="one-to-many";
While Coldbox's documentation for models overall is quite good, I am not able to find an answer for this, which probably means I am off track a good bit in my implementation of this.
Any insight would be appreciated.
So, what I did for this is I injected a surveyTemplateQuestions model into my surveyTemplate model:
property name="surveyTemplateQuestions" inject="surveyTemplateQuestions";
then I set the surveyTemplateQuestions property with the questions query:
surveyTemplateObj.setSurveyTemplateQuestions(qQuestions);
Not exactly what I was looking for but it works for now.
You can loop over query to build Arrays of objects. populateFromQuery method takes query row-number to get data from query.
var surveyTemplateObj = [];
for(var row in q){
ArrayAppend(surveyTemplateObj, populator.populateFromQuery( beanFactory.getInstance("surveyTemplate"), q , row.currentRow));
}
API Doc info
http://apidocs.ortussolutions.com/coldbox/4.3.0/coldbox/system/core/dynamic/BeanPopulator.html#populateFromQuery()

SQL Doctrine: InnerJoin where A.key = B.key or A.key = NULL

I'm trying to do a "complex" SQL request using Doctrine:
I've got a sport project with multiple delegations.
I created events ('e') to tell each ones what will happen next.
One event can be for multiple delegations, and if it's for all, it's linked to no one (to save database space)
Then I've got a ManyToMany relation beetween Events and Delegations
And I would like to fetch all events that concern one delegation ('A') after now(->where('e.startTime > :date'))
This event are linked by an:
->innerJoin('e.delegations', 'd', 'WITH', 'd.name = A' )
This work quite good, but for event associated with no one delegation it doesn't get returned
then I tried:
->leftJoin('e.delegations', 'd', 'WITH', 'd.name = A' )
But this return all the events
Then I need to add an orWhere to get catch the e.delegations = null but I don't know how to use it, because this will break the previous where that was concerning the time.
Or maybe I can specify something in the innerJoin (like a or NULL or something) but I didn't find this in any Doctrine QueryBuilder Doc
I found this in french, but this is only a join and selected using a Where, then it's certainly not optimal compare to an inner/left Join, and will be harder to maintain.
Problem
This problem is a little trick because once we filter delegation by name (d.name = A), for all purposes any event (e) which don't have that delegation, but have others, will be treated as it didn't any. Because the filter will "remove" others delegations to be returned.
Solution
Solve this by using a subquery:
//subquery - only returns event which has delegation 'A'
$sqb->select('e1.id')
->from('MyNamespace\Entity\Event','e1')
->innerJoin('e1.delegations', 'd', 'WITH', "d.name = 'A'");
//main query
$qb
->leftJoin('e.delegations', 'd')
->andWhere('e.delegation is null or e.id in ('.$sqb->getDQL().')');
PS
If you need use a placehold (:param) in a subquery, always set the parameter in the main QueryBuilder.

Rails ActiveRecord scoped many to many query

In my models Event and Artist are both HABTM relations.
Events are default scoped to current events by date so to only show events that haven't happened yet. I'm trying to write a method or scope to get all from Artist that have no current events. I tried ActiveRecord relation
none = Artist.where{|a| a.events.default_scope.count == 0}
But this returns
ActiveRecord::QueryMethods::WhereChain
And then I can't get the actual objects to work with. Then when I iterate over .all it's very slow because there's a ton of data in the models.
Artist.all.select{|a| a.events.default_scope.count == 0}
What is a faster or better way to handle this?
Use a LEFT JOIN on events and the join table, add the condition to include only current events, and get only artists where there is no events:
scope :no_events, -> {
joins('LEFT JOIN artists_events ON artists.id = artists_events.artist_id')
.joins('LEFT JOIN events ON events.id = artists_events.event_id')
.where([here the condition for current events])
.where('events.id IS NULL')
}
You may need to add a distinct

Distinct-style filtering on a Django model

Distinct might be the wrong word for what I want but I have a Message class like the following for a simple flat messaging system between users:
class Message(models.Model):
thread = models.ForeignKey(Thread)
from_user = models.ForeignKey(User, related_name='messagefromuser')
to_user = models.ForeignKey(User, related_name='messagetouser')
when = models.DateTimeField(auto_now_add=True)
message = models.TextField()
This allows two users to chat about a single Thread object. The system is designed to allow two users to have separate conversations on separate Threads.
So as it is, I can grab the messages a given user is involved in with the following query:
Message.objects.filter( Q(from_user=u) | Q(to_user=u) )
That outputs every message a user has sent or received. I'm building a page where users can see all their conversations with other users, grouped by thread. This is the ideal output that I can imagine getting to:
[
{
'thread': thread_instance,
'conversations': [
{
'other_user': user_instance
'latest_reply': date_time_instance
},
....
]
},
...
]
I have thought about iterating this from the top, but unless there's a way to filter through Thread into Message's to_user, from_user fields, there are just too many threads. The DB server would melt.
"Group" the messages by Thread
"Group" those by the other user so each group is between two distinct users, per Thread
Pluck the most recent to_user=u and annotate something with that.
I'm going a little crazy trying to warp my brain around the particulars. In my head it feels like something you should be able to do in a couple of lines but I just can't see how.
threads = Thread.objects.filter(
Q(message_set__from_user=u) | Q(message_set__to_user=u)
).order_by('id')
messages = Message.objects.filter(
Q(thread__in=threads) & Q(Q(thread__from_user=u) | Q(thread_to_user=u))
).order_by('thread__id', '-when').select_related('from_user', 'to_user')
from itertools import groupby
t_index = 0
for thread_messages in groupby(messages, lambda x: x.thread_id):
if threads[t_index].id is thread_messages[0].thread_id:
threads[t_index].messages = thread_messages
t_index += 1
That might look a bit complex or scary but it should do what you are after. Essentially it queries all your threads first so we can find out what threads we've messaged about. Then it finds all the related messages to those threads.
Both of the queries are ordered by the same field so that in the lower part of the code we can iterate through the list only once, instead of needing a nested for loop to find each thread with the correct id. They are also both filtered by the same query (regarding thread objects at least) to ensure we are getting back only the relevant results to this query.
Lastly, the messages that we received back are grouped together and attached to each thread; they will show up in descending order for each thread.
Right at the end, you may also want to re-sort your threads to show the latest ones first, easy to do assuming a 'when' field on the Thread model:
threads = sorted(threads, key=lambda x: x.when, reverse=True)
By using the aforementioned method, you will have to do 2 queries every time, regardless, first the threads, then the messages. But it will never go above this (watch out for joins on a select_related though or recursive queries on related objects).