Getting a 'flat' array out of Doctrine 2 queryBuilder with addSelect - php

I'm wondering if there's a clean way to get around this little conundrum... Been getting around it, but finally have time to look into "the right way"!
Doctrine Query Builder Snippet
public function getSprockets(array $id_list, $include_stats = false )
{
$qb = $this->getEntityManager()->createQueryBuilder()
->select('p')
->from( 'Entity\Foo', 'p' )
->where('p.id IN ( :pids )')
->setParameter('pids', $id_list);
if( $include_stats )
$qb->addSelect( '(SELECT SUM( a.value ) FROM Entity\Stats a WHERE a.foo_id = p.id AND a.type="bar" ) AS bar_count' );
$res = $qb->getQuery()->getArrayResult();
return $res;
}
As is, this snippet behaves very differently whether addSelect is invoked or no. If it is not there, I get a nice flat array as is expected. If the addSelect is however used ($include_stats is true), a row in $res is quite different, containing:
the entity's parts in a $row[0]
and the addSelect result at the base of the $row, e.g., $row['bar_count']
I realize that listing the columns in ->select('p.id, p.that, p.this') ... gets around the problem, but I don't want to have to maintain these many queries each time the schemata is changed.
Is there a convenient-or-built-in way to get a flat array out of getArrayResult?
Thanks!

Related

sum incorrect after using join in Eloquent Laravel

I had to join two tables together and after some trial and error I was able to get it, but while trying to sum the joined field to later sort by that quantity I realized the numbers were much higher than what they are supposed to be. I tried to follow this question's solution but it didn't work for me: The sum amount from join tables is incorrect . Here is my query down below:
$query = Item::has('backorders')
->join('SOP10200', 'IV00102.ITEMNMBR','=','SOP10200.ITEMNMBR')
->select('IV00102.ITEMNMBR',
//These sums are wrong when using join
Item::raw("SUM(IV00102.QTYONHND) as qty"),
Item::raw("SUM(IV00102.QTYONORD) as ordered"),
Item::raw("SUM( ( CASE WHEN IV00102.LOCNCODE LIKE 'IT-%' THEN IV00102.QTYONHND END ) ) as transit"),
Item::raw("SUM(SOP10200.QUANTITY) as backorder"),
)
->where('IV00102.PRIMVNDR', Auth::user()->vendor_id)
->groupBy('IV00102.ITEMNMBR')
->orderBy($group['field'], $group['sort'])
->limit(2147483647);
Here are my relationships:
public function item(){
return $this->belongsTo(Item::class, 'ITEMNMBR', 'ITEMNMBR');
}
public function backorders(){
return $this->hasMany(Backorder::class, 'ITEMNMBR', 'ITEMNMBR')->where('SOPTYPE', 5);
}
I'd like to note that I could not use the relational identifiers in the join and had to resort to referencing the tables directly, but the relationships work otherwise, and have been tested without the join. In other words the whole reason I am joining is so that I can sort by the backorders(SOP10200). if there is a more elegant solution to this I am all for it. To avoid repeated offered solutions I'd like to also attach my previous question related to and solving the join issue: Is there a way to select fields from an eager loaded table in Laravel?
I solved this by implementing a subquery, doing sum on the subquery and then finally joining it to the main query:
$subQuery = Backorder::select('ITEMNMBR', Backorder::raw('sum(QUANTITY) as backorder'), 'SOPTYPE')
->groupBy('ITEMNMBR', 'SOPTYPE');
$subQuerySql = $subQuery->toSql();
$query = Item::has('backorders')
->where('IV00102.PRIMVNDR', Auth::user()->vendor_id)
->leftjoin(Item::raw('(' . $subQuerySql . ') as bbo'),function($join) use ($subQuery) {
$join->on('IV00102.ITEMNMBR', '=', 'bbo.ITEMNMBR');
})
->where('bbo.SOPTYPE', 5)
->select('IV00102.ITEMNMBR',
'bbo.backorder',
Item::raw("SUM(IV00102.QTYONHND) as qty"),
Item::raw("SUM(IV00102.QTYONORD) as ordered"),
Item::raw("SUM( ( CASE WHEN IV00102.LOCNCODE LIKE 'IT-%' THEN IV00102.QTYONHND END ) ) as transit"),
)
->mergeBindings($subQuery->getQuery())
->groupBy('IV00102.ITEMNMBR', 'bbo.backorder')
->orderBy($group['field'], $group['sort'])

How to add a column in Symfony default query builder

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)

Aggregating fields to Fetched Objects in Doctrine2

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.

Codeigniter count_all for pagination

