PHP Propel ORM MySQL - Left Join on Many to Many - php

I have two tables one called meeting and one called attendance, attendance is a many to many relational database in the following format:
Attendance:
user_id | meeting_id | invited
--------+------------+--------
1 | 5 | 1
2 | 5 | 0
3 | 4 | 0
3 | 5 | 1
3 | 6 | 0
Meetings are in the following format:
Meetings:
meeting_id | meeting_name | owner_id
-----------+--------------+----------
3 | Awesome | 2
4 | Boring | 2
5 | Cool | 5
9 | Sexy | 3
There can only be one meeting row per meeting, but unlimited attendance rows per meeting (limited to for every possible user for every meeting).
How in SQL and/or Propel do I create something that would list all meetings where the (provided) user_id is either the owner_id in meetings OR were the user_id and invited in the attendance database.
I am looking for a result (based on the above data) when searching for userid 3 of:
Result for userid3:
meeting_id | meeting_name | owner_id
-----------+--------------+----------
5 | Cool | 5 - Because userid 3 is attending meeting 5
9 | Sexy | 3 - Because userid 3 owns meeting 9
I currently have the following which doesn't work really, and produces multiple rows per meeting (because the meeting exists more than once in the attendance DB).
$criteria->addJoin(MeetingMeetingsPeer::ID, MeetingAttendancePeer::MEETING_ID, Criteria::LEFT_JOIN);
$criterion = $criteria->getNewCriterion(MeetingMeetingsPeer::OWNER_ID, Meeting::getUserId());
$criterion->addOr($criteria->getNewCriterion(MeetingAttendancePeer::USER_ID, Meeting::getUserId()));
$criteria->add($criterion);
return $criteria;
Which is something like the below in SQL:
SELECT meeting_meetings.ID, meeting_meetings.OWNER_ID, meeting_meetings.GROUP_ID, meeting_meetings.NAME, meeting_meetings.COMPLETED, meeting_meetings.LOCATION, meeting_meetings.START, meeting_meetings.LENGTH, meeting_meetings.CREATED_AT, meeting_meetings.UPDATED_AT FROM `meeting_meetings` LEFT JOIN meeting_attendance ON (meeting_meetings.ID=meeting_attendance.MEETING_ID) WHERE (meeting_meetings.OWNER_ID=1 OR meeting_attendance.USER_ID=1)
Thanks for your time,

This should get you all meetings that are owned by user_id 1, as well as all meetings that are attended by user_id 1.
SELECT meeting_meetings.ID, meeting_meetings.OWNER_ID, meeting_meetings.GROUP_ID, meeting_meetings.NAME,
meeting_meetings.COMPLETED, meeting_meetings.LOCATION, meeting_meetings.START, meeting_meetings.LENGTH,
meeting_meetings.CREATED_AT, meeting_meetings.UPDATED_AT
FROM `meeting_meetings`
WHERE `meeting_meetings`.`owner_id` = 1
UNION DISTINCT
SELECT meeting_meetings.ID, meeting_meetings.OWNER_ID, meeting_meetings.GROUP_ID, meeting_meetings.NAME,
meeting_meetings.COMPLETED, meeting_meetings.LOCATION, meeting_meetings.START, meeting_meetings.LENGTH,
meeting_meetings.CREATED_AT, meeting_meetings.UPDATED_AT
FROM `meeting_meetings`
JOIN `meeting_attendance` ON `meeting_meetings`.`meeting_id` = `meeting_attendance`.`meeting_id` AND `meeting_attendance`.`invited`
WHERE `meeting_attendance`.`user_id` = 1
It's a bit clunky. Personally, I would consider adding an owner flag to the meetings_attendance table.

Related

laravel recursion display referred users by level/depth

