use where clauses to get data from join query in Laravel - mysql

I am creating a system using Laravel and AngularJS where I assign tasks to users. Multiple tasks has multiple users and vice versa. In the Database, I have this tables:
task:
id | name
task_users:
id | task_id | user_id
users:
id | name
In my view, I display a particular task, using id of task table. I display a list of users (called unassigned users) who are not assigned to that particular task. When that user is assigned, it's name gets removed from the list.
To achieve this, I used this query:
public static function remainingUser($task_id)
{
return \DB::table('users')
->leftjoin('task_users', 'task_users.user_id', '=', 'users.id')
->select('users.id',
'users.name as name'
)
->where('task_id', '!=', $task_id)
->orWhere('task_id', null)
->get();
}
Suppose I have this data
task:
id | name
1 | Task1
2 | Task2
3 | Task3
users:
id | name
1 | User1
2 | User2
3 | User3
4 | User4
5 | User5
6 | User6
7 | User7
8 | User8
9 | User9
10 | User10
task_users:
id | task_id | user_id
1 | 1 | 1
1 | 1 | 2
1 | 1 | 3
1 | 1 | 5
1 | 1 | 6
1 | 1 | 7
1 | 2 | 2
1 | 2 | 4
Now suppose I am displaying task details of task_id = 1 and I want to assign user 4 to this task. So my list of unassigned users should contain all the users who are not assigned this task. My query does not return the required data. I have also tried different conditions in where clause, but I do not get the correct required data. What am I doing wrong?

The issue exists because when you select from Users, you get all users left Joined with a single instance of tasks. So if User1 has Task1 and Task2, only Task1 will be matched here, because it will match User1 to the 1st row found for him within task_users. In order to list the unasigned users, your query would look similar to this:
public static function remainingUser($task_id)
{
return \DB::table('task_users')
->rightJoin('users', 'task_users.user_id', '=', 'users.id')
->select(
\DB::raw("DISTINCT(`users`.`id`)"),
'users.name as name'
)
->where('task_id', '!=', $task_id)
->orWhere('task_id', null)
->get();
}

Related

Laravel Eloquent, How to access eloquent on 3 tables with where?

