Why Manual prefetch (via select/columns) not supported with accessor 'multi'? - relationship

When I run code:
return $self->result_source->schema->resultset('Locality')->search(
{
'addresses_view.usage' => 0
,'me.id' => $self->id
}
,{
join => { servers => 'addresses_view' }
}
);
The next sql query generated:
SELECT
"me"."id", "me"."active", "me"."priority",
"me"."country_id", "me"."name"
FROM "localities" "me"
LEFT JOIN "servers" "servers" ON "servers"."locality_id" = "me"."id"
LEFT JOIN "pool_addresses_view" "addresses_view" ON "addresses_view"."server_id" = "servers"."id"
WHERE ( ( "addresses_view"."usage" = ? AND "me"."id" = ? ) )
And executed OK.
But when I want to add columns to 'SELECT'
,'+columns' => [ 'addresses_view.ip', 'addresses_view.id' ]
I get error: Manual prefetch (via select/columns) not supported with accessor 'multi'
I have found same qestion at www.mail-archive.com/dbix-class#, but I do not understand how pass the columns attribute to search_related as well to restrict the columns to select
Is there a way to add columns to 'SELECT' clause?

DBIx::Class::ResultSet#prefetch is just sugar which internally populates the join and columns attributes.
DBIx::Class::Manual::Joining contains lots of useful info about what's possible and how which should answer your question.

Related

Linq to entites (EF6) return latest records of each group using Row number in T-SQL server

I'm using Linq to entities to get most recent updated record of each group. But actually when I checked in sql profiler my Ling query generated many sub-query so that It really take too much time to complete. To solve this performance problem, I already wrote native T-Sql mentioned below so that I'm looking for solution to use Linq query that entity framework is generating the same my query using (ROW_NUMBER() OVER(PARTITION BY ...) . Below is my sample data :
Parent and Child tables:
Sample data of Parent and Child tables:
Below is my query result:
TSQL query:
WITH summary AS (
SELECT a.ParentId
,a.Name
,a.Email
,p.Created
,p.[Status],
ROW_NUMBER() OVER(PARTITION BY p.ParentId
ORDER BY p.Created DESC) AS rk
FROM Parent a
LEFT JOIN Child p
ON a.ParentId = P.ParentId
)
SELECT s.*
FROM summary s
WHERE s.rk = 1
My sample C# using Linq:
using (DbContext context = new DbContext())
{
return context.Parents.Where(p => p.ParentId == parentId)
.Include(a => a.Childs)
.Select(x => new ObjectDto()
{
ParentId = x.ParentId,
Status = x.Childs.OrderByDescending(a => a.Created).FirstOrDefault(p => p.ParentId).Status,
ChildName = x.Childs.OrderByDescending(a => a.Created).FirstOrDefault(p => p.ParentId).ChildName
})
.ToList();
}
There are a couple of things to improve your C# query:
using (DbContext context = new DbContext())
{
return context.Parents.Where(p => p.ParentId == parentId)
.Include(a => a.Childs)
.Select(x => new ObjectDto()
{
ParentId = x.ParentId,
Status = x.Childs.OrderByDescending(a => a.Created).FirstOrDefault(p => p.ParentId).Status,
ChildName = x.Childs.OrderByDescending(a => a.ChildName).FirstOrDefault(p => p.ParentId).ChildName
})
.ToList();
}
Firstly the Include call does nothing here as you're not just returning EF entities (and thus the lazy loading semantics don't apply).
Secondly: avoid the repeated subqueries with a let clause.
(Also the lambda passed to FirstOrDefault must be an error as it takes a Func<T, bool> which that isn't.)
Thus
using (DbContext context = new DbContext()) {
return await (from p in context.Parents
where p.ParentId == parentId
let cs = p.Childs.OrderByDescending(a => a.Created).FirstOrDefault()
select new ObjectDto {
ParentId = p.ParentId,
Status = cs.Status,
ChildName = cs.ChildName
}).ToListAsync();
}
Otherwise it looks reasonable. You would need to look at the generated query plan and see what you can do with respect to indexing.
If that doesn't work, then use a stored procedure where you have full control. (Mechanical generation of code – without a lot of work in the code generator and optimiser – can always be beaten by hand written code).

Subquerys in cakephp 3.x, new ORM?

I'm new in Cakephp 3.x and I'm having some trouble to create a subquery in the new ORM format. I have this report in my application, that needs to return the follow result:
1. There are three entities - Users, Calls, CallStatus.
2. Users hasMany Calls, Calls hasMany CallStatus.
3. I need to count how many CallStatus each user has in Calls.
Now follow the query that I need to put on new ORM format:
SELECT U.name,
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =1 and C.user_id=U.id) AS 'Unavailable',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =2 and C.user_id=U.id) AS 'Busy',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =3 and C.user_id=U.id) AS 'Contacted',
(SELECT COUNT(*) FROM calls as C WHERE C.call_status_id =4 and C.user_id=U.id) AS 'Error'
FROM `users` AS U
WHERE U.profile=3 and U.is_active=1
Could someone give me a help, please? Thanks
If I understand you correctly, you want to see the number of calls for every callstatus you have for a specific user.
Try the following. Note that I used the CakePHP convention for naming the callstatuses (which is plural).
// get the tableregistry
use Cake\ORM\TableRegistry;
$callstatuses = Cake\ORM\TableRegistry::get('Callstatuses');
// for user with id 2, get the number of calls for each callstatus
$callstatuses->find()
->contain(['Calls'])
->where(['Calls.user_id' => 2, 'User.is_active' => 1])
->countBy('name')
->toArray();
// output could be:
//[ 'Unavailable' => 2, 'Busy' => 1 ]
You can find information about creating queries in the CakePHP book: see 'Query Builder'.
If you want to know more about working with/on queries, note that queries are Collections. Anything you can do on a Collection object, you can also do in a Query object. See the Collection section in the CakePHP book.
You have to use subqueries, as many as you want!
Here is an example for your case:
$q = $this->Calls->find();
$q1->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 1]);
$q2->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 2]);
$q3->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 3]);
$q4->select([$q->func()->count('*')])
->where(['Calls.user_id = Users.id', 'call_status_id' => 4]);
$qUsers = $this->Users->find()
->select([
'id',
'first_name',
'Unavailable' => $q1,
'Busy' => $q2,
'Contacted' => $q3,
'Error' => $q4
])
->where(['profile' => 3, 'active' => 1])
->all();
Note: That nicer if you use a loop to create suqueries in this case.

