Limit a number of SQL queries with aggregated entities - php

Let's say I have users and things tables. Each user can have many things. One thing is assigned to exactly one user.
I have a code to fetch all things:
$things = $this->createQueryBuilder('t')->getQuery()->getResult();
To display all users details I do this:
foreach ($things as $thing) {
var_dump($thing->getUser()->getName());
}
The problem is this code needs n+1 queries (1 query to fetch all n things and n queries to fetch all users assigned to them).
The question is: does it possible to somehow build all things objects with assigned users objects using one query?

Yes it possible by joining and adding to select the related table User
$things = $this->createQueryBuilder('t')
->addSelect('u')
->join('t.user', 'u')
->getQuery()->getResult();
user in t.user is the name of your property in the Thing class.

Related

Php join multiple query results laravel

I have two queries that I need to run to obtain, a list of contracts and a list of sales, due to the fact that there is a duplicate contract for each customer I have written a query that will return customer_id and the date of their final contract while joining on our products table to filter for certain products. another query returns a list of sales for a given area by connecting to a couple of tables as well is their anyway after calling these queries I could join them using php .
$contracts = DB::table('contracts as c').....
$leads= DB::table('contracts as l')..
so after creating these two collections that both have the column customer_id, i want to join them using PHP if these where both tables in my database this would be a simple left_join, however, I can't figure out a way to do it with the output of the quarry .... thanks in advance for all the help. I have been doing some research and think i need to map somehow
$leads = $leads->map(function ($item, $key) {
$single_lead = $contract->where('l.customer_id',$item->spp.customer_id);
return collect($item)->merge($single_lead);
something like this but this is not working and I'm stuck so I should be going through each item in leads and then merge it with the contract information that has the same customer_id
Your question wasn't very clear but giving you a hint to do left join,
You can do it as:
$query = DB::table('contracts')->leftJoin('customer','customer.id','=','contracts.customer_id')->get()
You can see it in the Laravel Docs

Select rows using query builder in CakePHP based on the same end association but different relation to it

What I want to do is to get all rows related with user_id but in a different way.
First condition is to get all Books that are related with the User via Resources table where user_id is stored (in other words - Books owned by the User). Second condition is to get all Books that are related with the User through the Cities model again which is stored in the Resources table as well (Books that belong to Cities owned by the User).
I tried really a lot of things and I simply cannot make this two conditions work because I use JOIN (tried different combinations of innerJoinWith and leftJoinWith) on the same "end" model (User).
What I've done so far:
$userBooks = $this->Books->find()
->leftJoinWith("Resources.Users")
->leftJoinWith("Cities.Resources.Users")
->where(["Resources.Users" => 1])
->orWhere(["Cities.Resources.Users" => 1])
->all();
This of course does not work, but I hope you get the point about what I'm trying to achieve. The best what I was able to get with trying different approaches is the result of only one JOIN statement what is logical.
Basically, this can be separated into 2 parts which gives expected result (but I do not prefer it because I want it done with one query of course):
$userBooks = $this->Books->find()
->innerJoinWith("Resources.Users", function($q) {
return $q->where(["Users.id" => 1]);
})
->all();
$userBooks2 = $this->Books->find()
->innerJoinWith("Cities.Resources.Users", function($q) {
return $q->where(["Users.id" => 1]);
})
->all();
Also, before this I created an SQL script which works well and result is like expected:
SELECT books.id FROM books, cities, users_resources WHERE
(users_resources.resource_id = books.resource_id AND users_resources.user_id = 1)
OR
(users_resources.resource_id = cities.resource_id AND books.city_id = cities.id AND users_resources.user_id = 1)
This query works and I want to transfer it into ORM styled query in CakePHP to get both Books that are owned by the user and the ones that are connected with the User via Cities. I want somehow to separate these joins to individually filter data like I did in the SQL query.
EDIT
I've tried #ndm solution but the result is the same as where there is only 1 association (User) - I was still able to get data based on only one join statement (second one was ignored). Due to the fact I had to move on, I ended up with
$userBooks = $this->Books->find()
->innerJoinWith("Cities.Resources.Users"‌​)
->where(["Users.id" => $userId])
->union($this->Books->find()
->innerJoinWith("Resour‌​ces.Users")
->where([‌​"Users.id" => $userId])
)
->all();
which outputs correct result but not in very effective way (by union of 2 queries). I would really like to know the best way to approach this as this is a very common case (filtering by related model (user) that is associated with other models).
The ORM (specifically the eager loader) doesn't allow joining the same alias multiple times.
This can be worked around in various ways, the most simple one probaly being creating a separate association with a unique alias. For example in your ResourcesTable, create another association to Users with a different alias, say Users2, like:
$this->belongsToMany('Users2', [
'className' => 'Users'
]);
Then you can use that association in the second leftJoinWith(), and apply the conditions accordingly:
$this->Books
->find()
->leftJoinWith('Resources.Users')
->leftJoinWith('Cities.Resources.Users2')
->where(['Users.id' => 1])
->orWhere(['Users2.id' => 1])
->group('Books.id')
->all();
And don't forget to group your books to avoid duplicate results.
You could also create the joins manually using leftJoin() or join() instead, where you can define the aliases on your own (or don't use any at all) so that there are no conflicts, for more complex queries that can be a tedious task though.
You could also use your two separate queries as subqueries for conditions on Books, or even create a union query from them, which however might perform worse...
See also
Cookbook > Database Access & ORM > Query Builder > Adding Joins
CakePHP Issues > Improve association data fetching

Complex query join with doctrine

I'm building a query to show items with user and then show highest Bid on the item.
Example:
Xbox 360 by james. - the highest bid was $55.
art table by mario. - the highest bid was $25.
Query
SELECT i, u
FROM AppBundle:Item i
LEFT JOIN i.user u
I have another table bids (one to many relationship). I'm not sure how can I include single highest bid of the item in the same query with join.
I know I can just run another query after this query, with function (relationship), but I'm avoiding to do that for optimisation reasons.
Solution
SQL
https://stackoverflow.com/a/16538294/75799 - But how is this possible in doctrine DQL?
You can use IN with a sub query in such cases.
I am not sure if I understood your model correctly, but I attempted to make your query with a QueryBuilder and I am sure you will manage to make it work with this example:
$qb = $this->_em->createQueryBuilder();
$sub = $qb;
$sub->select('mbi') // max bid item
->where('i.id = mbi.id')
->leftJoin('mbi.bids', 'b'))
->andWhere($qb->expr()->max('b.value'))
->getQuery();
$qb = $qb->select('i', 'u')
->where($qb->expr()->in('i', $sub->getDQL()))
->leftJoin('i.user', 'u');
$query = $qb->getQuery();
return $query->getResult();
Your SQL query may look something like
select i,u
from i
inner join bids u on i.id = u.item_id
WHERE
i.value = (select max(value) from bids where item_id = i.id)
group by i
DQL, I don't think supports subqueries, so you could try using a Having clause or see if Doctrine\ORM\Query\Expr offers anything.
To solve this for my own case, I added a method to the origin entity (item) to find the max entity in a list of entities (bids), using Doctrine's Collections' Criteria I've written about it here.
Your Item entity would contain
public function getMaxBid()
{
$criteria = Criteria::create();
$criteria->orderBy(['bid.value' => Criteria::ASC]);
$criteria->setLimit(1);
return $this->bids->matching($criteria);
}
Unfortunately, there's no way that i know to find the maximum bid and the bidder with one grouping query, but there's several techniques to making the logic work with several queries. You could do a sub select and that might work fine depending on the size of the table. If you're planning on growing to the point where that's not going to work, you're probably already looking at sharing your relational databases, moving some data to a less transactional, higher performance db technology, or denormalizing, but if you want to keep this implemented in pure MySQL, you could use a procedure to express in multiple commands how to check for a bid and optionally add to the list, also updating the current high bidder in a denormalized high bids table. This keeps the complex logic of how to verify the bid in one, the most rigorously managed place - the database. Just make sure you use transactions properly to stop 2 bids from being recorded concurrently ( eg, SELECT FOR UPDATE).
I used to ask prospective programmers to write this query to see how experienced with MySQL they were, many thought just a max grouping was sufficient, and a few left the interview still convinced that it would work fine and i was wrong. So good question!

how to fetch specific and unique data from database different tables in php

i want to fetch data from database three tables all are linked together.
I'm facing problem, i have tried many methods to it like merge, unique and separate list.
following is the code.
<?php
$conn=new mysqli("localhost","root","","db");
$rows=$conn->query("select Account,Amount, event_name,event_description from donations
LEFT JOIN accounts
ON accounts.ID_Account=donations.ID_Account
LEFT JOIN events
ON events.event_id=donations.event_id ");
$rows1=$conn->query("SELECT Account from accounts");
while((list($Account, $Amount, $event_name,$event_description)=$rows->fetch_row()) and (list($Account)=$rows1->fetch_row()))
{
echo "<tr><td> $Account</td><td> $Amount </td><td>$event_name</td><td>$event_description</td></tr>";
}
?>
Now The problem is, it shows duplicate data from accounts and also I want it to be like if there is no donation $Amount for the account $Account it should not show donation in front of Account, but it is showing. is there any way i can make it working the way i want?
Many Thanks.
You are looking for some data hydration methods. Now you will get A * B * C = X rows with your resulting data because you are doing multiple joins and your resulting array from mysql will always have only one dimension and therefore a lot of duplicated data.
What you want is array (or object) with more than one dimension. I assume you want a resulting array with something like:
$result = array(
'account1' => array('event1', 'event2'),
'account1' => array('event3', 'event4', 'event5')
);
There are multiple solutions to this:
Write your own data hydration method to create a new array with values from your result
Do three single queries instead of one query with JOIN, then merge the results (can even be faster!)
Use doctrine (a php database abstraction layer) which can do exactly what you want without too much trouble.
If you start to code new app I would recommend 3.) to you:
http://doctrine-orm.readthedocs.org/en/latest/tutorials/getting-started.html

Multiple SELECT queries and looping through results in PHP and mySQL

I'm trying to learn best practices with multiple SQL queries in PHP and mySQL. Not sure if I should have two separate queries or if I should join (or UNION?) my queries into one.
Can I do the following or is there another way to accomplish the same thing?
It's important to note that my first query is pulling a list of questions and answers for a quiz. The second query is pulling a list of score profiles that I'll be using to assign to users based on their quiz score. The only relationship between these two tables is that both use the same foreign key linked to an ID in my "quiz" table. I wouldn't need to JOIN these tables side by side, but I think I would probably need to UNION them so my second query results appear after the first.
Now if I should union my queries into one, I have no idea how I would loop through the query results to create my array. When I loop through each result, I would need some conditional logic that does something with my first query results and something different with my second query results. How would I write those conditions?
Here is what I'm doing now that seems to work fine as two separate queries...
...query the database for the first time to get my quiz questions and answers:
$result_quiz = mysql_query("
SELECT quiz_question.question_text, quiz_question.id, quiz_answer.answer_text, quiz_answer.points
FROM quiz
JOIN quiz_question ON (quiz.id = quiz_question.quiz_id)
JOIN quiz_answer ON (quiz_question.id = quiz_answer.quiz_question_id)
WHERE quiz.id = $id;
");
...then based on the above query, build my array of questions and answers:
$quiz = array();
while ($row = mysql_fetch_assoc($result_quiz)) {
if (!isset($quiz['questions']['q'.$row['id'].''])) {
$quiz['questions']['q'.$row['id'].''] = array(
'question' => $row['question_text'],
'answers' => array()
);
}
$quiz['questions']['q'.$row['id'].'']['answers'][] = array('answer' => $row['answer_text'],'points' => $row['points']);
}
...then query the database for the second time to get a list of score profiles:
$result_profile = mysql_query("
SELECT quiz_profile.name, quiz_profile.description, quiz_profile.min_points, quiz_profile.max_points
FROM quiz
JOIN quiz_profile ON (quiz.id = quiz_profile.quiz_id)
WHERE quiz.id = $id;
");
...then loop through my second query so I can append the score profiles to my array:
while ($row = mysql_fetch_assoc($result_profile)) {
$quiz['profiles'][] = array('name' => $row['name'],'description' => $row['description'],'min_points' => $row['min_points'],'max_points' => $row['max_points']);
}
Is this the best approach?
Or should I combine my queries into one using UNION?
If so, how can I write my conditions to know what results I'm looping through?
Thanks SO much!
I suggest sticking with the current approach, unless there is a serious problem with performance - combining the two queries might improve total throughput (slightly), but would significantly increase the complexity of the code required to display it.
I recommend to take a step back at the design of your app, not the code itself: If you go down the UNION road, you have to sacrifice all isolation between your questions-code and your scoreprofiles-code.
I say, this is not a good thing: What if one day the score profiles are in some way recalculated or trabsformed between db and user? Is it intuitive, that for that you have to touch the function getting the questions from the db?
In PHP, there is a rule of thumb, which (as all rules of thumb) is not 100% perfect, but quite a start: It should be possible to have all code relating to one field of functionality be located in one file, the includes and requires mirroring the dependencies.

Categories