How to use 'distinct' in zend db model - mysql

I have search for a long time to get this thing work.
What I want is to know how I user the 'distinct' in a zend db model to make my selection for the followers of a user unique.
My db model to count followers for a user (here I need to add the 'distinct')
public function countFollowers($user_id)
{
$rowset = $this->fetchAll("user_id = $user_id");
$rowCount = count($rowset);
if ($rowCount > 0) {
return $rowCount;
} else {
return $rowCount;
}
}
EDIT: This function is part of 'class Application_Model_DbTable_Followers extends Zend_Db_Table_Abstract'
My table structure
id
article_id // Id of the article who is written by 'user_id'.
user_id // user_id owner of the article
follower_id // member who has following this article
date // date of follow
'user_id' can be written various articles, the follower can follow various articles of the same writer. I want to make a unique follower count. As an example what I want, If a follower is following 8 articles of one writer it has to be compared to '1' in the count.
I hope this will be clear enough to understand what I tried to reach.
With kind regards,
Nicky

Using distinct:
public function countFollowers($user_id)
{
$select = $this->select()
->distinct()
->where('user_id = ?', $user_id);
$rowset = $this->fetchAll($select);
$rowCount = count($rowset);
return $rowCount;
}
EDIT: After edit in question to get count of followers of a user. You actually need to use group NOT distinct. I have tested the following query works to fetch the data to be count()ed,
SELECT * FROM followers WHERE user_id = 1 GROUP BY user_id,
follower_id
I have not tested the code, but something like this should work:
public function countFollowers($user_id)
{
$select = $this->select()
->where('user_id = ?', $user_id)
->group(array('user_id', 'follower_id'));
$rowset = $this->fetchAll($select);
$rowCount = count($rowset);
return $rowCount;
}

You can specify mysql functions in the 'from' function that makes up select query function. To use the from function you need to pass the table name as the first parameter, however passing $this (your table model class) works fine.
public function countFollowers($user_id)
{
$rowset = $this->fetchAll(
$this->select()
->from($this, array('DISTINCT user_id'))
->where('user_id = ?', $user_id)
);
return count($rowset);
}
[edit]
Based on your edit, 'group' may also work for you:
public function countFollowers($user_id)
{
$rowset = $this->fetchAll(
$this->select()
->where('user_id = ?', $user_id)
->group('user_id')
);
return count($rowset);
}
This will group all matching user_id into one record. So if a user is found, it will return 1, else 0.

Retrieving all the rows simply to get a count strikes me as overkill.
You can do a count using something like this:
$select = $db->select();
$select->from('testcount', new Zend_Db_Expr('COUNT(id)'))
->where('user_id = ?', $someUserId);
return $db->fetchOne($select);

don't write that :
public function countFollowers($user_id)
{
$rowset = $this->fetchAll(
$this->select()
->from($this, array('DISTINCT user_id'))
->where('user_id = ?', $user_id)
);
return count($rowset);
}
But that :
public function countFollowers($user_id)
{
$rowset = $this->fetchAll(
$this->select()
->from($this, array('DISTINCT(user_id)'))
->where('user_id = ?', $user_id)
);
return count($rowset);
}
Else you will have an error wich looks like to Mysqli prepare error:
Unknown column 'repertoire.distinct idRepertoireParent' in 'field list'

Also we have one method from the official manual
Just use "distinct"
Build this query: SELECT DISTINCT p."product_name" FROM "products" AS p
$select = $db->select()
->distinct()
->from(array('p' => 'products'), 'product_name');

Today I tried DISTINCT in JOIN LEFT case and it doesn't work. But if you add a Group By to the DISTINCT column, it works fine.

Related

Code Igniter Get Comments Count from another table For Post List Array for API response

