How to use an expression in select count(...)? (Doctrine) - php

I have User and UserGroup entities. Each user has a group (UserGroup), and each user has a boss (User).
I would like to get a list of groups, and decide if the current user is the boss of someone in the group. I already implemented this in SQL, and now I tried to solve it using Doctrine. This is what I tried:
$qb = em()->createQueryBuilder()
->select(array('gr.id AS group_id', 'gr.name AS group_name', 'COUNT(us.boss = :current_user_id)>0 AS is_boss'))
->from('\App\Entity\UserGroup', 'ug')
->leftJoin('\App\Entity\User', 'us', 'WITH', 'us.group = ug.id')
->setParameter('current_user_id', $_SESSION['uid'])
->groupby('gr.id')
;
$groups = $qb->getQuery()->getScalarResult();
Unfortunately I get an uncaught QueryException, and I have no idea how to fix that. How is it possible to put an expression inside the COUNT(...) function?

You can use Mysql case function to do this:
$queryBuilder->addSelect('case when COUNT(case when us.boss = :current_user_id then 1 else 0 end)>0 then 1 else 0 end AS is_boss');
Reference
Is it possible to specify condition in Count()?

Related

CakePHP 3 putting un-necessary parentheses in SQL causing error

CakePHP 3.7. Trying to use the ORM to write a query which contains a MySQL COALESCE condition.
Followed advice on CakePHP 3 - How to write COALESCE(...) in query builder? and ended up having to write it manually using newExpr() as this was the given solution.
The code I have is as follows:
$TblRegulatoryAlerts = TableRegistry::getTableLocator()->get('TblRegulatoryAlerts');
$subscribed_to = $TblRegulatoryAlerts->getUserRegulations($u_id, $o_id, false);
$query = $this->find()
->contain('Filters.Groups.Regulations')
->select(['id', 'date', 'comment', 'Filters.label', 'Filters.anchor', 'Groups.label']);
$query->select($query->newExpr('COALESCE((SELECT COUNT(*)
FROM revision_filters_substances
WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances'));
$query->where(['date >=' => $date_start, 'date <=' => $date_end, 'Regulations.id' => $regulation_id, 'Filters.id IN' => $subscribed_to]);
$query->enableHydration(false)->orderDesc('date');
This produces the following SQL (output of debug($query->sql()):
SELECT RevisionFilters.id AS `RevisionFilters__id`, RevisionFilters.date AS `RevisionFilters__date`, RevisionFilters.comment AS `RevisionFilters__comment`, Filters.label AS `Filters__label`, Filters.anchor AS `Filters__anchor`, Groups.label AS `Groups__label`, (COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances) FROM revision_filters RevisionFilters INNER JOIN dev_hub_subdb.filters Filters ON Filters.id = (RevisionFilters.filter_id) INNER JOIN dev_hub_subdb.groups Groups ON Groups.id = (Filters.group_id) INNER JOIN dev_hub_subdb.regulations Regulations ON Regulations.id = (Groups.regulation_id) WHERE ...
Unfortunately this doesn't execute because Cake is putting in un-necessary parentheses surrounding the COALESCE statement, which changes the SQL.
In the above code it generates:
(COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances)
Whereas it needs to omit the parentheses surrounding COALESCE so it's just:
COALESCE((SELECT COUNT(*) FROM revision_filters_substances WHERE revision_filter_id = RevisionFilters.id), 0) AS count_substances
Is this possible?
Don't specify the alias in the expression, instead specify it using the key => value syntax of the Query::select() method, like this:
$query->select([
'count_substances' => $query->newExpr('...')
]);
It would still wrap the expression in parentheses, but that would then be valid as it doesn't include the alias.
That being said, using the function builders coalesque() method should work fine, the problem described in the linked question can be fixed by using the key => value syntax too, where the value can specify the kind of the argument, like ['Nodes.value' => 'identifier'], without that it would bind the value as a string.
However there shouldn't be any such problem with your example, using the function builders coalesce() method should work fine.
$query->select([
'count_substances' => $query->func()->coalesce($countSubquery, 1, ['integer'])
]);
The type argument is kinda optional, it would work with most DBMS without it, but for maximum compatibility it should be specified so that the integer value is being bound properly, also it will automatically set the return type of the function (the casting type) to integer too.

Update certain fields based on condition in Doctrine

I have created a Symfony 2 Bundle that supports private messages between users. I gave them the ability to send messages from their inbox or sent folder to the trash one. Messages will be marked as trash through the isRTrash and isSTrash fields, marked by receiver and by sender, respectively. That is because, being the same message in my database, if I had one single field here, one user marking it as trash, would mark it for the other one, as well.
Now, I want to give them the possibility to delete them, too, from their trash folder. Messages won't be deleted, but marked similarly to trash ones, just that they are forever gone from standard user view. I'm having problems with marking them like this, because I have to mark both messages that are sent and received by the user.
I've made the following query in the entity's repository:
public function delete($user, $msg)
{
$qb = $this->createQueryBuilder('a')
->update('PrivateMessageBundle:Message', 'a')
->where('a IN(:msg)')
->andwhere('a.receiver = :user AND a.isRTrash IS NOT null AND a.isRDeleted = false')->set('a.isRDeleted', true)
->orWhere('a.sender = :user AND a.isSTrash IS NOT null AND a.isSDeleted = false')->set('a.isSDeleted', true)
->setParameters(
array('user' => $user, 'msg' => $msg)
);
echo '<pre>';
\Doctrine\Common\Util\Debug::dump($qb->getQuery()->getSQL()); exit;
echo '</pre>';
return $qb->getQuery();
}
And the output query is string(196) "UPDATE message SET isRDeleted = 1, isSDeleted = 1 WHERE (id IN (?) AND (receiver_id = ? AND isRTrash IS NOT NULL AND isRDeleted = 0)) OR (sender_id = ? AND isSTrash IS NOT NULL AND isSDeleted = 0)"
I give as input the curent logged in user and an array of message id's. Then, I check messages that are in trash, are not marked as deleted and have the curent user as receiver or sender and want to mark them as deleted.
The problem is that both conditions are met, and both SET are being called, marking a message's isRDeleted and isSDeleted to true, regardless.
I am very close, but don't know how to make it so that the fields are marked separately, only if their condition is met.
Meanwhile, I'm using a foreach loop, but I think it can be done faster with a query
$em = $this->getDoctrine()->getManager();
foreach ($msgs as $msgid) {
$msg = $messages->findOneBy(array('id' => $msgid));
if ($msg->getSender() == $this->getUser() && $msg->getIsSTrash() && $msg->getIsSDeleted() == false) {
$msg->setIsSDeleted(true);
$changedno++;
} else if ($msg->getReceiver() == $this->getUser() && $msg->getIsRTrash() && $msg->getIsRDeleted() == false) {
$msg->setIsRDeleted(true);
$changedno++;
}
$em->flush();
}
I think you need a CASE .. WHEN construction but Doctrine doesn't have that in DQL (See the Grammar). So you either must use a raw query, something along these lines (it's pseudo MySQL) :
UPDATE PrivateMessageBundle:Message a
SET a.isRDeleted = CASE
WHEN a.receiver = :user AND a.isRTrash IS NOT null THEN TRUE
ELSE a.isRDeleted = FALSE
END,
SET a.isSSDeleted = CASE
WHEN a.receiver = :user AND a.isRTrash IS NOT null THEN TRUE
ELSE a.isSDeleted = FALSE
END
... or use two standard queries, one for isRDeleted and one for isSDeleted, like the one you already did. To be honest I think that's a pretty simple solution in your case, and it looks more maintenance-friendly if you ever need to read your code again in six months.
NB : on a side note, the ->set() or ->when() functions in Doctrine (and all the others, in fact) do not follow a specific order; they are just adding properties to the Doctrine query object, and when you call getQuery(), a SQL query is made. That means that the following construction :
->when()->set()
->orwhen()->set()
is equivalent to :
->set()->set()
->when()->orWhen()
which is why your solution cannot work. There is no condition to be met before set() is called (if I'm not clear, tell me)

CodeIgniter Where_In select from different table?

i am trying to covert this query in active record
SELECT
crm_clients.id,
crm_clients.`moved_date`,
crm_clients.`contractor_id`
FROM
dev_pfands.`crm_clients`
WHERE crm_clients.`contractor_id` = 11
AND (
crm_clients.`status` = 9
OR crm_clients.`status` = 8
OR crm_clients.`status` = 7
)
AND crm_clients.id IN
(SELECT
crm_client_cheques.`client_id`
FROM
dev_pfands.`crm_client_cheques`)
AND crm_clients.`moved_date` BETWEEN '2014-08-01'
AND '2014-11-29 '
AND crm_clients.`contractor_id`<>''
GROUP BY crm_clients.`id
the section I'm having issue is
AND crm_clients.id IN
(SELECT
crm_client_cheques.client_id
FROM
dev_pfands.crm_client_cheques) `
i've tried the where_in method but overtime i try to include my attempt of $this ->db_pfands -> where('crm_client_cheques.client id' ,'id'); get hit with errors and have no idea how to get past this.
the original query should return 703 rows and when I've removed the part I'm stuck with it increase to 3045 so i need it to be included. any help is appreciated.
First of all you have a error in your code.
$this->db_pfands->where('crm_client_cheques.client id', 'id');
This will be
$this->db_pfands->where('crm_client_cheques.client_id', 'id');
You have to provide the right column name and as far i know database's column name have not contain any space.
Now I think this active record query will help you to get your result.
$query = $this->db->select('crm_clients.id, crm_clients.moved_date, crm_clients.contractor_id')
->where('moved_date BETWEEN "2014-08-01" AND "2014-11-29"')
->where('contractor_id', 'id')
->where_in('status', [9,8,7])
->from('crm_clients')
->join('crm_client_cheques', 'crm_client_cheques.client_id = crm_clients.id')
->group_by('id')
->get();
$result = $query->result();
May be you have change couple of names because they are in different database, but i believe you can do it.

addBetweenCondition in YII

SO I want to find that if value x is exits between the values of 2 columns or not, For that i have run the query in phpmyadmin :
Normal Approch :-
SELECT * FROM `traits_versions` WHERE 16 BETWEEN `trait_value_lower` and `trait_value_upper` and `style_id` = 1
and it is giving me fine result.But when the same approach i want to find achieve in YII that it is not running and giving the sql error :
YII apprroch :-
$details = array();
$criteria = new CDbCriteria();
$criteria->addCondition('style_id='.$style_id);
$criteria->addCondition('version='.$version);
$criteria->addBetweenCondition($style_contribution,$this->trait_value_lower,$this->trait_value_upper);
$trait_details= $this->find($criteria);
When i debug the query in log than it shows in case of yii :
SELECT * FROM `traits_versions` `t` WHERE ((style_id=1) AND (version=1)) AND (16 BETWEEN NULL AND NULL) LIMIT 1
Why it is giving NULL value in query while i'm passing the name of the column in it.
So please guide me where i'm going wrong in yii.
Add compare condition like below
$criteria->compare('trait_value_lower', 16, false, '>');
$criteria->compare('trait_value_upper',16, false, '<');
instead of between condition
$criteria->addBetweenCondition($style_contribution,$this->trait_value_lower,$this->trait_value_upper);
because between condition will apply on one column as per Yii doc.
public static addBetweenCondition(string $column, string $valueStart, string $valueEnd, string $operator='AND')

zend based where clause disappearing

I have a weird problem using socialEngine DB class (based on zend framework).
I wrote something like this:
$statusTable = Engine_Api::_()->getDbtable('actions', 'activity');
$myPosts = $statusTable->fetchAll($statusTable->select()
->where('subject_id = ?',$id)
->where('comment_count > ?',0)
->where('type = ?',$type)
->where('date > ?',$newer_than)
->order('date DESC')
->limit(intval($num_items)));
Its a part of a plugin a made, the problem is the query generated is somthing like this:
SELECT `engine4_activity_actions`.*
FROM `engine4_activity_actions`
WHERE (subject_id = 5) AND (comment_count > 0) AND (type = ) AND (date > )
ORDER BY `date` DESC LIMIT 10
You can see that the $type and the $newer_than have disappeared, even though they have values ($type='status', $newer_than='01/01/2000')
EDIT:
It seems to respond only to integers and not strings, if i replace the 'status' with 0 it shows up in the query.
The server runs on php 5.3.2
There's a third optionnal argument on the where() method which is the type of your argument. Depending on your DB adapter it can maybe get an important thing to tell for the Zend_Db_Select query builder.
So you could try
->where('subject_id=?',$subject,'TEXT')
ZF API indicates as well "Note that it is more correct to use named bindings in your queries for values other than strings", this can help the query builder, to get the real type of your args, so you could try as well this way:
$myPosts = $statusTable->fetchAll($statusTable->select()
->where('subject_id=:psubject')
(...)
,array('psubject'=>$subject));

Categories