I have 3 tables like this below :
Items table
id | name
1 | Laptop
2 | Computer
3 | Tv
Production table
id | date
1 | 2021-10-01
2 | 2021-10-03
3 | 2021-10-30
Detail table
id | production_id| item_id |qty
1 | 1 | 1 | 5
2 | 1 | 3 | 10
3 | 2 | 1 |2
4 | 2 | 2 |3
5 | 2 | 3 |23
And what I'm trying to achieve is like this :
(where Production dateBetween this date and that date)
Items |Sum qty
Laptop | 7
Computer | 3
Tv | 33
How to do this in eloquent way ?
Should I use hasManyThrough relationship ?
Thank you for your kind help.
Since this is concatenation problem, i would not use standard relationships for this. Instead let mysql solve this, by creating a custom query in the ORM.
The strategy is to join all the rows, group it by the name of the product. Sum the quantity and create the where condition for the dates.
$items = Item::select('name', DB::raw('SUM(details.qty) as quantity'))
->join('details', 'items.id', '=', 'details.item_id')
->join('productions', 'productions.id', '=', 'details.production_id')
->whereBetween('productions.date', [now()->subMonth(), now())
->groupBy('details.name')
->get();
You could loop the data like so.
foreach ($items as $item) {
$item->name;
$item->quantity;
}

Laravel Eloquent multiple whereHas on relationship where column 'order' is higher than the previous whereHas

I'm working on a bus route system with stops at certain locations.
The route stops are in a ascending sequence by a 'order' column, as in 1, 2, 3, 4, 5.. etc.
These are my tables:
BUS
id | operator_id | name
1 | 1 | The Big Red Bus
PLACES (some dummy data just for example)
id | name | slug | parent_id
1 | Amsterdam | amsterdam |
2 | London | london |
3 | Stockholm | stockholm |
4 | Helsinki | helsinki |
5 | Dam Square | dam-square | 1
ROUTES
id | name
1 | Amsterdam - London
2 | London - Amsterdam
ROUTE_LOCATIONS (LOCATIONS)
id | route_id | place_id | order | start | end
1 | 1 | 1 | 1 | 1 | 0
2 | 1 | 2 | 2 | 0 | 0
3 | 1 | 3 | 3 | 0 | 0
4 | 1 | 4 | 4 | 0 | 1
User Input
The given user input when I start a search to check if we have any available bus routes is a place slug from the PLACES table.
For example:
from: london
to: dam-square
Below is the query what I tried so far, but I am really unsure on how to build a check/join into the actual query to check if the 'order' sequence is ascending. I just can't get my head around it.
$buses = Bus::whereHas('route.locations.place', function ($query) use ($from, $to) {
$query->where('slug', $from)->where('end', 0);
})->whereHas('route.locations.place', function ($query) use ($from, $to) {
$query->where('slug', $to)->where('start', 0);
})->get();
Relationships structure is as follows:
BUS hasOne ROUTE
ROUTE belongsToMany BUS
ROUTE hasMany LOCATIONS (ROUTE_LOCATIONS TABLE)
I have already tried the following query which works to get available routes, but I really like to do it directly in laravel eloquent with my models, so I can easily use the relationships in my view. I am just unsure on how to go about it.
Below query only works with a place ID instead of a slug, and I really like it to be a slug instead of an ID.
$routes = DB::select('SELECT R.id, R.name
FROM route_locations L
INNER JOIN routes R ON R.id = L.route_id
WHERE
L.place_id = "'.$from->id.'" AND
EXISTS (SELECT id FROM route_locations F WHERE L.route_id = F.route_id AND F.order > L.order AND F.place_id = "'.$to->id.'")');
Does anyone know if this is possible and how?
Thank you!
Your code could be rewritten to this one:
$busses = Bus::whereHas('route.locations.place', function ($query) use ($from, $to) {
$query->where('slug', $from)
->where('slug', $to)
->where('start', 0)
->where('end', 0);
// here should do a check if the 'order' column is higher than the first wherehas
})->get();
And it doesn't make much sense to me.

Application specific MySQL table Structure

I have a question about my DB table structure. I want to know if i'm on the right track or if I have missed a good alternative. Here is the case:
To make it easy to read, I haven't pasted the full contents as my question is only about the structure.
2 tables:
1: id (AI), task
2: id, name, task
Table 1 presents dynamic check-boxes which can be altered by an admin panel so the contents would be like this
1 task1
2 task2
5 task5
(3 & 4 are missing cause the administrator deleted those records).
In table number two are the people who should do the tasks from table 1. And the goal is that the tasks wich are not checked will be displayed.
So the contents of table 2 would be:
1 Name1 1,5
2 Name2 1,2
3 Name3 1,2,5
The numbers in table 2 represent the checked boxes from table 1. So with a query i can compare the numbers from table 2 with the id's from table 1 and display the missing ids as "todo".
In my opinion this looks very overdone, and there must be an easier way to create dynamic options which can be compared and stored as a todo.
Suggestions are welcome!
I suggest you to use basic structure for many-to-many relationship:
tasks users user_tasks
+----+-----------+ +----+-------+ +---------+---------+
| id | name | | id | name | | user_id | task_id |
+----+-----------+ +----+-------+ +---------+---------+
| 1 | Buy milk | | 1 | John | | 1 | 2 |
| 2 | Get drunk | | 2 | Tim | | 3 | 2 |
| 3 | Have fun | | 3 | Steve | | 2 | 4 |
| 4 | Go home | +----+-------+ | 3 | 4 |
+----+-----------+ +---------+---------+
And you can fetch unassigned tasks using following query:
SELECT
tasks.*
FROM
tasks
LEFT JOIN
user_tasks
ON (tasks.id = user_tasks.task_id)
WHERE
user_tasks.user_id IS NULL
You also can fetch users who have no assigned tasks:
SELECT
users.*
FROM
users
LEFT JOIN
user_tasks
ON (users.id = user_tasks.user_id)
WHERE
user_tasks.user_id IS NULL
Hope this will help you.

query to get most matched likes first in mysql

I have table like:
user :
uid | course_id | subjects
---------------------------
1 | 1 | html,php
2 | 1 | java,html,sql
3 | 1 | java
4 | 1 | fashion,html,php,sql,java
I want to run a query which can return most liked subjects in query and then second most and so on...
For Example :
select * from user where subjects like '%java%' or '%php%' or '%html%';
this query will return data like this:
uid | course_id | subjects
---------------------------
2 | 1 | java,html,sql
3 | 1 | java
4 | 1 | fashion,html,php,sql,java
but i want output like this :
uid | course_id | subjects
---------------------------
4 | 1 | fashion,html,php,sql,java
2 | 1 | java,html,sql
1 | 1 | html,php
3 | 1 | java
so the most matched subjects 1st then 2nd most matched subjects and so on....
Is there any modification in my query so that i can get this type of sorted output.
Never, never, never store multiple values in one column!
Like you see now this will only give you headaches. Normalize your user table. Then you can select normally.
It should look like this
uid | course_id | subjects
---------------------------
1 | 1 | html
1 | 1 | php
2 | 1 | java
2 | 1 | html
2 | 1 | sql
3 | 1 | java
...
or better introduce an new table subjects and then make a mapping table called course_subjects
subject
id | name
------------
1 | html
2 | sql
3 | java
...
course_subjects
uid | course_id | subject_id
---------------------------
1 | 1 | 1
1 | 1 | 2
...
Based on the way you want your results, it looks like you want to order by the number of subjects (or tags) within subject. This can be accomplished by counting the number of , (commas).
The way to count the number of occurances of a character is to subtract the original length by the length when the character is removed.
Example:
SELECT *
FROM USER
WHERE subjects LIKE '%java%'
OR '%php%'
OR '%html%'
ORDER BY ( Length(subjects) - Length(Replace(subjects, ',', '')) ) DESC;
SQLFiddle: http://sqlfiddle.com/#!2/cc793/4
Result:
UID COURSE_ID SUBJECTS
4 1 fashion,html,php,sql,java
2 1 java,html,sql
3 1 java
Note:
As juergen says it is a bad idea to store multiple values in one column.
With MyISAM storage engine you can do match against.
The simplest example:
SELECT *,
MATCH (subjects) AGAINST ('java php html') AS relevance
FROM `user`
WHERE MATCH (subjects) AGAINST ('java php html')
ORDER BY relevance DESC
In MySQL 5.6 full-text search is available with InnoDB too but needs a bit extra to make it work. For more info checkout the following post: http://www.mysqlperformanceblog.com/2013/03/04/innodb-full-text-search-in-mysql-5-6-part-2-the-queries/

mysql group rows in joined query

Im stumbling upon a problem where i need to retrieve data from the following tables
events
+-------+---------+---------+
| e_id | e_title | e_link |
+-------+---------+---------+
| 1 | Event 1 | event_1 |
| 2 | Event 2 | event_2 |
| 3 | Event 3 | event_3 |
| 4 | Event 4 | event_4 |
| 5 | Event 5 | event_5 |
+-------+---------+---------+
reservations
+-------+---------+---------+
| r_id | r_e_id | r_u_id |
+-------+---------+---------+
| 1 | 2 | 1 |
| 2 | 2 | 3 |
| 3 | 5 | 4 |
| 4 | 2 | 4 |
| 5 | 1 | 1 |
+-------+---------+---------+
users
+-------+---------+----------+
| u_id | u_name | u_gender |
+-------+---------+----------+
| 1 | One | Male |
| 2 | Two | Male |
| 3 | Three | Female |
| 4 | Four | Male |
| 5 | Five | Female |
+-------+---------+----------+
I want to display an event page with the users that are subscribed to that event, like follows:
Event 2
Users:
- One
- Three
- Four
I have the following query with the problem that this one only displays the first user (so in this case Four), which makes sense because the mysql_fetch_assoc() is not in a while() loop.
$result = mysql_query("
SELECT events.e_title, reservations.*, users.u_name
FROM events
JOIN reservations
ON events.e_id = reservations.r_e_id
JOIN users
ON reservations.r_u_id = users.u_id
WHERE events.e_link = '".mysql_real_escape_string($_GET['link'])."'
");
$show = mysql_fetch_assoc($result);
What should i change in my query to make it work the way i want?
EDIT:
The solution from Teez works perfect, but wat if i want to attach more info, say for a link? My desired output is something like this:
Event 2
Users:
- User 1 Male
- User 3 Female
- User 4 Male
How am i going to achieve that? And eventually i even want to split the users by gender. So one list for females and one for males
SECOND EDIT:
I'm stunned with the result so far, but to complete it i want to sort the users by gender, like so:
Event 2
Users male:
- User 1 Male
- User 4 Male
Users female:
- User 3 Female
but how?
Best way will be first make a 2D array containing all events with respective users
Like below:
while( $show = mysql_fetch_assoc($result))
{
$events[$show['e_id']][]=$show['u_name'];
$uid[$show['e_id']][]=$show['u_id'];
}
Then loop arround above array for displaying :
foreach($events ad $key=>$users)
{
echo "Event ".$key."<br>";
echo "Users : <br>";
foreach($users as $ukey=>$name)
{
echo " -<a href='domain.com/user/".$uid[$key][$ukey]."'>".$name."</a>;
}
}
So with each call of mysql_fetch_assoc you want to have the event details and a list of usernames? In MySQL you can use GROUP_CONCAT for this purpose, although it is quite limited and error-prone. You should rather put mysql_fetch_assoc() in a loop to build an array of users. Anyway, here is the GROUP_CONCAT solution:
$result = mysql_query("
SELECT events.e_title, GROUP_CONCAT(users.u_name) e_reservation_users
FROM events
JOIN reservations ON events.e_id = reservations.r_e_id
JOIN users ON reservations.r_u_id = users.u_id
WHERE events.e_link = '".mysql_real_escape_string($_GET['link'])."'
GROUP BY 1
");
$show = mysql_fetch_assoc($result);
$show will then be
array('e_title' => '...', 'e_reservation_users' => '...,...,...').