I am new in Symfony, and I have a problem with an existing application I maintain.
In one of the repositories, there's is a method, that selecting the failed transactions, and the related payment.
Now, they have asked me, to allow filter the transactions, based on the total amount of failed transactions which could be either 1 failed transaction or 2.
What I am trying to do in the query builder, is something like that:
$this
->createQueryBuilder('t')
->join('t.payment', 'p')
->leftJoin( Transaction::class, 'tr', Query\Exprt\Join::WITH, 'p.id = tr.payment')
->groupBy('tr.id');
Until that point everything is fine. The query is executed normally, and I can see the transactions I need.
Now the problem is that I cannot use the following statement:
$this
->createQueryBuilder('t')
// This is the column I need to insert
->addSelect('COUNT(tr.id) AS TotalRecords')
->join('t.payment', 'p')
->leftJoin( Transaction::class, 'tr', Query\Exprt\Join::WITH, 'p.id = tr.payment')
->groupBy('tr.id');
Because the output looks like that:
array:1 [▼
0 => array:2 [▼
0 => Transaction {#1221 ▶}
"TotalRecords" => "1" <- This is the total transactions number I need
]
]
Instead of the output above, I need to have the TotalRecords inside the Transaction Object.
So, Is there a way to achieve that with the query builder? Do you think I do something wrong?
you can just loop over your result set and set TotalRecords on all Transaction objects... and return an array of Transactions, as you probably have hoped. The overhead is minimal but the standard doctrine hydration isn't smart enough
// the following is your query:
$qb = $this
->createQueryBuilder('t')
->addSelect('COUNT(tr.id) AS TotalRecords')
->join('t.payment', 'p')
->leftJoin( Transaction::class, 'tr', Query\Exprt\Join::WITH, 'p.id = tr.payment')
->groupBy('tr.id');
// fetch the results, and instead of straight returning them, "merge"
$results = $qb->getQuery()->getResult();
$return = [];
foreach($result as $row) {
$row[0]->totalCount = $row['TotalCount'];
$return[] = $row[0];
}
return $return; // <-- now an array of Transaction
you also could just not use addSelect but instead having and just use the number of transactions you want to filter by, as a parameter (unless the filtering is done later, in which case that approach won't work)
Related
I have the following code in an entity repository:
$rows = $this->createQueryBuilder('t')
->select(['t.idColumn', 't.valueColumn'])
->where('t.foo = :foo')
->orderBy('t.idColumn', 'ASC')
->setParameter('foo', $foo)
->getQuery()
->getArrayResult(); // This returns [[idColumn => ..., valueColumn => ...], ...]
$data = [];
foreach ($rows as $row) {
$data[$row['idColumn']] = abs($row['valueColumn']); // Remapping to [id => value]
}
return $data;
Is there any way to get rid of the custom remapping natively? I know that you can use the indexBy parameter, but that only gets me the correct keys but not values.
P.S. I know of array_column(), but that's an extra step that I have to make every time, not to mention it doesn't work on methods that entities have.
P.P.S. This is not using Symfony.
It does not appear this is a feature implemented in the QueryBuilder, however fetchAllKeyValue was added in DBAL 2.11 to the Connection object.
Commit, usage
I have a query:
$query = Products::find();
$query->joinWith('vendor', true, 'LEFT JOIN');
$query->joinWith('values', true,'LEFT JOIN');
$query->where(['<>', 'stock', 7]);
$query->andWhere(['category_id' => $model->id]);
if (!empty($activeVendors))
$query->andWhere(['lan_vendors.id' => array_flip($activeVendors)]);
if (!empty($activeValues)){
$query->andWhere(['lan_products_values.value_id' => $activeValues]);
}
$totalProducts = $query->count();
$products = $query->all();
In result:
$totalProducts = 12;
count($products) = 3;
I can not solve this problem. Reading the documentation did not help. Is there something wrong with the database itself?
your left join statements generate duplicate rows.
after a the query runs yii removes duplicate data and creates a usable array of uniqe Product models
the duplicate rows are not avoidable in your case since you enforce eager loading with left join
$query->joinWith('vendor', true, 'LEFT JOIN');
$query->joinWith('values', true,'LEFT JOIN');
you can try to run something like this to adjust the relations to your conditions, and follow the generated queries
in the debug log,
$query->with([
'vendor' => function (\yii\db\ActiveQuery $query) use ($activeVendors) {
$query->andFilterWhere(['lan_vendors.id' => array_flip($activeVendors)]);
},
'values' => function (\yii\db\ActiveQuery $query) use ($activeValues) {
$query->andWhere(['lan_products_values.value_id' => $activeValues]);
},
])
also follow the generated queries in the debug log, it's a usefull way of figuring out what happens in the two cases
Because you are joining additional tables here most probably you have got dupicated results - you can check it by running this query manually outside of Yii.
Query count() is showing you all the rows fetched from database (with duplicates).
all() on the other hand takes the fetched rows and while populating the Yii 2 models it removes duplicates so you have got unique ones.
EDIT:
Further research: Looks like the answer lies on changin default Hydrator for a customized. Doctrine2 allows you to change it just by sending his name as a parameter:
$query->getResult('CustomHydrator');
Dont forget to register it first, in your config.yml file:
doctrine:
orm:
hydrators:
CustomHydrator: \your\bundle\Hydrators\CustomHydrator
My relationship between Blog and Comment entity is one to many. 1 Blog has N Comments
After researching about how to add an extra field to a fetched object in Doctrine 2 I found Aggregate Fields, it is a good article but just talk about to get balance from a single Account, it never says what we should do when working with an array of Accounts, it may sound silly but let me explain my situation.
In my case is not about accounts and entries, is about blogs and comments.
What I'm trying to do is to list a number of blogs and just show how many comments it has without loading any comment information, in other words I want to translate this query to the Doctrine2 World.
'SELECT b.*, COUNT( b.id ) AS totalComments FROM `blogs` b LEFT JOIN comments c ON b.id = c.blog_id GROUP BY b.id LIMIT 8'
and the result that I expect is an array of Blog Objects with a totalComments attribute setted correctly, like this:
array (size=8)
0 =>
object Blog
'id' => int 330
'title' => string 'title blog'
// Added field, not visible in table DB. Came through query COUNT() statement
'totalComments' => int 5
// ... more attributes
1 => ...
//more object blogs
);
I just can't achieve this, the best I could do was this:
Creating and fetching Query:
$qb = $this->createQueryBuilder('b')
->select('b, c')
->addSelect('count(b.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getResult();
and the result I'm getting is an array of arrays, where position 0 has Blog Object and position "totalComments"
// var_dump($result)
array (size=8)
0 =>
array(2) =>
0 =>
object Blog
'id' => int 330
'title' => string 'title blog'
// ... more attributes
"totalComments" => int 5
1 => ...
);
I also tried to make my own Hydrator but I just started using Doctrine2 and found myself kinda lost.
I hope been enough clear. I can give any other information if needed.
Thanks in advance!
You either have to name the fields you want, or have a mixed result ( like your 2nd example). So for a flat array:
$qb = $this->createQueryBuilder('b')
->select('b.title, b.author')
->addSelect('count(c.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getArrayResult();
Or a mixed result:
$qb = $this->createQueryBuilder('b')
->select('b')
->addSelect('count(c.id) as nComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id')
return $qb->getQuery()->getResult();
After few days I came up with this solution.
I had to add a totalComments attribute to my Blog Entity Class and his get/set methods, and tweak a bit my getLatestBlogs function:
function getLatestBlogs(){
$qb = $this->createQueryBuilder('b')
->select('b, c')
->addSelect('count(b.id) as totalComments')
->leftJoin('b.comments', 'c')
->groupBy('b.id');
$result = $qb->getQuery()->getResult();
//tweaking original result
foreach($result as $row){
$row[0]->setTotalComments($row['totalComments']);
$blogList[] = $row[0];
}
return $blogList;
}
Doing it this way I finally get a simple array of Blog Objects, and it just took an extra loop.
After this I realized that would be nice to have a general function who can work with any Entity, so I made the next function:
function fixResult($qResult){ //Receives $qb->getQuery()->getResult();
if(is_array($qResult)){
$list = array();
$keys = array_keys($qResult[0]); //Getting all array positions from first row
$object = $qResult[0][0]; //Getting the actual object fetched
foreach($keys as $key){ //Searching for existing set methods in the Object
$method = "set".ucfirst($key);
if(method_exists($object,$method))
$methods[$key] = $method;
}
foreach($qResult as $row){ //Calling set methods for every row fetched and storing into a new array
foreach($methods as $key => $met){
$row[0]->$met($row[$key]);
$list[] = $row[0];
}
}
return $list;
}
else return false;
}
I hope somebody else find it useful.
I have in my table "Artiste" one column "valideAdmin" who takes value 1 or 0.
I try to make a simple count to return the number of entries in my table where "valideAdmin" is to 1:
$repo = $this ->getDoctrine()
->getManager()
->getRepository('ProjectMainBundle:Artiste');
$qb = $repo->createQueryBuilder('valideAdmin');
$qb->select('COUNT(valideAdmin)');
$qb->where('valideAdmin=1');
$count = $qb->getQuery()->getSingleScalarResult();
return array(
'count' => $count
);
But it always "1" who's return...
Without where clause, I have the total count of the entries of the table, but valideAdmin can be 0 or 1. I only want the count number where valideAdmin=1
Thanks for help
createQueryBuilder()'s first parameter is the alias that you want your entity to take (ie.: a short name to be used to refer to your entity in the query).
What you need to do is set a proper alias for your entity (for example a for Artiste) and then COUNT() the instances of your entity where the property (not the column) valideAdmin is set to one:
$repo = $this ->getDoctrine()
->getManager()
->getRepository('ProjectMainBundle:Artiste');
$qb = $repo->createQueryBuilder('a');
$qb->select('COUNT(a)');
$qb->where('a.valideAdmin = :valideAdmin');
$qb->setParameter('valideAdmin', 1);
$count = $qb->getQuery()->getSingleScalarResult();
Remember that DQL runs queries on entities. The DQL your write is then translated into SQL to query the underlying data source after.
Also you can fetch all date then use of COUNT function in PHP
This method has an advantage.You do not have to use a complex query.
You have all the information with count columns
$repositoryArtiste = $this->getDoctrine()
->getRepository('ProjectMainBundle:Artiste');
$queryArtiste= $repositoryArtiste->createQueryBuilder('a')
->Where('a.valideAdmin = :valideAdmin')
->setParameter('valideAdmin',1)
->getQuery();
$Artiste = $queryArtiste->getResult();
var_dump(count($Artiste));
I am new to Doctrine and I am trying to figure out how to add a having clause on my statement. Basically I want to be able to filter down on items returned based on how many attributes the user selects. The code is as follows:
// create query builder
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('p')
->from($this->_entityName, 'p')
->leftJoin('p.options', 'o')
->where('p.active = :active')
->setParameter('active', 1);
// add filters
$qb->leftJoin('o.attributes', 'a');
$ands = array();
foreach ($value as $id => $values)
{ echo count($values);
$ands[] = $qb->expr()->andX(
$qb->expr()->eq('a.attribute_id', intval($id)),
$qb->expr()->in('a.attribute_value_id', array_map('intval', $values))
$qb->having('COUNT(*)=3) // THIS DOESN'T WORK
//$qb->expr()->having('COUNT(*)=3) // THIS DOESN'T WORK EITHER
);
}
$where = $qb->expr()->andX();
foreach ($ands as $and)
{
$where->add($and);
}
$qb->andWhere($where);
$result = $qb->getQuery()->getResult();
return $result;
When I try to execute the statement with the having() clause I get this error:
Expression of type 'Doctrine\ORM\QueryBuilder' not allowed in this context.
Without the having() clause everything works perfectly.
I have no idea how to solve this.
HAVING clause requires a GROUP BY. In doctrine it would be something like that:
$qb->groupBy('p.id'); // or use an appropriate field
$qb->having('COUNT(*) = :some_count');
$qb->setParameter('some_count', 3);
Assuming you're using mysql, here is a having clause tutorial: http://www.mysqltutorial.org/mysql-having.aspx
Perhaps you should bind number 3 to a parameter:
$qb->having('COUNT(*)=:some_count')
$qb->setParameter('some_count',3)
Goal: filter down The one side where we have some known summable conditions we want to filter by (e.g., the count of Original Parts in a Bundle) on the many side of a O2M relationship wehere want to limit the One side along with some other criteria to select on.
We are then adding in a few conditions for the LEFT_JOIN operation:
Condition #1 - the bundle.id == the original_part.bundle ID.
Condition #2 - The original_part's partStatusType == 3 (some hard-coded value).
Then we filter down to the COUNT(op) >= 1 which gives our more limited response that works just fine with pagination.
$qb->leftJoin(OriginalPart::class, 'op', Expr\Join::WITH,
$qb->expr()->andX(
$qb->expr()->eq('op.bundle', 'row.id'),
$qb->expr()->eq('op.partStatusType', 3)));
$qb->groupBy('row.id');
$qb->having($qb->expr()->gte('COUNT(op)', 1));
row is the alias for the One (bundle entity).