Laravel hasmanythrough or mysql groupby - php

I have three tables
category
id | category_name | created_at | updated_at
profile
id | category_id | profile_name | created_at | updated_at
photos
id | profile_id | photo_name | photo_path | photo_type | created_at | updated_at
Now i need to list photos by category and also only one photo from each profile by photo_type
I have tried using hasManyhrough but descending by created_at not working
public function categoryPhotos()
{
return $this->hasManyThrough(ProfilePhoto::class,Profile::class);
}
$response=Category::with(['categoryPhotos'=>function($query){
$query->where('profile_photos.photo_type',2);
$query->orderBy('profile_photos.created_at','ASC');
$query->groupBy('profile_photos.profile_id');
}])->whereHas('categoryPhotos')->get();
if its not possible via laravel relation then mysql query also fine for me.Thank you

Possible solution (if I understand the task correctly) may be
SELECT *
FROM category ca
JOIN profile pr ON ca.id = pr.category_id
JOIN photos ph ON pr.id = ph.profile_id
JOIN ( SELECT ph1.profile_id, MAX(ph1.id) id
FROM photos ph1
JOIN ( SELECT profile_id, MAX(created_at) created_at
FROM photos
WHERE phototype=2
GROUP BY profile_id ) sq1 ON ph1.profile_id = sq1.profile_id
AND ph1.created_at = sq1.created_at
WHERE ph1.phototype=2
GROUP BY ph1.profile_id ) sq2 ON ph.id = sq2.id
Explanations.
Subquery sq1 selects last created_at for each profile for photos with phototype=2.
Subquery sq2 takes this result and gets maximal id for photos which's profile and datetime matched those obtained in sq1 (if more than 1 photo matches the conditions, this subquery selects one of them).
FROM category ca
JOIN profile pr ON ca.id = pr.category_id
JOIN photos ph ON pr.id = ph.profile_id
collects all possible combined rows - for each category it gets all related profiles with all related photos. Then we join our subquery, which filters this total list and gets only one row for each (category, profile) pair with the photo matched our conditions (or, if none such photo, none pair is selected).
Test it in this pure SQL form (does it gives correct result? start from separate sq1 execution, check, if correct then execute sq2 and check again, and finally check the whole query). If it is correct then try to convert it to Laravel form.

Related

Create JOIN for a many-to-many relationship

