Symfony2 app, MongoDB: Count all records matching criteria - mongodb

In my controller I'm fetching limited number of objects (for pagination) like this:
$dm = $this->get('doctrine_mongodb');
$repo = $dm->getRepository('AcmeMyBundle:MyDocument');
$criteria = array(
'field1' => 'value1',
'field1' => 'value1',
);
$logs = $repo->findBy(
$criteria, /* criteria */
array($field => $direction), /* sort */
$limit, /* limit */
(($page-1)*$limit)?:null /* skip */
);
Now I like to get total number of records that meet the $criteria.
I was trying to count it like this:
$count = $repo->createQueryBuilder('MyDocument')
->count()->getQuery()->execute();
But it counts all records in collection. How can I apply $criteria to that counting query?
I need result as native MongoDB db.MyDocument.find({'field2': "value1", 'field2': "value2"}).count()

Done it like this:
$countQuery = $repo
->createQueryBuilder('MyDocument')
->requireIndexes(false)
;
foreach(array_filter($criteria) as $field=>$value){
$countQuery->field($field)->equals($value);
}
$count = $countQuery->count()->getQuery()->execute();

Related

Doctrine Entity values are all null except for id

I'm trying to fetch an Object from the database with the repository method findOneBy (id).
Basically, the line looks like this:
public function findAssignedTickets(User $user)
{
$userId = $user->getId();
$ticketMapping = new ResultSetMapping;
$ticketMapping->addEntityResult(Ticket::class, 't');
$ticketMapping->addFieldResult('t', 'id', 'id');
// Postgresql Native query, select all tickets where participants array includes the userId
$query = "SELECT *
FROM (
SELECT id, array_agg(e::text::int) arr
FROM ticket, json_array_elements(participants) e
GROUP BY 1
) s
WHERE
$userId = ANY(arr);
";
$results = $this->getEntityManager()->createNativeQuery($query, $ticketMapping)->getResult();
$results = array_map(function($item) {
return $item->getId();
}, $results); // Transform to array in integers
dump($results); // array:2 [0 => 83, 1 => 84] -> It's correct
$tickets = [];
foreach ($results as $ticketId) {
dump($this->findOneById($ticketId));
// $ticket = $this->findOneById($ticketId);
// $tickets[] = [
// 'identifier' => $ticket->getIdentifier(),
// 'title' => $ticket->getTitle(),
// 'author' => $ticket->getAuthor()->getUsername(),
// 'status' => $ticket->getStatus(),
// 'created' => $ticket->getCreatedAt()->format('c'),
// 'updated' => $ticket->getUpdatedAt()->format('c'),
// ]; // Ticket formatting to send in json
}
return $tickets;
}
which will output :
And I'm sure that the received id matches a row in the database, and that the database contains data, and all fields belong directly to the entity, except for author which represents a ManyToOne and I heard about the lazy displaying of Doctrine, but it shouldn't happen on other fields.
Why can't I retrieve data from the Object even with getters, and why are all the values set to null Except for id ?
EDIT : I was wondering if that had a connexion to the ResultSetMapping I used to fetch the tickets IDs in a totally separate request earlier, and when I added a addFieldResult('t', 'title', 'title'); it did the work, but not on the other fields, another mystery.

Query an entity in a different Language

