Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine
This tutorial has a new version, check it out!

SELECT the SUM (or COUNT)

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $6.00

In every query so far, Doctrine gives us objects. That's its default mode, but we can also easily use it to select specific fields.

On our category page, you can see how many of each fortune has been printed over time. At the top, let's total those numbers with a SUM() query and print it out.

In showCategoryAction(), create a new variable - $fortunesPrinted, that'll be a number. And of course, we'll write a new query to get this. But instead of shoving this into CategoryRepository, this queries the FortuneCookie entity, so we'll use its repository instead. So, AppBundle:FortuneCookie, and we'll call a new countNumberPrintedForCategory method. Pass the $category object as an argument:

... lines 1 - 9
class FortuneController extends Controller
{
... lines 12 - 35
public function showCategoryAction($id)
{
... lines 38 - 47
$fortunesPrinted = $this->getDoctrine()
->getRepository('AppBundle:FortuneCookie')
->countNumberPrintedForCategory($category);
... lines 51 - 56
}
}

This will return the raw, summed number. To actually use this, pass this into the template:

... lines 1 - 35
public function showCategoryAction($id)
{
... lines 38 - 47
$fortunesPrinted = $this->getDoctrine()
->getRepository('AppBundle:FortuneCookie')
->countNumberPrintedForCategory($category);
... lines 51 - 52
return $this->render('fortune/showCategory.html.twig',[
'category' => $category,
'fortunesPrinted' => $fortunesPrinted,
]);
}
... lines 58 - 59

And now print it out: {{ fortunesPrinted }}, put that through Twig's number_format filter and add the word "total":

... lines 1 - 12
<th>
Printed History ({{ fortunesPrinted|number_format }} total)
</th>
... lines 16 - 36

Amazing. You already know the next step: we need to create a new countNumberPrintedForCategory method inside of FortuneCookieRepository and make it query not for an object, but just a single number: the sum of how many times each fortune has been printed. That means we'll be totaling the numberPrinted property on FortuneCookie:

... lines 1 - 12
class FortuneCookie
{
... lines 15 - 45
/**
* @var integer
*
* @ORM\Column(name="numberPrinted", type="integer")
*/
private $numberPrinted;
... lines 52 - 174
}

Open FortuneCookieRepository and add the new public function. We're expecting a Category object, so I'll type-hint the argument like a nice, respectable programmer. Every query starts the same: $this->createQueryBuilder() and we'll use fc as the alias. Keep the alias consistent for an entity, it'll save you heartache later.

Next, we need an andWhere() because we need to only find FortuneCookie results for this Category. So, fc.category - because category is the name of the property on FortuneCookie for the relationship. Now, equals :category. And next, we'll set that parameter:

... lines 1 - 14
public function countNumberPrintedForCategory(Category $category)
{
return $this->createQueryBuilder('fc')
->andWhere('fc.category = :category')
->setParameter('category', $category)
... lines 20 - 22
}
... lines 24 - 26

This looks like every query we've made before, and if we finish now, it'll return FortuneCookie objects. That's lame - I want just the sum number. To do this, call select(). Nope, this is not addSelect like we used before. When we call createQueryBuilder from inside FortuneCookieRepository, the query builder has a ->select('fc') built into it. In other words, it's selecting everything from FortuneCookie. Calling select() clears out anything that's being selected and replaces it with our SUM(fc.numberPrinted) as fortunesPrinted:

... lines 1 - 14
public function countNumberPrintedForCategory(Category $category)
{
return $this->createQueryBuilder('fc')
->andWhere('fc.category = :category')
->setParameter('category', $category)
->select('SUM(fc.numberPrinted) as fortunesPrinted')
... lines 21 - 22
}
... lines 24 - 26

We're giving the value an alias, just like you can do in SQL. Now, instead of an object, we're getting back a single field. Let's finish it! Add getQuery().

Last, should we call execute() or getOneOrNullResult()? If you think about the query in SQL, this will return a single row that has the fortunesPrinted value. So we want to return just one result - use getOneOrNullResult():

... lines 1 - 14
public function countNumberPrintedForCategory(Category $category)
{
return $this->createQueryBuilder('fc')
->andWhere('fc.category = :category')
->setParameter('category', $category)
->select('SUM(fc.numberPrinted) as fortunesPrinted')
->getQuery()
->getOneOrNullResult();
}
... lines 24 - 26

Love it! I'm curious to see what this query returns, so let's var_dump $fortunesPrinted inside our controller:

