How to change Doctrine "findBy/findOneBy" functions's behaviors to reduce the number of queries? - sql

I'm working on a Symfony2 using Doctrine.
I would like to know how to change the behavior of "findBy" functions when retrieving my entities.
For example, if you call "findAll()", it returns all products.
$entities = $em->getRepository('ShopBundle:Product')->findAll();
However, how to reduce the number of queries, because, by default, it will create a new query each time I want to get a member linked to a join column. So if I get 100 entities, it will process 101 queries (1 to get all entities and 1 by entity to get join column).
So today, I use createQuery() function by specifying the joins. Is there a way to configure something about findBy functions to skip createQuery method ?
Thanks in advance !
K4

You can fetch out this in below way
public function findUser() {
$query = $this->getEntityManager()
->createQuery('SELECT us.id as id, us.name as user_name FROM Bundle:User us');
try {
return $query->getResult();
} catch (\Doctrine\ORM\NoResultException $e) {
return null;
}
}

Related

Laravel 8 relationship with two columns in each table

I have a multiple DB connection in Laravel 8 projects, I can't modify one of them which is mainly used in ROR project.
I need to fetch data from two tables only if id1, id2 columns have same value in both the tables. I went on google but did not find anything.
I tried some examples, either it is giving me N+1 query issue or second table o/p always null
function example()
{
return $this->belongsTo(Model::class, 'id1', 'id1')->where('id2', $this->id2);
}
function example()
{
return $this->belongsTo(Model::class, 'id1,id2', 'id1,id2');
}
I came up with a solution from https://laravel.io/forum/06-26-2014-one-to-one-relationship-with-two-fieldscolumns-connection-how
I used join with Laravel relation query:
HouseHold::where("printedcardno","=","17130103-0124")->with(array('members' => function($query)
{
$query->leftJoin('PatientCard', function($join)
{
$join->on('PatientCard.HouseHoldID', '=', 'shp_members.HouseHoldID')
->on('PatientCard.MemberID', '=', 'shp_members.MemberID');
});
}))->get()->take(10)->toArray();
I think this should be good approach as it fixed N+1 query issue and getting desired o/p.

Union between optional and non-optional tables

I have two queries that select records where a union needs to be taken, one of which is a left join and one of which is a regular (i.e. inner) join.
Here's the left join case:
def regularAccountRecords = for {
(customer, account) <- customers joinLeft accounts on (_.accountId === _.accountId) // + some other special conditions
} yield (customer, account)
Here's the regular join case:
def specialAccountRecords = for {
(customer, account) <- customers join accounts on (_.accountId === _.accountId) // + some other special conditions
} yield (customer, account)
Now I want to take a union of the two record sets:
regularAccountRecords ++ specialAccountRecords
Obviously this doesn't work because in the regular join case it returns Query[(Customer, Account),...] and in the left join case it returns Query[(Customer, Rep[Option[Account]]),...] and this results in a Type Mismatch error.
Now, If this were a regular column type (e.g. Rep[String]) I could convert it to an optional via the ? operator (i.e. record.?) and get Rep[Option[String]] but using it on a table (i.e. the accounts table) causes:
Error:(62, 85) value ? is not a member of com.test.Account
How do I work around this issue and do the union properly?
Okay, looks like this is what the '?' projection is for but I didn't realize it because I disabled the optionEnabled option in the Codegen. Here's what your codegen extension is supposed to look like:
class MyCodegen extends SourceCodeGenerator(inputModel) {
override def TableClass = new TableClassDef {
override def optionEnabled = true
}
}
Alternatively, you can use implicit classes to tack this thing onto the generated TableClass yourself. Here is how that would look:
implicit class AccountExtensions(account:Account) {
def ? = (Rep.Some(account.id), account.name).shaped.<>({r=>r._1.map(_=> Account.tupled((r._2, r._1.get)))}, (_:Any) => throw new Exception("Inserting into ? projection not supported."))
}
NOTE: be sure to check the field ordering, depending on how this
projection is done, the union query might put the ID field in the wrong
place in the output, use
println(query.result.statements.headOption) to debug the output
SQL to be sure.
Once you do that, you will be able to use account.? in the yield statement:
def specialAccountRecords = for {
(customer, account) <- customers join accounts on (_.accountId === _.accountId)
} yield (customer, account.?)
...and then you will be able to unionize the tables correctly
regularAccountRecords ++ specialAccountRecords
I really wish the Slick people would put a note on how the '?' projection is useful in the documentation beyond the vague statement 'useful for outer joins'.