I'm trying to query entietes in a language, other than the system default one, my repository method looks like this,
public function findOneByMaterialnumber( $materialnumber, $sysLanguageUid ){
$query = $this->createQuery();
$query->matching($query->like('materialnumber',$materialnumber));
$query->getQuerySettings()->setIgnoreEnableFields(true);
$query->getQuerySettings()->setRespectStoragePage(false);
$query->getQuerySettings()->setLanguageUid($sysLanguageUid);
//$query->getQuerySettings()->setRespectSysLanguage(false);
//$query->getQuerySettings()->setLanguageMode('strict');
//$query->getQuerySettings()->setLanguageOverlayMode(false);
$parser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Storage\\Typo3DbQueryParser');
$queryParts = $parser->parseQuery($query);
//\TYPO3\CMS\Core\Utility\DebugUtility::debug($queryParts, 'Query');
print_r($queryParts);
exit;
return $query->execute()->getFirst();
}
but the query this results in still incorporates the sys_language_uid in the non-strict way. The debugged query object looks like this.
[keywords] => Array
(
)
[tables] => Array
(
[tx_productfinder_domain_model_product] => tx_productfinder_domain_model_product
)
[unions] => Array
(
)
[fields] => Array
(
[tx_productfinder_domain_model_product] => tx_productfinder_domain_model_product.*
)
[where] => Array
(
[0] => tx_productfinder_domain_model_product.materialnumber LIKE :
)
[additionalWhereClause] => Array
(
[0] => (tx_productfinder_domain_model_product.sys_language_uid IN (0,-1))
)
[orderings] => Array
(
[0] => tx_productfinder_domain_model_product.ordercode ASC
[1] => tx_productfinder_domain_model_product.title ASC
[2] => tx_productfinder_domain_model_product.materialnumber ASC
)
[limit] =>
[offset] =>
[tableAliasMap] => Array
(
[tx_productfinder_domain_model_product] => tx_productfinder_domain_model_product
)
This happens regardless of the sys_language_uid i query for. What am I doing wrong?
As you can see, I've tried combinations of all sorts of QuerySettings, setRespectSysLanguage, setLanguageMode and setLanguageOverlayMode. As I understand it, I have to query strictly and without language overlay. But none of those, nor their combinations, worked as intended.
This works with TYPO3 8.7.xx
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper;
/**
* The repository for Products
*/
class ProductRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
/*
* #param int $uid id of record
* #param int $langUid sys_language_uid of the record
* #return \ITSHofmann\ItsProductfile\Domain\Mpdell\Product
*/
public function findByUidAndLanguageUid($uid,$langUid)
{
$query = $this->createQuery();
$object = $query->matching(
$query->equals('uid', $uid)
)->execute()->getFirst();
if ($object) {
$className = get_class ($object);
$dataMapper = $this->objectManager->get(DataMapper::class);
$tableName = $dataMapper->getDataMap($className)->getTableName();
$transOrigPointerField = $GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'];
$languageField = $GLOBALS['TCA'][$tableName]['ctrl']['languageField'];
$connection = GeneralUtility::makeInstance(ConnectionPool::class);
$queryBuilder = $connection->getQueryBuilderForTable($tableName );
$statement = $queryBuilder->select('*')
->from($tableName )
->where(
$queryBuilder->expr()->eq(
$transOrigPointerField,$queryBuilder->createNamedParameter($object->getUid(), \PDO::PARAM_INT)),
$queryBuilder->expr()->eq(
$languageField,$queryBuilder->createNamedParameter($langUid, \PDO::PARAM_INT))
)->execute();
$objectRow = $statement ->fetch();
if ($objectRow) {
$langObject = $dataMapper->map($className,[$objectRow]);
if ($langObject ) {
return reset ($langObject );
}
}
}
return $object;
}
}
edit. The answer I posted here originally turned out not to work either. What I ended up doing instead, was to write a custom statement, execute it to return the raw data and map it, via DataMapper->map(); Refer to Christop Hofmanns answer for details.

Column already exists in LeftJoin with Zend