... lines 1 - 35
public function showCategoryAction($id)
{
... lines 38 - 47
$fortunesPrinted = $this->getDoctrine()
->getRepository('AppBundle:FortuneCookie')
->countNumberPrintedForCategory($category);
var_dump($fortunesPrinted);die;
... lines 52 - 56
}
... lines 58 - 59

Refresh! It's just what you'd expect: an array with a single key called fortunesPrinted. So the $fortunesPrinted variable isn't quite a number - it's this array with a key on it. But let me show you a trick. I told you about execute() and getOneOrNullResult(): the first returns many results, the second returns a single result or null. But if you're returning a single row that has only a single column, instead of getOneOrNullResult(), you can say getSingleScalarResult():

... lines 1 - 14
public function countNumberPrintedForCategory(Category $category)
{
return $this->createQueryBuilder('fc')
->andWhere('fc.category = :category')
->setParameter('category', $category)
->select('SUM(fc.numberPrinted) as fortunesPrinted')
->getQuery()
->getSingleScalarResult();
}
... lines 24 - 26

This says: ok, you're only returning one row with one column, let me just give you that value directly. This is really handy for SUMs and COUNTs.

Refresh! Hey, we have just the number! Time to celebrate - take out the var_dump, refresh and... great success! So not only can we select specific fields instead of getting back objects, if you're selecting just one field on one row, getSingleScalarResult() is your new friend.

Leave a comment!

13
Login or Register to join the conversation
Default user avatar
Default user avatar brentheigold | posted 4 years ago

Hi, I like the tutorial, very straight forward.

One question - do you have to actually create a new migration and add this new numberPrinted field to your physical database?

28 Reply

Hey brentheigold

Migrations are for updating your DB schema, so, whenever you have to add a new field, a new table or updating an existing field you should generate a migration for it

Cheers!

Reply
Default user avatar
Default user avatar Lavin Bassam | posted 4 years ago

very interesting tutorial can i ask question how to addapt this tutorial into let say querying to count total_price? thank you

Reply

Hey Lavin Bassam

If you need to do a "COUNT" operation, then you only have to change the "SUM" statement by a "COUNT", select the right field, and that's it.

Cheers!

Reply
Default user avatar

how can put a count number beside the checkboxes retrieved from an Entity class

Reply

Hey @mike

Can you elaborate a bit more your question. I don't fully understand what you mean

Reply
Default user avatar
Default user avatar Mykel Chang | posted 5 years ago

Hello,

About that getSingleScalarResult method :

What if there is no entity persisted at all in the first place ?
Will the method call raise an error / throw an exception ?

Reply

Hey Mykel Chang

That's correct!
If the query doesn't return a result it throws, or if it returns more than one result it throws too

Cheers!

Reply
Default user avatar
Default user avatar Dominik | posted 5 years ago

Hey, why you skip getManager() part in controller when you were writing scalar type result.

Reply
Default user avatar

Ok, EntityManager is only needed when you want to save data tego database. No need this guy when fetching data.

Reply

Hey Dominik,

Yes, getManager() return EntityManager instance which basically need when you want persist() an entity or flush() persisted ones.

Cheers!

Reply

Hey,
The use of alias into select('SUM(fc.numberPrinted) as fortunesPrinted') is not mandatory ? If you don't write the query works.

Reply

Hey Stéphane,

You're right, field alias is optional here. But if you missing this alias, the field name should be "SUM(fc.numberPrinted)" but "fortunesPrinted". So in this case you have to use $fortunesPrinted['SUM(fc.numberPrinted)'].

Cheers!

1 Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

This course is built on Symfony 2, but most of the concepts apply just fine to newer versions of Symfony. If you have questions, let us know :).

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=5.3.3, <7.3.0",
        "symfony/symfony": "2.6.*", // v2.6.13
        "doctrine/orm": "~2.2,>=2.2.3", // v2.4.8
        "doctrine/doctrine-bundle": "~1.2", // 1.6.4
        "twig/extensions": "~1.0", // v1.5.4
        "symfony/assetic-bundle": "~2.3", // v2.8.2
        "symfony/swiftmailer-bundle": "~2.3", // v2.3.12
        "symfony/monolog-bundle": "~2.4", // v2.12.1
        "sensio/distribution-bundle": "~3.0.12", // v3.0.36
        "sensio/framework-extra-bundle": "~3.0", // v3.0.29
        "incenteev/composer-parameter-handler": "~2.0", // v2.1.3
        "hautelook/alice-bundle": "0.2.*" // 0.2
    },
    "require-dev": {
        "sensio/generator-bundle": "~2.3" // v2.5.3
    }
}
userVoice