Hello,
I am working on a Posts and Comment Models response API in Code Igniter!
MY Controller:
public function getPosts()
{
if (isset($_POST["getPosts"]))
{
$data = $this->api_model->getPosts();
$json_response2 = array('status' => 'success', 'postList' => $data->result_array());
echo json_encode($json_response2);
}
else
{
$data['status'] = 'error';
echo json_encode($data);
}
}
My Model:
public function get_posts()
{
$this->db->order_by('postID', 'DESC');
$query = $this->db->get('posts');
return $query->result_array();
}
MYSQL Tables:
for Posts---
postID | postTitle | postBody
for Comments---
commentID | postID | commentBody
I want to Get Comments Count for post Array in API response to display list of posts and comments Count for that Post?
ThankYou!
This is the query needed for you to get all those data in single query. It will join both table with left join clause and will give you the comment count as well.
SELECT
p.`postID`,
`postTitle`,
`postBody`,
COUNT(c.commentID) AS comment_cnt
FROM
`Posts` AS p
LEFT JOIN `Comments` AS c
ON p.postID = c.postID
GROUP BY c.postID
ORDER BY p.postID DESC ;
N:B left join is mandatory, otherwise it will not give you zero
commented post.
to convert it to codeigniter you can write it as.
$this->db->from('Posts p');
$this->db->join('Comments c','p.postID = c.postID','left');
$this->db->group_by('c.postID');
$this->db->order_by('p.postID', 'DESC');
$this->db->select('p.*,COUNT(c.commentID) AS comment_cnt');
$query = $this->db->get();
$res = $query->result_array();
Hope this would help you.
You need to join your comments table to get count and group by your postID.
Modify your get_posts() function in model as follows:
$this->db->from('posts');
$this->db->select("posts.*, count(*) as comments_count");
$this->db->join('comments', 'posts.postID = comments.postID');
$this->db->group_by('posts.postID');
$this->db->order_by('postID', 'DESC');
return $this->db->get()->result_array();
Also, you are using result_array() twice which is wrong. Change your controller line
$json_response2 = array('status' => 'success', 'postList' => $data->result_array());
as
$json_response2 = array('status' => 'success', 'postList' => $data);
because you are already getting result from model.
Also, use the correct name for function in controller, $this->api_model->get_posts();
Hope it helps.

How to convert this query to doctrine DQL

