Doctrine / DQL get the entities having the exactly the same related entities - php

Imagine i have a entity Person which has a many-to-many relation with Vehicle
How can i find another Person exactly having the same Vehicles?
Example:
Person 1 (ID=1) has vehicle with id 5,6 and 7
Person 2 (ID=2) has vehicles with id 5,6,8 -> should not match with person 1!
Person 3 (ID=3) has vehicle with id 5 ->should not match with person 1!
Person 4 (ID=4) has vehicles with id 5,6 and 7 -> should match with person 1
Person 5 (ID=5) has vehicles with id 5,6 and 7 -> should match with person 1 and person 4
So what is the DQL expression for that?
I tried already with "IN" but this will also match even if just one of my id's match.
Thanks

Clean DQL:
$query = $entityManager->createQuery("
SELECT p, pv
FROM Person p
INNER JOIN p.vehicles pv
WHERE pv.id in (
SELECT v.id
FROM Vehicle v
INNER JOIN v.persons vp
WHERE p.id <> vp.id
) GROUP BY p.id
HAVING count(pv.id) > 1");
$persons = $query->getResult();
Explanation:
To get persons with same vehicles, you need to get vehicles, that equal to person's, but attached to other. HAVING regulates count of minimal same vehicles, by which we group persons in collection.
You can use more flexible approach with PHP, that get only persons with full identical vehicles. Steps:
get all persons with vehicles
compare vehicles Ids
add to result only persons IDs with the same vehicles
get collection of persons with same vehicles
Code:
<?php
$query = $entityManager->createQuery("select p,v from Person p JOIN p.vehicles v");
$persons = $query->getArrayResult();
$personsIdsWithSameVehicles = [];
foreach ($persons as $person) {
$vehiclesIds = array_column($person['vehicles'], 'id');
$subPersons = array_filter($persons, function($filterPerson) use ($person) {
if($filterPerson['id'] != $person['id']) return $filterPerson;
});
foreach ($subPersons as $subPerson) {
$subVehiclesIds = array_column($subPerson['vehicles'], 'id');
if(count($vehiclesIds) == count($subVehiclesIds) and
empty(array_diff($vehiclesIds, $subVehiclesIds))) {
$personsIdsWithSameVehicles[] = $person['id'];
break;
}
}
}
$personsQuery = $entityManager->createQuery("select p from Person p where p.id IN(:persons)");
$personsQuery->setParameter('persons', $personsIdsWithSameVehicles);
$personsWithSameVehicles = $personsQuery->getResult();

Related

Mysql join three table/ Laravel query

I have three tables Beneficiary, Questions, Responses..Beneficiary
Benficiary
id name
1 user1
2 user2
3 user3
Questions
id question type
1 what is your hobby hobby
2 address contact
3 phone contact
..questions upto 500 and i need to select according to its type
Response
id beneficiary_id question_id response
1 1 1 football
2 1 3 5354353
3 2 1 basketball
4 3 2 cityname
5 3 3 432423
Beneficiary table and Question table are static but response table joins both of them. I tried to do this from laravel query but it gives me only responses
sql query
select response,questions.question, beneficiarys.name
from responses RIGHT join (questions,beneficiarys)
on beneficiarys.id = responses.beneficiary_id && questions.id = responses.question_id
where questions.sheet_type = 'SCDS' OR questions.sheet_type = 'SMM'
laravel query
$beneficiaries = Beneficiary::with('quests','response')->whereHas('response', function ($query)use($sheet_type) {
$query->where('beneficiary_id', '>', 0);
})
->whereHas('quests',function($query)use($sheet_type){
$query->where('questions.sheet_type','=',$sheet_type);
$query->orWhere('questions.sheet_type','=','SCDS');
})
->where('updated_at','>=',!empty($this->request->from_date)?$this->request->from_date:$from)->where('updated_at','<=',!empty($this->request->to_date)?$this->request->to_date:$date)->get();
Beneficiary Model
public function response()
{
return $this->hasMany('App\Model\Response');
}
public function quests(){
return $this->belongsToMany('App\Model\Question', 'responses','beneficiary_id','question_id');
}
Question Model
public function response(){
return $this->hasMany('App\Model\Response');
}
public function bens(){
return $this->belongsToMany('App\Model\Bebeficiary', 'responses','question_id','beneficiary_id');
}
Response Model
public function beneficiary(){
return $this->belongsTo('App\Model\Beneficiary', 'beneficiary_id');
}
public function question(){
return $this->belongsTo('App\Model\Question', 'question_id');
}
I need all the question according to its type(type is selected by user from view) and i need output like this
beneficary hobby address phono ....
user1 football null 4244 ....
user2 basketball null null ....
is there any way to achieve it with sql or laravel query*
this is the mysql select, maybe is a better way to it but this work
SELECT b.name,hobby.response as hobby,address.response as addres,phone.response as
phone FROM beneficiary as b
left join (select * from response where question_id=1 ) as hobby
on hobby.beneficiary_id = b.id
left join (select * from response where question_id=2 ) as address
on address.beneficiary_id = b.id
left join (select * from response where question_id=3 ) as phone
on phone.beneficiary_id = b.id
You Will get result from this query. Laravel Support customization as well.
$sSQL = "SELECT * FROM RESPONSE
INNER JOIN QUESTION ON QUESTION.id=RESPONSE.QUESTION_id
INNER JOIN BENEFICIARY ON BENEFICIARY.id=RESPONSE.BENEFICIARY_id";
DB::query($sSQL);
Try
$res = DB::table('responses')
->join('questions','responses.question_id','=','questions.id')
->join('beneficiarys','responses.beneficiary_id','=','beneficiarys.id')
->select('responses.*','questions.question','beneficiarys.name')
->where('questions.sheet_type', '=','SCDS')
->orWhere('questions.sheet_type','=','SMM')
->get();
Use the result as
foreach($res as $res)
{
echo $res->name;
}
If you want to do it Eloquent way you could try using whereHas on your nested relationships. Try something like :
Beneficiary::with('response.question')->whereHas('response.question')
For more references try this link Eloquent with nested relationships

How do I combine results from more than one table?

I have to tables: cities and clients
cities: cities_id, cities_name
New York
Amsterdam
Paris
Milan
etc.
Clients:
columns with personal informaition but the following two columns are importen: plac_of_residence_id and place_of_birth_id. These two columns contain the different value from cities. For example:
client_id: 1
client_name: John
client_sex: m
plac_of_residence_id: 3 (cities_id)
place_of_birth_id: 1 (cities_id)
How do I get two different values from table cities?
My sql statement is as follows:
SELECT * FROM clients C LEFT JOIN cities C ON C.plac_of_residence_id = C.cities_id AND place_of_birth_id = C.cities_id WHERE C.client_id = $client_id";
I get same citiesname for plac_of_residence_id as for place_of_birth_id instead of two different cities
How can i get as output: Paris and New York?
You should join cities two times
"SELECT C.*, a.*, b.* FROM clients C
LEFT JOIN cities a ON C.plac_of_residence_id = a.cities_id
LEFT JOIN cities b on c.place_of_birth_id = b.cities_id
WHERE C.client_id = $client_id";
Instead of using an AND in your statement you have to use an OR:
SELECT * FROM clients C LEFT JOIN cities C ON C.plac_of_residence_id = C.cities_id OR place_of_birth_id = C.cities_id WHERE C.client_id = $client_id";
This is because you want to have both: city of birth and the current residence. With your example above:
For John the join requirement (your "ON" part) should evaluate to true iff current row in cities table is no. 3 OR no. 1 because John is in a relationship to both entries.

mysql join 3 tables related in pairs

► Context : I work in a museum (for real), people come everyday, they buy tickets for themselves (humans) and sometimes they also buy tickets for drinks and foods (objects). There are events, tickets have the same names per event but the prices are different.
► The problem : I have to create a report with 2 results : total sales (visitors + food + drinks) and how many people came (visitors only) for a specific event. Next is an image of the 3 tables in the database, how they relate and some sample data :
Table TICKETS relates to SALES_MAIN through EVENT_ID column.
Table SALES_MAIN relates to SALES_DETAIL through ID→MAIN_ID columns.
Table SALES_DETAIL have a column TICKET_NAME but it's not unique in table TICKETS.
► The question : How to get both results, total sales and human count, for event 555 in one "select" ? I tried next 2 "select" but when I combine them with another INNER JOIN I get cartesian results :
Get detail sales for event 555 :
SELECT sales_detail.* FROM sales_main
INNER JOIN sales_detail ON sales_detail.main_id = sales_main.id
WHERE sales_main.event_id = '555'
Get tickets for event 555 :
SELECT * FROM tickets WHERE tickets.event_id = '555'
Use:
SELECT
SUM(CASE WHEN sd.ticket_name IN ('adult', 'child') THEN sd.quantity
ELSE 0 END) AS total_visitors,
SUM(sd.quantity * t.price) AS total_sales
FROM sales_main sm
JOIN sales_detail sd
ON sd.main_id = sm.id
JOIN ticket t
ON t.event_id = sm.event_id
AND t.ticket_name = sd.ticket_name
WHERE sm.event_id = '555';
Conditional aggregation could also be based on type:
SUM(CASE WHEN t.ticket_type ='human' THEN sd.quantity ELSE 0 END)

MySQL query to select specific resources

Scenario:
The school where I work at has already a system that handles resource reservation (racks with tv's, projectors, etc), however I was asked to make a quick-fix for today since the system has a problem, there's no way to make the resource only appear for a specific segment:
1-6th grade is a segment,
7-11th grade is a segment.
The system is made out of PHP and MySQL.
Problem:
Without creating a new table, I have to find a way to show a specific resource only to 1-3rd grade and another one only to 4-6th grade.
Here are the tables:
I added into the grade table a new column called UNIQUERESOURCESwhere I put a value of 1 to sections between first and third grade and a value of 2 to sections between fourth and sixth grade.
How can I show to grades from 1-3 grade ONLY those specific resources and from 4-6 grade ONLY the other specific resources?
Here is the current query:
SELECT DISTINCT r.RESOURCEID, r.RESOURCENAME, rl.RESOURCELOCATIONNAME FROM resource r
inner join resource_location rl
on r.RESOURCELOCATIONID = rl.RESOURCELOCATIONID
inner join grade g
on g.RESOURCELOCATIONID = r.RESOURCELOCATIONID
inner join users_intermediate ui
on g.GRADEID = ui.GRADEID
WHERE ui.USERID = '%s'
How can I fix this query to include the resources as follows:
Include RESOURCELOCATIONID = 5 where UNIQUERESOURCES = 1 if the user is in between GRADEID 1-12
Include RESOURCELOCATIONID = 6 where UNIQUERESOURCES = 2 if the user is in between GRADEID 13-24.
This is the part I can't figure out. Some help would be really appreciated!
EDIT:
Here are some pictures of the resources
Resources:
Grades:
Users Intermediate (emails are censored in this pic):
As you can see, what I'm trying to accomplish is also include the resources that have resourcelocationid = 5 or resourcelocationid = 6 if the user teaches in grades that go from 1-3 or 4-6
Have you tryed this way:
SELECT RESOURCEID, RESOURCENAME, RESOURCEDESCRIPTION,
T3.RESOURCELOCATIONNAME, T2.RESOURCESTATUSNAME,
T2.RESOURCESTATUSDESCRIPTION, T2.RESOURCESTATUSID
FROM resource T1
INNER JOIN resource_status T2 USING(RESOURCESTATUSID)
INNER JOIN resource_location T3 USING(RESOURCELOCATIONID)
INNER JOIN grade USING(RESOURCELOCATIONID)
WHERE T1.RESOURCELOCATIONID IN (
SELECT DISTINCT RESOURCELOCATIONID FROM grade G
INNER JOIN users_intermediate UI
ON G.GRADEID = UI.GRADEID
WHERE USERID = "%s"
)
AND (
T1.RESOURCESTATUSID = 1
OR (
T1.RESOURCELOCATIONID = 5
AND (
UNIQUERESOURCES = 1 AND GRADEID >= 1 AND GRADEID <= 12
)
)
OR (
T1.RESOURCELOCATIONID = 6
AND (
UNIQUERESOURCES = 2 AND GRADEID >= 13 AND GRADEID <= 24
)
)
)
Not seeing sample data and confirming true context of your data and expected output, I would like to mentally describe via transitive association.
User "A" is associated with class/grade 1,2 and 3.
Looking into the Grade table, you find User "A" has grade information pointing to Locations "LH", "LR" and "LX" (arbitrary as to not confuse a location with a grade of 1-3 OR 4-6 (or other as later needed)).
Since we know the qualified "Locations", User "A" has access to ANYTHING found within the "LH", "LR" and "LX" locations.
Now, because the ResourceLocationID is in both the Resource_Location and the Resource table, the Grade table can actually join to each directly as the resource location table is nothing more than a lookup to get a descriptive name of where it is. The multiple things IN the room are the resources.
So, the only thing my inner prequery "QualGroups" does, is get a list of the UniqueResources groups (either 1 or 2) a user has access to. The next level is to get all DISTINCT locations under that 1 or 2 status. Because IF a user is access for group 1 at location "LH" AND class group 2 at location "LH", you do not want duplicated resources for location "LH". I am getting the location name while I am at it. (result alias QualLocations)
Now, I can get the resources for those distinct qualified locations
SELECT
QualLocations.ResourceLocationID,
QualLocations.ResourceLocationName,
R.ResourceID,
R.ResourceName,
R.ResourceDescription,
R.ResourceStatusID
from
( SELECT distinct
RL.ResourceLocationID,
RL.ResourceLocationName
from
grade G2
JOIN ( select distinct
G.UniqueResources
from
users_intermediate UI
JOIN grade G
ON UI.GradeID = G.GradeID
where
UI.UserID = "%s" ) QualGroups
ON G2.UniqueResources = QualGroups.UniqueResources
JOIN Resource_Location RL
ON G2.ResourceLocationID = RL.ResourceLocationID )
as QualLocations
JOIN Resource R
ON QualLocations.ResourceLocationID = R.ResourceLocationID
Now, if you ONLY CARE about a very specific classification group, UPDATE the inner-most query joining the "Grade G" table to include the unique resource you are looking for... in this case either 1 or 2
JOIN grade G
ON UI.GradeID = G.GradeID
AND G.UniqueResource = 1 (or 2 if you only wanted 4th-6th grade)
Hope this helps you on your way.
Aside from posted question above, if you change your "UniqueResource" to have a numeric value of the specific GRADE it is vs the verbiage description of the grade name, the AND clause above could be simplified to something like
AND G.UniqueResource IN ( 1, 2, 3 ) // for only grades 1-3
AND G.UniqueResource BETWEEN 1 and 6 // ex: for grades 1-6
AND G.UniqueResource > 6 // for any grade above 6th grade

select all users where count() equals a specific value

$sql = "SELECT * FROM cars WHERE COUNT(brand)='4'";
$results = mysql_query($sql);
$rows = mysql_fetch_array($results);
print_r($rows);
the table cars has these columns:
id brand price
0 bmw
1 corvette
2 mercedes
3 bmw
4 bmw
5 toyota
6 bmw
7 honda
8 lotus
this is what I am trying to do, return from the table 'cars' every brand that has 4 different cars. In this example bmw has 4
4 different cars is 4 different rows with the same brand name.
so I am trying to echo the name of the brand where the total number of inventory is 4.
I hope I made sense, any help would be appreciated.
EDIT: I tried
SELECT * FROM cars LEFT JOIN users ON cars.user_id=users.user_id HAVING count(user_id) = 4 this is not working any ideas?
SELECT brand FROM cars GROUP BY brand HAVING COUNT(brand) = 4
For you edit:
SELECT t1.brand, t2.email
FROM cars t1
LEFT JOIN users t2 ON t1.user_id = t2.user_id
GROUP BY t1.brand HAVING COUNT(t1.brand) = 4
I think in the query like you have you need to use having
SELECT * FROM cars HAVING COUNT(brand)=4;
and group by depending on what you grouping
You want perform an aggregate function (COUNT) on multiple rows at once. Pretty much anytime you want to perform multiple counts, you should think GROUP BY.
$sql = "SELECT brand FROM cars GROUP BY brand HAVING COUNT(*) >= 4";
If you do SELECT * in this query, you'll get 1 random row for each brand (probably the one with the lower id).
The HAVING clause will act as a WHERE on each of the groups.

Categories