use no join. cake php

I have the following code.
var $uses = array('TABLE1','TABLE2','TABLE3');
function test() {
$this->Table1->updateAll(
array('Table1.field1' => 'some value'),
array('Table1.field1 ' => 'some condition')
);
......
.....
problem is that when I try to update only one table...Table1, it joins other tables with it.
UPDATE
`Table1`
LEFT JOIN
`Table2`
ON
(`Table1`.`id` = `Table2`.`uid`)
LEFT JOIN
`Table3`
ON
(`Table1`.`Table3_id` = `Table3`.`id`)
SET
`Table1`.`field1` = 1
WHERE
some condition.......
How can I not join the table and run update only on Table1?
Edit:
I used this but did not work :
$this->Table1->unBindModel(array(hasMany => array('Table2', 'Table3')));
firstly, stop using $uses. it will only cause you more pain than needed.
to avoid the joins use Model::unbindModel(array('relationType' => array('Relation')) http://book.cakephp.org/view/1045/Creating-and-Destroying-Associations-on-the-Fly
Try setting your recursive level to -1. I'm not sure whether this affects Update calls but it's worth a try.
$this->Table1->recursive = -1;
$this->Table1->updateAll(...);

Zend DB Select nested Joins

I'm trying to realize the following query with zend db select:
SELECT `uac`.`uid`, `u`.`uid`, `g`.`groupid`, `g`.`packageid`
FROM `user_has_data` AS `uac`
INNER JOIN `users` AS `u` ON u.uid = uac.uid
LEFT JOIN (`user_in_group` AS `uig`
INNER JOIN `groups` AS `ag` ON (ag.groupid = uig.groupid) AND (ag.packageid = 2)
) AS `g` ON uac.uid = g.uid
WHERE (uac.dataid = '3') AND (u.uname LIKE 'test%')
GROUP BY `u`.`uid`
I got the following, but got stuck when trying to convert the nested join to zend structure:
$select = $db->select()->from(array('uac' => 'user_has_data'), array('uac.uid'))
->join(array('u' => 'users'), 'u.uid = uac.uid', array('uid', 'uname'))
->joinLeft(array('uig' => 'user_in_groups'), 'uig.uid = uid', array('agid' => 'uig.groupid'))
->join(array('ag' => 'groups'), '(ag.agid = uig.groupid) AND ( ag.packageid = '.$packageid.')', array('packageid'))
->where('uac.dataid = ?', $dataid)
->where('(u.uname LIKE ?)', $value)
->group('u.uid');
Is it possible to get the given sql query into a suitable structure for zend db select? I need a select object for further handling in a paginator, so if this is not possible I have to make a straight forward sql query.
I don't believe you can do a nested join like that with Zend_Db_Select. Your best bet is to create your own paginator adapter (it's easier than you might think, look at the Select one) and manage the LIMIT part of the SQL yourself.
I know the question is old, but i think people might still benefit from the right answer for this question.
That nested join is the same as
SELECT * FROM `user_in_group` AS `uig`
INNER JOIN `groups` AS `ag` ON (ag.groupid = uig.groupid) AND (ag.packageid = 2)
So this should work just fine.
$subquery = $db->select()
->from(array('uig' => 'user_in_groups'),'*')
->joinInner(array('ag'=>'groups'),'(ag.groupid = uig.groupid) and (ag.packegeid = 2)',array('*'));
$select = $db->select()->from(array('uac' => 'user_has_data'), array('uac.uid'))
->join(array('u' => 'users'), 'u.uid = uac.uid', array('uid', 'uname'))
->joinLeft('g'=>$subquery), 'uac.uid = g.uid', array('g.groupid', 'g.packageid'))
->where('uac.dataid = ?', $dataid)
->where('(u.uname LIKE ?)', $value)
->group('u.uid');