So I'm working with affiliate and doing fine with registration and saving who referred to a user,
now I'm struggling in showing those users with referrals by Level.
Level is not save in the database, I'm thinking of it as incrementing in the logic area?
users table structure
id | name | sponsor_id |
1 | John | NULL |
2 | Jane | 1 |
3 | Jess | 1 |
4 | Joe | 2 |
so the output I want should be like this:
I am John, level 1 are who's sponsor_id is mine, and level 2 are who's sponsor_id is id's of whom I invited.
ID | Name | Level |
2 | Jane | 1 |
3 | Jess | 1 |
4 | Joe | 2 |
where Jane & Jess's sponsor_id is mine, and Joe's sponsor_id is Jess
User has sponsor
public function sponsor()
{
return $this->belongsTo('App\User', 'sponsor_id');
}
User has referrals
public function referrals()
{
return $this->hasMany('App\User', 'sponsor_id');
}
If MySQL 8 (very advised with hierarchical data like yours), you can do this with a recursive CTE :
WITH RECURSIVE CTE_users_levels(id, name, level) AS
(
SELECT id, name, 0 as level
FROM users
WHERE sponsor_id IS NULL
UNION ALL
SELECT u.id, u.name, cte.level+1
FROM CTE_users_levels cte
JOIN users u ON cte.id=u.sponsor_id
)
-- To output all users' levels:
SELECT * FROM CTE_users_levels ORDER BY level;
Now you have virtual table containing the level column for all your users and you can query it
| id | name | level |
| --- | ---- | ----- |
| 1 | John | 0 |
| 2 | Jane | 1 |
| 3 | Jess | 1 |
| 4 | Joe | 2 |
DBFiddle
GOing further...
Make a view out of your CTE
CREATE VIEW VIEW_User_Levels AS
(
WITH RECURSIVE CTE_users_levels(id, name, level) AS
(
SELECT id, name, 0 as level
FROM users
WHERE sponsor_id IS NULL
UNION ALL
SELECT u.id, u.name, cte.level+1
FROM CTE_users_levels cte
JOIN users u ON cte.id=u.sponsor_id
)
SELECT * FROM CTE_users_levels ORDER BY level
)
Now it's easy to get all your users levels (no need to have the CTE statement) :
SELECT * FROM VIEW_User_Levels
Then, if you have a lot of users, it's a bit ovekilling to recompute the whole tree all the time (which the VIEW does)
Then you can fix the level of your users in a new column.
ALTER TABLE users ADD level INT;
And populate that column for all users with the help of your view:
UPDATE users u, VIEW_User_Levels v
SET u.level=v.level
WHERE u.id=v.id;
Which gives
SELECT * FROM users
id name sponsor_id level
1 John null 0
2 Jane 1 1
3 Jess 1 1
4 Joe 2 2
DBFiddle
If you have a lot of users, and your aim is to query the level column a lot, INDEX IT.
Note that you can also update the level value of only one user (for instance if its sponsor_id changes, or if the user is new)
UPDATE users u, VIEW_User_Levels v
SET u.level=v.level
WHERE u.id=v.id AND u.name='Jane';

how to select from two table in sql

i am creating a voting application and i want to select from the database tables and display the result .1 want to get the candidate name form the first table which i call candidate.sql and then get the amout of votes from the second table called voter.
this is the candidate.sql table
id | candidate
1 |**mark**
2 |**david**
3 |**jeff**
voter.sql
voter_id | forr |user |candidate_id
1 |**mark** |tobe | 1
2 |**david** |sandra| 2
3 |**jeff** |john | 3
4 |**jeff** |steve | 3
5 |**david** |linda | 2
6 |**mark** |ken | 1
7 |**mark** |jacob | 1
My question is how do i join it so it can display like a list. e.g
mark 3
david 2
jeff 2
or is there a better way.
SELECT forr,count(*) FROM voter
GROUP BY forr
ORDER BY 2 DESC
SELECT c.candidate, count(*)
FROM candidate c
JOIN voter v ON v.forr = c.candidate
GROUP BY c.candidate
ORDER BY count(*) DESC, c.candidate ASC

MySQL - Grouping multiple rows where criteria is the same

I have a table of movie ratings that contains millions of rows containing userid's, movieid's and ratings.
| userId | movieId | rating |
------------------------------
| 1 | 213 | 5 |
| 1 | 245 | 4 |
| 2 | 213 | 4 |
| 2 | 245 | 4 |
| 3 | 657 | 5 |
| 3 | 245 | 5 |
I'm trying to figure out a way of grouping together userId's that contain matching sets of movieId's. Ideally I want the query to only find matches if they have at least 5 movieId's in common and if the rating is above 4, but I've simplified it for this example.
In the instance above, userId 1 and 2 would be the only users that match as they both contain the same movieIds. I need a statement that would essentially replicate this. Thanks in advance for any help.
You can perform a self-join on matching movies, filter out records with uninteresting ratings, group by user-pairs and then filter the resulting groups for only those that have at least the requisite number of matching records:
SELECT a.userId, b.userId
FROM myTable a JOIN myTable b USING (movieId)
WHERE a.userId < b.userId
AND a.rating > 4
AND b.rating > 4
GROUP BY a.userId, b.userId
HAVING COUNT(*) >= 5
select movieId, rating
from tablename
group by movieId
having count(userId) > 1 and rating > 4;
this gives me movieId 245 and rating 5, which should be correct according to your provided example data, have more than 1 userId and a rating greater than 4.

Counting in a complex query