symfony (doctrine): DQL query, how to exclude results?

I have very basic knowledge of SQL and don't how to modify my query so it returns the expected result.
My entities Activity has a field members (a manyToMany relation). An activity can be done by several members.
I want to select all the activities a specific member does NOT take part into. I know the member's id (my variable $selfId).
Here's what I'm doing in ActivityRepository:
public function getCollectiveActivities($selfId)
{
$qb = $this->createQueryBuilder('a');
$qb->join('a.members', 'm')
->where('m.id != :selfId')
->setParameter('selfId', $selfId);
return $qb
->getQuery()
->getResult();
}
My problem: when an activity "owns" two or more members, if one of them has $selfId as id, this activity ends up in my query results.
But since one of the members of this activity has the id $selfId, it should not be returned. What am I doing wrong?
EDIT
I think I need to be more restrictive, I just don't know how.
Let's say I have two activities:
activity1 which is owned by member1 and member2
activity2 which is owned by member3 and member4
$selfId = 3 (this means I don't want to fetch activities owned by member3)
From what I understand, the join clause might return lines like these:
activity1 | memberId: 1 (different from selfId = 3 so activity1 will be fetched)
activity1 | memberId: 2 (different from selfId = 3 so activity1 will be fetched)
activity2 | memberId: 3 (= selfId so activity2 shouldn't be fetched)
activity2 | memberId: 4 (different from selfId = 3 so activity2 WILL be fetched. PROBLEM???)
EDIT 2
Others already faced the same problem and found this solution and this one, but they seem a bit hacky. Any clue on how to improve them would be welcome.
You have to specifically select the results you want to be hydrated. The problem you're seeing is that you're just selecting activity.
Then when you call $activity->getMembers() members are lazy loaded, and this doesn't take into account your query.
You can avoid this like so:
public function getCollectiveActivities($selfId)
{
$qb = $this->createQueryBuilder('a');
$qb->addSelect('m');
$qb->join('a.members', 'm')
->where('m.id != :selfId')
->setParameter('selfId', $selfId);
return $qb
->getQuery()
->getResult();
}
This means your fetched activity will already have its members hydrated and restricted by your query condition.
I ended up adapting a solution posted by #NtskX.
public function getCollectiveActivities($selfId)
{
$qbMyCollectives = $this->createQueryBuilder('ca');
$qbMyCollectives
->select('ca.id')
->leftJoin('ca.members', 'm')
->where('m.id = :selfId')
->setParameter('selfId', $selfId);
$qb = $this->createQueryBuilder('a');
$qb
->where($qb->expr()->notIn('a.id', $qbMyCollectives->getDQL()))
->setParameter('selfId', $selfId); // I had to declare the parameter one more time
return $qb
->getQuery()
->getResult();
}
I really thought someone would show up with a better way to do the join, so any other answer is much welcome.
public function getCollectiveActivities($selfId)
{
return $this->createQueryBuilder('a')
->join('a.members', 'm')
->where($qb->expr()->neq('c.selfId', $selfId))
->getQuery()
->getResult();
}

How to use YII's createCommand to also return total items?

Let's say I do a simple query:
$products =
Yii::app()->db->createCommand()->setFetchMode(PDO::FETCH_OBJ)
->select('*')
->from('products')
->limit(9)
->queryAll();
Let's say there are 500 products in the database. Is there a way I can get YII to automatically return the total number (count) of products if the "limit" was included? Perhaps return an object like this:
$products->products = array( ... products ... )
$products->totalProducts = 500;
The problem is, if LIMIT is included, it will return items, and the count will therefore be 9. I want a solution whereby it will return the 9 items, but also the count of say 200 items if there were 200 items.
Why not an easy:
$сount = Yii::app()->db->createCommand('select count(*) from table')->queryScalar();
echo $count;
You'll either have to run two queries (a count(*) query without the limit and then the limited query) or you can send you can retrieve your products using CSqlDataProvider and let it do it for you. But it generally takes two queries.
Note: one of the nifty features in Yii 1.1.13 is that you can send your query builder command in to the CSqlDataProvider if you're going to be using a dataprovider. More information at on this pull request that fixed it. That way, you can both use the power of query builder while also being able to shift your data into a dataprovider. Previously you had to build your SQL statement manually or grab the queryText of the command.
Yii::app()->db->createCommand('select count(*) from tbl_table')->queryScalar();
Try to use execute() instead of query() because execute returns rows count.
example:
$rowCount = $command->execute();
You could try using COUNT like this:
$dbCommand = Yii::app()->db->createCommand("
SELECT COUNT(*) as count FROM `products`");
$data = $dbCommand->queryAll();
Hope that helps!
EDIT: You might find this useful too: CDataProvider
Try this -
$sql = Yii::app()->db->createCommand('select * from tbl_table')->queryAll(); //It's return the Array
echo count($sql); //Now using count() method we can count the array.

NHibernate Criteria Transform Result

I have an SecurityGroup entity witch has Memebers and Application properties.
Application is a lookup.
So securityGroups is in many-to-many relationship with User table and one-to-many with LookupApplciation (FK)
Now I want to select all application linked to a specific user.
I have follow criteria:
public IList<LookupApplication> GetApplicationByUser(User user)
{
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>();
}
It trows an exception
The value "Edi.Advance.Core.Model.Security.SecurityGroup" is not of type "Edi.Advance.Core.Model.Lookups.LookupApplication" and cannot be used in this generic collection.
Parameter name: value
and it is right.
How can I transform the result to IList<LookupApplication>?
Thanks
You can only return the type which you create the criteria from.
The easiest way starting from the code you have will be:
return
this.Session.CreateCriteria(typeof(SecurityGroup), "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<SecurityGroup>()
// get the lookup applications in memory
.SelectMany(x => x.LookupApplications);
This loads all SecurityGroups into memory, even if it only needs the LookupApplications. This might not be an issue when you need them anyway or when they are small.
You could also reverse the query and start from the LookupApplication
return
this.Session.CreateCriteria(typeof(LookupApplication), "la")
// requires navigation path from SecurityGroup to LookupApplication
.CreateCriteria("la.SecurityGroup", "sg")
.CreateAlias("Members", "u")
.CreateAlias("Application", "al")
.Add(Restrictions.Eq("u.Id", user.Id))
.List<LookupApplication>()
Or use HQL, which has some features not available in Criteria, items gets all the items from a collection:
select sg.LookupApplications.items
from SecurityGroup sg inner join sg.Members u
where u.Id = :userId
HQL is actually recommended when you don't have dynamic queries.
Update, from isuruceanu's comment:
Session
.CreateQuery(
#"select sg.Application
from SecurityGroup sg
inner join sg.Members u
where u.Id = :userId")
.SetParameter("userId", user.Id)
.List<LookupApplication>();
It depends on how the SecurityGroup looks like and how LookupApplication looks like.
You could use ResultTransformer like:
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();
Granted that SecurityGroup has properties matchinig LookupAppliaction, or else you have to project those properties like:
.SetProjection(NHibernate.Criterion.Projections.ProjectionList()
.Add(Projections.Property("Number"), "OrderNumber")
.Add(Projections.Property("CreatedOn"), "CreatedOn")
.Add(Projections.Property("MemeberName"), "Name"))
.SetResultTransformer(Transformers.AliasToBean<LookupApplication>())
.List<LookupApplication>();