CakePHP 3 find() with beforeFind() callback SQL issue

I'm having a problem with data integrity when using find() in my controller in conjunction with beforeFind() in a behavior callback. The WHERE Submissions.site_id is not being added in the WHERE clause like it should be. I get different result sets depending on where the WHERE clause is set.
in my SubmissionsController:
public function index()
{
$query = $this->Submissions->find('all')
->where(['user_id' => $this->Auth->user('id')])
->contain(['Users', 'Categories']);
$this->set('submissions', $this->paginate($query));
}
In my beforeFind() Model callback (attached as a 'TenantBehavior' to
$query->where([$this->_table->alias().'.'.'site_id' => 3]);
The problem is that with the above, the SQL generated puts the "WHERE" clause as an AND on the JOIN condition like so, and NOT on the actual WHERE:
...
FROM
submissions Submissions
INNER JOIN users Users ON (
Users.id = (Submissions.user_id)
AND Users.site_id = 3
)
INNER JOIN categories Categories ON (
Categories.id = (Submissions.category_id)
AND Categories.site_id = 3
)
WHERE
user_id = 315
If I remove the beforeFind() ->where and instead place it on the controller ->where I get the expected SQL and result set like so:
...
FROM
submissions Submissions
INNER JOIN users Users ON (
Users.id = (Submissions.user_id)
AND Users.site_id = 3
)
INNER JOIN categories Categories ON (
Categories.id = (Submissions.category_id)
AND Categories.site_id = 3
)
WHERE
(
user_id = 315
AND Submissions.site_id = 3
)
Thoughts? Suggestions?
EDIT
As #ndm's suggestion, I began to update and provide much more context. In doing so I discovered (like an idiot) that I was missing the $this->addBehavior('Tenant'); on my 'SubmissionsTable' model. Adding this of course solved the issue.
$query = $this->Submissions->find('all', [
'conditions' => [
'user_id' => $this->Auth->user('id')
],
'contain' => [
'Users',
'Categories',
]
]);
Passing conditions as inline array will solve your issue and append beforeFind() where conditions properly.

Resources