I can't get my head around JOIN and many-to-many database relationships.
I have 3 tables (structure has been simplified)
USERS:
id (primary) | userEmail | userName | userAddress | userCreated
TEAMS:
id (primary) | teamName | teamLogo | teamCreated
TEAMS-USERS
id (primary) | userId | teamId | userLevel
Teams can have many Users. And Users can be in many Teams. The UsersTeams table tracks which users are in which teams (note: I'm not using foreign keys or anything).
When a user logs in, I want to get a list of all the teams they are a member of and the corresponding data i.e.
get the data from USERS (where id = $id)
look it up in TEAMS-USERS
get the team information from TEAMS via the teamID in TEAMS-USERS
store it all in one array
I have this code so far but to be honest I'm just trying lots of variations:
$model->select('users.*, teams.*, teams.id as teamId, teams-users.id as teamsUsersId, teams-users.userId as teamsUsersUserId, teams-users.teamId as teamsUsersTeamId');
$model->where("users.id", $id);
$model->join('teams-users','teams.id = teams-users.teamId','inner');
$data = $model->get()->getRowArray();
Running the query above gives me the error: Unknown table 'myproject.teams' - even though the TEAMS table 100% exists and I use it throughout my project.
Where am I going wrong?
You get this error because in your query you are not including a from clause and you are missing a join clause. With codeigniter you can do that like:
$model->select('users.*, teams.*, teams.id as teamId, teams-users.id as teamsUsersId, teams-users.userId as teamsUsersUserId, teams-users.teamId as teamsUsersTeamId');
//$model->where("users.id", $id);
$model->join('teams-users','teams.id = teams-users.teamId','left');
$model->join('users','user.id = teams-users.userId and users.id='.$id,'left');
$data = $model->get('teams')->getRowArray();
explanation:
there was a missing join of table users
you want to use left joins
place the where clause inside the corresponding join (it becomes and)
you missed the from part, which can be done placing the table name in
the get() function or by $model->from('teams');
see also this nice explanation of joins

GROUP_CONCAT with ordering and missing fields

I have a series of tables that I want to get rows returned from in the following format:
Student ID | Last Name | First Name | Quiz Scores
-------------------------------------------------
xxxxxxx | Snow | Jon | 0,0,0,0,0,0,0,0
There's 3 relevant tables (changing any existing DB structure is not an option):
person - table of all people in the organization
enrollment - table of student and faculty enrollment data
tilt.quiz - table of quiz scores, with each row storing an individual score
The tricky part of this is the Quiz Scores. A row for the quiz score only exists if the student has taken a the quiz. Each quiz row has a module, 1 - 8. So possible quiz data for a student could be (each of these being a separate row):
person_id | module | score
---------------------------
223355 | 1 | 100
223355 | 2 | 95
223355 | 4 | 80
223355 | 7 | 100
I need the quiz scores returned in proper order with 8 comma separated values, regardless if any or all of the quizzes are missing.
I currently have the following query:
SELECT
person.id,
first_name,
last_name,
GROUP_CONCAT(tilt.quiz.score) AS scores
FROM person
LEFT JOIN enrollment ON person.id = enrollment.person_id
LEFT JOIN tilt.quiz ON person.id = tilt.quiz.person_id
WHERE
enrollment.course_id = '$num' AND enrollment_status_id = 1
GROUP BY person.id
ORDER BY last_name
The problems with this are:
It does not order the quizzes by module
If any of the quizzes are missing it simply returns fewer values
So I need the GROUP_CONCAT scores to at least include commas for missing quiz values, and have them ordered correctly.
The one solution I considered was creating a temporary table of the quiz scores, but I'm not sure this is the most efficient method or exactly how to go about it.
EDIT: Another solution would be to execute a query to check for the existence of each quiz individually but this seems clunky (a total of 9 queries instead of 1); I was hoping there was a more elegant way.
How would this be accomplished?
There are some assumptions here about your data structure, but this should be pretty close to what you're after. Take a look at the documentation for GROUP_CONCAT and COALESCE.
SELECT `person`.`id`, `person`.`first_name`, `person`.`last_name`,
GROUP_CONCAT(
COALESCE(`tilt`.`quiz`.`score`, 'N/A')
ORDER BY `tilt`.`quiz`.`module_id`
) AS `scores`
FROM `person`
CROSS JOIN `modules`
LEFT JOIN `enrollment` USING (`person_id`)
LEFT JOIN `tilt`.`quiz` USING (`person_id`, `module_id`)
WHERE (`enrollment`.`course_id` = '$num')
AND (`enrollment`.`enrollment_status_id` = 1)
GROUP BY `person`.`id`
ORDER BY `person`.`last_name`
First thing to do is use the IFNULL() function on the score
Then, use ORDER BY inside the GROUP_CONCAT
Here is my proposed query
SELECT
person.id,
first_name,
last_name,
GROUP_CONCAT(IFNULL(tilt.quiz.score,0) ORDER BY tilt.quiz.module) AS scores
FROM person
LEFT JOIN enrollment ON person.id = enrollment.person_id
LEFT JOIN tilt.quiz ON person.id = tilt.quiz.person_id
WHERE
enrollment.course_id = '$num' AND enrollment_status_id = 1
GROUP BY person.id
ORDER BY last_name

MYSQL query to exclude records in specific condition

Hey I have the following MYSQL DB structure for 3 tables with many to many relation. Many users can have many cars and cars can be for many users as showing below:
Users
ID | Name
---------
100|John
101|Smith
Cars
ID | Name
---------
50|BMW
60|Audi
Users_cars
ID | UID | CID
---------
1| 100 |50
2| 100 |60
3| 101 |60
I have a page users_cars.php this page have two drop down lists
list of all users
list of all cars
In this page you can select a user from user's list and select a car from car's list then click add to insert into users_cars table.
What am trying to do is to exclude from user's drop down list all the users that have been linked with all the available cars from cars table.
In the example above user's drop down list will just have "Smith" because "John" linked with all cars available (BMW,AUDI), if "Smith" also has the BMW he will be excluded from the list. I need a select query for this condition and i don't want to use any nest select query to count user records inside users_cars table
If I understand what you are after you need to use GROUP BY in your query. So to select all users:
SELECT ID, UID FROM Users_cars GROUP BY UID
and for all cars:
SELECT ID, CID FROM Users_cars GROUP BY CID
That will group results that are the same, so you only get one instance of each user, or one instance of each car.
I hope I understood your question right.
I think you can so this using some programming -
With PHP/mysql -
Get count of all distinct car ID's
Get count of cars for each user. (making sure this lists only unique car ID's)
Loop through all users and in each loop compare the above two and exclude the user where this condition matches.
SELECT *
FROM users
WEHRE id NOT IN (SELECT uid
FROM (SELECT uid, COUNT(cid), COUNT(*)
FORM cars
LEFT OUTER JOIN users_cars ON cars.id = users_cars.cid
GROUP BY uid
HAVING COUNT(cid) = COUNT(*)
Basically, what you want to do is that (if I understood your problem) :
SELECT UID FROM Users_cars WHERE CID NOT IN (SELECT ID FROM Cars);
But carefull, this is a greedy request (depends on the size of the tables of course) and you should better put a flag on the user table and update then when your user uses the last available car (or with a batch) so you don't run the request too often !

Joining 2 Tables and show the same column repeatedly based on its associative ID

Hi i am not sure how to put this in a brief sentences, but i have DB table like the following
User Table
user_id
username
and so on...
Item
item_id
item_name
Item_Equipped
equipped_id head (FK to item_id)
hand (FK to item_id)
user_id (FK to user_id IN User Table)
I would like to generate a query that will display like the following format
user_id | head | head_item_name | hand | hand_item_name | ...
So far i only able to do this:
SELECT user.user_id, user.username,
equipments.head, equipments.head_acc,
equipments.hand,
equipments.acc, equipments.body
FROM gw_member_equipped AS equipments
LEFT JOIN gw_member AS user ON user.memberid = equipments.member_id
Which (i have to be brutally honest) doesn't do anything much.
I tried to perform INNER JOIN between item and item_equipped however i am unable to get individual name for each item (based on its item ID)
you need to join ITEM table two times with ITEM_EQUIPPED table.
you can use below query for your desired output column shown in question..
SELECT USER.User_Id,
Item_Equipped.Head,
Item_Heads.Item_Id Head_Item_Id, -- if you want you can remove this column
Item_Heads.Item_Name Head_Item_Name,
Item_Equipped.Hand,
Item_Hands.Item_Id Hand_Item_Id, -- and this column also as these columns are same as their previous select columns
Item_Hands.Item_Name Hand_Item_Name
FROM USER, Item Item_Heads, Item Item_Hands, Item_Equipped
WHERE USER.User_Id = Item_Equipped.User_Id
AND Item_Heads.Item_Id = Item_Equipped.Head
AND Item_Hands.Item_Id = Item_Equipped.Hand

sorting by value in another table php mysql

I have two tables, one called episodes, and one called score. The episode table has the following columns:
id | number | title | description | type
The score table has the following columns:
id | userId | showId | score
The idea is that users will rate a show. Each time a user rates a show, a new row is created in the score table (or updated if it exists already). When I list the shows, I average all the scores for that show ID and display it next to the show name.
What I need to be able to do is sort the shows based on their average rating. I've looked at joining the tables, but haven't really figured it out.
Thanks
To order the results, use and ORDER BY clause. You can order by generated columns, such as the result of an aggregate function like AVG.
SELECT e.title, AVG(s.score) AS avg_score
FROM episodes AS e
LEFT JOIN scores AS s ON e.id=s.showId
GROUP BY e.id
ORDER BY avg_score DESC;
You're right. You have to JOIN these tables, then use GROUP BY on the 'episodes' table's 'id' column. Then you'll be able to use AVG() function on 'the scores' tables's 'score' column.
SELECT AVG(scores.score) FROM episodes LEFT JOIN scores ON scores.showId = episodes.id GROUP BY episodes.id
SELECT episodes.*, AVG(score.score) as AverageRating FROM episodes
INNER JOIN score ON (episodes.id = score.showId)
GROUP BY episodes.id
ORDER BY AVG(score.score) DESC

Categories