I will explain my problem with an example:
Tables:
place - comment - activities_comment - activities
User can comment a place and select what kind of activities are being able to do in that place.
Activities are for example: run, swim, walk and climb.
So some users would vote more than 1 activity, so i can't put it right in the same table COMMENT, i need to create a pivote table ACTIVITIES_COMMENT, the problem goes here, i was able to show all the comments with their activities selected in each place..
But now i want to count the number of activities and order them by the most selected activity to the less selected by the users in the comments for each place.
How can I do that??
The best thing i can do is:
$place = Place::with('city','comments')
->where('id',$placeId)->first();
$place->activities = Activity::join('activities_comment', 'activity.id', '=', 'activities_comment.activity_id')
->join('comment', 'comment.id', '=', 'activities_comment.comment_id')
->select('comment.id','activities_comment.activity_id',
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 1) as run'),
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 2) as swim'),
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 3) as walk'),
DB::raw('(SELECT count(activities_comment.activity_id) FROM activities_comment WHERE activities_comment.activity_id = 4) as climb'),
)
->where('comment.place_id',$place->id)
->get();
The problem is that this query counts the most selected activities but in ALL places, i want to count only in each place.
EDIT:
Example rows:
place table:
id | name
----------
1 | Alaska
2 | Peru
3 | Argentina
comment table:
id | user_id | place_id | text
------------------------------------
1 | 1 | 1 | some text
2 | 3 | 1 | some text
3 | 2 | 2 | some text
activity table:
id | name
----------
1 | run
2 | swim
3 | walk
4 | climb
activity_comment table:
id | comment_id | activity_id
------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
4 | 3 | 2
When i get into Alaska comments, i would like to see the times that users selected an activity there, for alaska it will show:
run: 1 time
swim: 2 times
walk: 0 times
climb: 0 times
If i go to Peru comments:
run: 0 times
swim: 1 time
walk: 0 times
climb: 0 times
Thank you in advance.
First, get the tables all linked up via the joins showing how each table is related to the next by their respective keys. Then, its just a matter of each name (city/location), and name of the activity and getting a count grouped by the respective location and activity.
The order by clause will put all same locations first, then within each location will put the highest count activity secondary... if any have same counts, then the names will be alpha ordered
SELECT
p.name,
a.name as Activity,
COUNT(*) as NumComments
from
place p
join comment c
ON p.id = c.place_id
join activity_comment ac
ON c.id = ac.comment_id
join activity a
ON ac.activity_id = a.id
group by
p.name,
a.name
order by
p.name,
COUNT(*) desc,
a.name
Now, you will just need to plug in your WHERE clause (before the group by) for whatever location or activity you may be interested in.

summing column in SQL from two tables

I'm using MySQL and trying to sum the number of hours that user has participated in events, but only for a specific organization.
EDIT: I have another table thrown into the mix (misEvents). I think the GROUP BY statement is causing some problems, but I'm not quite sure what to do about it.
table events: contains the "event" information. orgID is the ID of the organization hosting it
-each listing is a separate event
ID | orgID | hours
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
table eventUserInfo: shows which users attended the events
-refID references the ID column from the events table
ID | refID | userID
1 | 1 | 1
2 | 1 | 3
3 | 2 | 2
4 | 2 | 1
5 | 3 | 2
table miscEvents
-these are entered in manually if an event by an organization wasn't posted on the site, but
the users still wants to keep track of the hours
ID | userID | orgID | Hours
1 | 1 | 1 | 2
So, when I view the member activity for organization 1, the following table should display
userid | total hours
1 | 5 //participated in ID #1 and 2 and a misc event, total of 4 hours
2 | 2 //participated in ID #2 only for org 1
3 | 1 //participated only in ID #1
assume the given input is $orgID which is set to 1 in this case
SELECT eventUserInfo.userID, sum(events.hours) as hours,sum(miscEvents.hours) AS mhours FROM events
JOIN eventUserInfo ON events.ID = eventUserInfo.refID
JOIN miscEvents ON events.orgID = miscEvents.orgID
WHERE events.orgID = '$orgID'
GROUP BY eventUserInfo.userID
I think it should be:
SELECT eventInfo.userID --- there is no "events.userID" column
, SUM(events.hours) AS hours --- alias added
FROM events
JOIN eventInfo --- no need for LEFT JOIN
ON events.ID = eventInfo.refID
WHERE events.orgID = '$orgID'
GROUP BY eventInfo.userID
The problem lies probably in that you are trying to print the "hours" with: $event['hours'] but there is no hours column returned (only userID and SUM(event.hours). Use an alias in the SELECT list, as above.
Or since eventINFO seems to be you primary table in the query:
SELECT eventINFO.userID, SUM(events.hours)
FROM eventINFO
JOIN events ON eventINFO.refID = events.ID
WHERE events.orgID = '$orgID'
GROUP BY eventINFO.userID
Should result in the same as ypercube but to me seems a little more clear calling your primary table in the FROM call
To your new problem, you need to get rid of this:
sum(events.hours + miscEvents.hours)
If I'm not mistaken, you can't have multiple columns in a SUM you can only add a single column per call.
I would try something like:
SUM(events.hours) AS hours, SUM(miscEvents.hours) AS mhours
Then, in your function, add together those values $total = hours + mhours

Categories