SELECT apntoken,deviceid,created
FROM `distribution_mobiletokens` as dm
WHERE userid='20'
and not exists (
select 1
from `distribution_mobiletokens`
where userid = '20'
and deviceid = dm.deviceid
and created > dm.created
)
What this query does is selects all mobiletokens where the user id is equal to 20 and the deviceid is the same but chooses the newest apntoken for the device.
My database looks like below.
For more information on this query, I got this answer from another question I asked here(How to group by in SQL by largest date (Order By a Group By))
Things I've Tried
$mobiletokens = $em->createQueryBuilder()
->select('u.id,company.id as companyid,user.id as userid,u.apntoken')
->from('AppBundle:MobileTokens', 'u')
->leftJoin('u.companyId', 'company')
->leftJoin('u.userId', 'user')
->where('u.status = 1 and user.id = :userid')
->setParameter('userid',(int)$jsondata['userid'])
->groupby('u.apntoken')
->getQuery()
->getResult();
//#JA - Get the list of all the apn tokens we need to send the message to.
foreach($mobiletokens as $tokenobject){
$deviceTokens[] = $tokenobject["apntoken"];
echo $tokenobject["apntoken"]."\n";
}
die();
This gives me the incorrect response of
63416A61F2FD47CC7B579CAEACB002CB00FACC3786A8991F329BB41B1208C4BA
9B25BBCC3F3D2232934D86A7BC72967A5546B250281FB750FFE645C8EB105AF6
latestone
Any help here is appreciated!
Other Information
Data with SELECT * FROM
Data after using the SQL I provided up top.
You could use a subselect created with the querybuilder as example:
public function selectNewAppToken($userId)
{
// get an ExpressionBuilder instance, so that you
$expr = $this->_em->getExpressionBuilder();
// create a subquery in order to take all address records for a specified user id
$sub = $this->_em->createQueryBuilder()
->select('a')
->from('AppBundle:MobileTokens', 'a')
->where('a.user = dm.id')
->andWhere('a.deviceid = dm.deviceid')
->andWhere($expr->gte('a.created','dm.created'));
$qb = $this->_em->createQueryBuilder()
->select('dm')
->from('AppBundle:MobileTokens', 'dm')
->where($expr->not($expr->exists($sub->getDQL())))
->andWhere('dm.user = :user_id')
->setParameter('user_id', $userId);
return $qb->getQuery()->getResult();
}
I did this for now as a temporary fix, not sure if this is best answer though.
$em = $this->em;
$connection = $em->getConnection();
$statement = $connection->prepare("
SELECT apntoken,deviceid,created
FROM `distribution_mobiletokens` as dm
WHERE userid=:userid
and not exists (
select 1
from `distribution_mobiletokens`
where userid = :userid
and deviceid = dm.deviceid
and created > dm.created
)");
$statement->bindValue('userid', $jsondata['userid']);
$statement->execute();
$mobiletokens = $statement->fetchAll();
//#JA - Get the list of all the apn tokens we need to send the message to.
foreach($mobiletokens as $tokenobject){
$deviceTokens[] = $tokenobject["apntoken"];
echo $tokenobject["apntoken"]."\n";
}

Symfony2 Doctrine error: Cannot count query that uses a HAVING clause. Use the output walkers for pagination

I am trying to get collections that are non-empty, i.e. have at least 1 object. Collection entity has OneToMany relationship with Object entity. I am using KNP paginator to paginate result. This is my function:
public function fetchAction(Request $request){
$em = $this->getDoctrine()->getManager();
$page = $request->get('page', 1);
$limit = 10;
$collections = $em->createQueryBuilder()
->select('c')
->add('from', 'CollectionBundle:Collection c LEFT JOIN c.object o')
->having('COUNT(o.id)>0')
->orderBy('c.date', 'DESC')
->getQuery();
$collections = $this->get("knp_paginator")->paginate($collections, $page, $limit);
return $this->render('CollectionBundle:Collection:fetch.html.twig', [
'collections' => $collections
]);
}
Error
I keep getting following error
Cannot count query that uses a HAVING clause. Use the output walkers for pagination
Without 'Having' clause everything works fine, but I must get non-empty collections.
wrap-queries solved this problem
$collections = $this->get("knp_paginator")->paginate($collections, $page, $limit,array('wrap-queries'=>true));
You can implement the Manual counting, as described here in the doc.
As example, you can modify your code as follow:
$count = $em->createQueryBuilder()
->select('COUNT(c)')
->add('from', 'CollectionBundle:Collection c LEFT JOIN c.object o')
->having('COUNT(o.id)>0')
->orderBy('c.date', 'DESC')
getSingleScalarResult();
$collections = $em->createQueryBuilder()
->select('c')
->add('from', 'CollectionBundle:Collection c LEFT JOIN c.object o')
->having('COUNT(o.id)>0')
->orderBy('c.date', 'DESC')
->getQuery();
$collections->setHint('knp_paginator.count', $count);
$collections = $this->get("knp_paginator")->paginate($collections, $page, $limit,array('distinct' => false));
return $this->render('CollectionBundle:Collection:fetch.html.twig', [
'collections' => $collections
]);
Hope this help
My solution is based on #Matteo's solution, since my query was a bit complicated I wanted to share my version also:
$qb = $this->createQueryBuilder('c');
$qb->select('count(c.id)')
->addSelect('COUNT(DISTINCT m.id) AS HIDDEN messageCount')
->addSelect('COUNT(DISTINCT f.id) AS HIDDEN fileCount')
->join('c.user', 'u')
->join('c.status', 's')
->join('c.company', 'comp')
->leftJoin('c.files', 'f')
->leftJoin('c.messages', 'm');
$this->_set_filters($filter, $qb);
$qb->groupBy('c.id');
$countQuery = $qb->getQuery();
/** wrap query with SELECT COUNT(*) FROM ($sql)
* I don't know what exactly does this block but
* I coppied it from Doctrine\ORM\Tools\Pagination\Paginator::getCountQuery()
*/
$platform = $this->getEntityManager()->getConnection()->getDatabasePlatform();
$rsm = new Query\ResultSetMapping();
$rsm->addScalarResult($platform->getSQLResultCasing('dctrn_count'), 'count');
$countQuery->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, CountOutputWalker::class);
$countQuery->setResultSetMapping($rsm);
return $countQuery->getSingleScalarResult(); //returns integer