I'm trying to create pagination in codeigniter and I have it working, but I have a small issue. It seems to be loading all the entries in my database and not the selected ones I want.
public function original_count() {
$this->db->where('type', 'Original');
return $this->db->count_all("story_tbl");
}
I know that whats happening is that the last line is overrighting my previous statements. I can't seem to find a way around it though. I tried just a staight sql statement and then returning it, but I could not get that to work either.
this was my statement...
SELECT COUNT(*) FROM story_tbl where type = 'Original';
Some help would much appreciated! :)
CI has inbuilt count method
count_all_results()
Permits you to determine the number of rows in a particular Active Record query. Queries will accept Active Record restrictors such as where(), or_where(), like(), or_like(), etc. Example:
https://www.codeigniter.com/userguide2/database/active_record.html
$total_count = $this->db->count_all_results('story_tbl', array('type' =>'Original'));
You could also use the built-in num_rows() function...
$query = $this->db->where('type', 'original')->get('story_tbl');
return $query->num_rows();
First Try this one.
$query = $this->db->where('tbl_field', 'value')
->get('your_table');
return $query->num_rows();
Besides this Codeigniter has it own function like the following.
$this->db->where('tbl_field', 'value')
->get('your_table');
return $this->db->count_all_results();
Or use this.
return $this->db->count_all('your_table');
Where wont work on count_all condition.. you can use below method to find out total number of rows..
public function count_all() {
$this->db->select ( 'COUNT(*) AS `numrows`' );
$this->db->where ( array (
'type' => 'Original'
) );
$query = $this->db->get ( 'story_tbl' );
return $query->row ()->numrows;
}

What is a good approach in dealing with a set of select statements but different conditions?

One problem that I am facing is having many queries with similar select statements but different join/where statements.
Below is an example of a code that I am working on via CodeIgniter. What I normally do is make one function, get(), that accepts an array of random keys/values. Depending on what keys/values are passed, it will generate and run the appropriate query. Now, I am wondering is this a good way of doing things? Because as you can see, this function becomes more and more complex. Initially, I had a bunch of functions such as get_all(), get_only_lessons(), etc but it becomes kinda annoying having to repeat the same set of code with one or two lines that are different.
My question is what is the best way with dealing with this problem.
function get($param = NULL)
{
/*
SELECT m.id AS id, CAST(m.order_number AS SIGNED) AS order_number, m.name AS name, m.permalink as permalink,
m.suplesson_id as suplesson_id, CAST(sm.order_number AS SIGNED) AS suplesson_order_number
FROM lessons m
JOIN courses c ON m.course_id = c.id
LEFT JOIN lessons sm ON m.suplesson_id = sm.id
WHERE [various]
*/
$select = 'm.id AS id, CAST(m.order_number AS SIGNED) AS order_number, m.name AS name, m.permalink as permalink, ';
$select .= ' m.suplesson_id as suplesson_id';
if (isset($param['id']) || isset($param['suplesson_order_number']) || isset($param['permalink']))
$select .= ', CAST(sm.order_number AS SIGNED) AS suplesson_order_number ';
$this->db->select($select);
$this->db->from($this->table_name.' m');
$this->db->join($this->courses_table_name.' c', 'm.course_id = c.id');
if (isset($param['id']) || isset($param['suplesson_order_number']) || isset($param['permalink']))
$this->db->join($this->table_name.' sm', 'm.suplesson_id = sm.id', 'left');
// where clauses
if (isset($param['course_id']))
$this->db->where(array('c.id' => $param['course_id']));
if (isset($param['id']))
$this->db->where(array('m.id' => $param['id']));
if (isset($param['order_number']))
$this->db->where(array('m.order_number' => $param['order_number']));
if (isset($param['permalink']))
$this->db->like('m.permalink', $param['permalink'], 'none');
if (isset($param['suplesson_id']))
$this->db->where(array('m.suplesson_id' => $param['suplesson_id']));
if (isset($param['suplesson_order_number']))
$this->db->where(array('sm.order_number' => $param['suplesson_order_number']));
if (isset($param['NULL']))
$this->db->where('m.'.$param['NULL'].' IS NULL');
if (isset($param['NOT NULL']))
$this->db->where('m.'.$param['NOT NULL'].' IS NOT NULL');
$this->db->order_by('order_number');
// filter based on num_rows/offset
if (isset($param['id']) || isset($param['permalink']))
$this->db->limit(1);
if (isset($param['num_rows']) && isset($param['offset']))
$this->db->limit($param['num_rows'], $param['offset']);
$query = $this->db->get();
// return row if expecting 1 result
if (isset($param['id']) || isset($param['suplesson_order_number']) || isset($param['permalink']))
return ($query->num_rows() == 1) ? $query->row_array() : NULL;
return ($query->num_rows() > 0) ? $query->result_array() : NULL;
}
The usual way of running DB queries is to structure your model code to have multiple function calls, each one relating to one SQL statement, for example:
function get_user($userId)
{
$this->db->get_where('user', array('userId' => $userId))
//...
//...
}
function delete_user($userId)
{
$this->db->delete('user',array('userId' => $userId))
}
You might create a model class called User_model which contains all of the functions you need for reading/updating the user table, so in your controller you call down to the specific model function
$user = $this->User_model->get_user($userId)
It looks like you're trying to construct one giant model function which checks various parameters in order to determine what SQL statement to run. This isn't good design and doesn't fit well with Codeignitors MVC model.
Instead, create a separate model for each table, then in each model create separate functions for each SQL operation you wish to run. Call these models from your controllers to get/update/delete data in your tables.

Categories