With relationship and joins I'm trying get the latest post of each customer.
But I don't really get it to work. I get error:
Column already exists: 1060 Duplicate column name 'custID'
Based on my searching both here and Google it could have been for * but I have removed so all table columns is specified by name so I don't get why I get column already exists?
$db = $this->getDbTable();
// create sub query
$subSql = $db->select()
->setIntegrityCheck(false)
->from(array('s1' => 'sales'), array('s1.custID', 's1.saledate'))
->joinLeft(array('s2' => 'sales'), 's1.custID = s2.custID AND s1.saledate < s2.saledate', array('s2.custID', 's2.saledate'))
->where('s2.custID IS NULL')
->limit(1);
//main query
$sql = $db->select()
->setIntegrityCheck(false)
->from(array('customers' => 'customers'), array("customers.custID"))
->joinLeft(array('sale_tmp' => new Zend_Db_Expr('(' . $subSql . ')')), "customers.custID = sale_tmp.custID", array('sale_tmp.custID'));
//echo $sql->assemble();
//exit;
$resultSet = $db->fetchAll($sql);
return $resultSet;
Since two of your tables have the field custID, there is a conflict about how to populate the custID value in your joined table.
You need to provide a column-alias for one of them. The signature of the joinLeft() method is:
joinLeft($table, $condition, [$columns])
The third argument $columns can be a straight integer-indexed array of columns (as you are using) or it can be an associative array whose values are the columns, but whose keys are the column-aliases.
So perhaps try something like:
// create sub query
// add alias for the custID field
$subSql = $db->select()
->setIntegrityCheck(false)
->from(array('s1' => 'sales'), array('s1.custID', 's1.saledate'))
->joinLeft(array('s2' => 'sales'), 's1.custID = s2.custID AND s1.saledate < s2.saledate', array('sales_custID' => 's2.custID', 's2.saledate'))
->where('s2.custID IS NULL')
->limit(1);
// main query
// add alias for custID field
$sql = $db->select()
->setIntegrityCheck(false)
->from(array('customers' => 'customers'), array("customers.custID"))
->joinLeft(array('sale_tmp' => new Zend_Db_Expr('(' . $subSql . ')')), "customers.custID = sale_tmp.custID", array('temp_custID' => sale_tmp.custID'));

MongoDB & PHP - Returning a count of a nested array

Imagine I have a MonogDB collection containing documents as follows:
{name: 'Some Name', components: {ARRAY OF ITEMS}}
How can I return the name and the count of items in components?
Do I have to use a map/reduce?
I am using PHP's Mongo extension.
EDIT: Snippet of current code in PHP (working) but I just want count of the components
$fields = array(
'name', 'components'
);
$cursor = $this->collection->find(array(), $fields);
$cursor->sort(array('created_ts' => -1));
if (empty($cursor) == true) {
return array();
} else {
return iterator_to_array($cursor);
}
Thanks,
Jim
You could use map-reduce or you could use a simple group query as follows. Since I am assuming that your name property is a unique key, this should work even though it isn't a reason that you'd normally use the group function:
db.test.group({
key: { name:true },
reduce: function(obj,prev) {
var count = 0;
for(k in obj.components)
count++;
prev.count = count;
},
initial: { count: 0}
});
You mentioned that you have an array of components, but it appears that you are storing components as an object {} and not and array []. That is why I had to add the loop in the reduce function, to count all of the properties of the components object. If it were actually an array then you could simply use the .length property.
In PHP it would look something like this (from the Manual):
$keys = array('name' => 1);
$initial = array('count' => 0);
$reduce =<<<JS
function(obj,prev) {
var count = 0;
for(k in obj.components)
count++;
prev.count = count;
},
JS;
$m = new Mongo();
$db = $m->selectDB('Database');
$coll = $db->selectCollection('Collection');
$data = $coll->group($keys, $initial, $reduce);
Finally, I would strongly suggest that if you are trying to access the count of your components on a regular basis that you store the count as an additional property of the document and update it whenever it changes. If you are attempting to write queries that filter based on this count then you will also be able to add an index on that components property.
You could use db.eval() and write the calculation in JavaScript.
Jim-
These are two separate operations; Unless you want to leverage PHP's count on the results you get which you would then do something like:
$m = new Mongo();
$db = $m->selectDB('yourDB');
$collection = $db->selectCollection('MyCollection');
$cursor = $collection->find(array(), array("name"=>1, "components"=>1));
foreach($cursor as $key){
echo($key['name'].' components: '.count($key['components']);
}
Ran across this today, If your using the new driver with aggregate you can do this in php, ( given this schema )
{name: 'Some Name', components: {ARRAY OF ITEMS}}
In PHP:
$collection = (new Client())->db->my_collection;
$collection->aggregate([
'$match' => ['name' => 'Some Name'],
'$group' => [
'_id' => null,
'total'=> ['$sum' => "\$components"]
]
]);
The trick here with PHP is to escape the $ dollar sign, this is basically what the mongo documentation says when using size or sum
https://docs.mongodb.com/manual/reference/operator/aggregation/size/
https://docs.mongodb.com/manual/reference/operator/aggregation/sum/
The problem I had is mongo puts fields in as "$field" and PHP doesn't like that at all because of the way it does variable interpolation. However, once you escape the $, it works fine.
I think for this particular case you'd need to do something similar but with $project instead of $group Like this
$collection = (new Client())->db->my_collection;
$collection->aggregate([
'$match' => ['name' => 'Some Name'],
'$project' => [
'name' => "\$name",
'total'=> ['$sum' => "\$components"]
]
]);
This is an old question but seeing as there is no answer picked, I'll just leave this here.

How to get affected number of rows using mongodb ,php driver

I have two problem : How can I get affected rows by php mongodb driver ,and how about the last insert id ? thanks .
You can get number of results right from the cursor using count function:
$collection->find()->count();
You can even get number of all records in collection using:
$collection->count();
Using insert method, _id is added to input array automatically.
$a = array('x' => 1);
$collection->insert($a,array('safe'=>true));
var_dump($a);
array(2) {
["x"]=>
int(1)
["_id"]=>
object(MongoId)#4 (0) {
}
}
I don't believe that there is any type of affected_rows() method at your disposal with mongodb. As for the last insert _id You can generate them in your application code and include them in your insert, so there's really no need for any mysql like insert_id() method.
$id = new MongoId();
$collection->insert(array('
'_id' => $id,
'username' => 'username',
'email' => 'johndoe#gmail.com'
'));
Now you can use the object stored in $id however you wish.
Maybe MongoDB::lastError is what you are looking for:
(http://php.net/manual/en/mongodb.lasterror.php)
It calls the getLastError command:
(http://www.mongodb.org/display/DOCS/getLastError+Command)
which returns, among other things:
n - if an update was done, this is the number of documents updated.
For number of affected rows:
$status = $collection->update( $criteria, array( '$set' => $data ) );
$status['n']; // 'n' is the number of affected rows
If you have the output of your action, you can call relative function:
// $m hold mongo library object
$output = $m->myCollection->updateOne([
'_id' => myMongoCreateID('56ce2e90c9c037dba19c3ce1')], [
'$set' => ['column' => 'value']
]);
// get number of modified records
$count = $output->getModifiedCount();
$output is of of type MongoDB\UpdateResult. Relatively check following files to figure out the best function to find inserted, deleted, matched or whatever result you need:
https://github.com/mongodb/mongo-php-library/blob/master/src/InsertManyResult.php
https://github.com/mongodb/mongo-php-library/blob/master/src/DeleteResult.php
https://github.com/mongodb/mongo-php-library/blob/master/src/InsertOneResult.php
https://github.com/mongodb/mongo-php-library/blob/master/src/UpdateResult.php