order and limit on a select query zend framework 2

Earlier this day a asked a question about an update query. But now i want to select some things ( and it is working ) but I also want to order them and put a limit on it.
This is the code to select all the food :
public function getFood($id)
{
$id = (int)$id;
$rowset = $this->tableGateway->select(array('kindOfFood_id' => $id));
$row = $rowset->current();
if (!$row) {
throw new \Exception("Could not find row $id");
}
return $row;
}
But how can i do this :
Select * from KindOfFood ==> order by kindOfFood_votes DESC ?
I saw on the documentation you can do something like this, but it doesn't work with me?
$rowset = $artistTable->select(function (Select $select) {
$select->where->like('name', 'Brit%');
$select->order('name ASC')->limit(2);
});
Are you looking to return only single row or multiple rows.
Try this for multiple rows -
use Zend\Db\Sql\Select; //at the top of the page among other use statements.
public function getFood($id)
{
$id = (int) $id;
$select = new Select(TABLE_NAME); //CHANGE TABLE_NAME as per needs
$select->where('kindOfFood_id = ' . $id);
$select->order('kindOfFood_votes DESC');
$resultSet = $this->tableGateway->selectWith($select); //Will get array of rows.
//$row = $rowset->current(); THIS IS FOR RETURNING ONLY SINGLE ROW NOT ALL ROWS
if (!$resultSet) {
throw new \Exception("Could not find rows with food id - $id");
}
return $resultSet;
}
Can access the returned resultSet via loop. Eg: foreach
foreach($resultSet as $row) {
echo $row->kindOfFood_id; //or something
}
Note:
If you need only
Select * from KindOfFood order by kindOfFood_votes DESC
then remove the $select->where('kindOfFood_id = ' . $id); line from above.

Symfony 1.4 select query with selected columns is not working

I want to run following query in symfony doctrine.
SELECT p.id AS id FROM skiChaletPrice p WHERE ski_chalet_id = ? AND month = ?
I wrote my doctrine query as following.
$q = Doctrine_Query::create()
->select('p.id AS id')
->from('skiChaletPrice p')
->andWhere('ski_chalet_id = ?', $chaletId)
->andWhere('month = ?', $from);
$result = $q->fetchOne();
if ($result->count() > 0) {
return $result->toArray();
} else {
return null;
}
But my result always include all columns in the table. What the issue? Please help me.
The issue is that fetchOne() will return a Doctrine object, which implicitly contains all the columns in the table. $result->toArray() is converting that doctrine object to an array, which is why you get all the columns.
If you only want a subset of column, don't hydrate an object, instead do something like this:
$q = Doctrine_Query::create()
->select('p.id AS id')
->from('skiChaletPrice p')
->andWhere('ski_chalet_id = ?', $chaletId)
->andWhere('month = ?', $from);
$results = $q->execute(array(), Doctrine::HYDRATE_SCALAR);
See http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/data-hydrators.html
This is how I should do it:
$result = Doctrine_Query::create()
->select('id')
->from('skiChaletPrice')
->andWhere('ski_chalet_id = ?', $chaletId)
->andWhere('month = ?', $from)
->limit(1)
->fetchOne(array(), Doctrine_Core::HYDRATE_SINGLE_SCALAR);
// result will be a single id or 0
return $result ?: 0;
// if you want array($id) or array() inseatd
// return (array) $result;