Laravel Selection Query with left join - mysql

Table - tbl_user_details
UserId | Username
------------------
1 | jijo
2 | libin
Table - tbl_user_followups
FollowupId | UserId | Status
---------------------------------
1 | 1 | Negative
2 | 1 | Neutral
3 | 1 | Positive
My Controller is
$result= DB::table('tbl_user_details')
->leftjoin('tbl_user_followups','tbl_user_details.UserId','=','tbl_user_followups.UserId')
->select('tbl_user_details.*','tbl_user_followups.*')
->orderBy('tbl_user_followups.FollowupId','DESC')
->groupBy('tbl_user_details.UserId')
->get();
I want to get the output like as below
UserId | Username | FollowupId | Status
----------------------------------------
1 | jijo | 3 | Positive
2 | libin
Anyone can you please suggest an edit in my controller ???

Not sure if I understood question right: You want list of users with last "followup"? If it is so, then:
SELECT d.userid,
d.username,
det.followupid,
det.status
FROM tbl_user_details d
LEFT JOIN (SELECT userid,
followupid,
status
FROM tbl_user_followups f
WHERE followupid IN (SELECT Max(followupid)
FROM tbl_user_followups
GROUP BY userid)) det
ON d.userid = det.userid
ORDER BY det.followupid DESC;
order by det.followupid desc;
Please note that "order by det.followupid" will not sort them correctly, as we expect for some users to have "null" followupid.

Related

MySQL left join that shows NULL for missing rows

I have 2 tables
Table:
recip
recipid | recipname
1 | Recip1
2 | Recip2
And table:
recipuser
recipid | userid
1 | 1
2 | 1
1 | 2
So userid 2 has 1 recip
The result I'm trying to achieve is to show all "recip" rows with matching or null for given user id, EG:
SELECT r.recipid, r.recipname, ru.userid
FROM recip r
left JOIN recipuser ru ON r.recipid = ru.recipid
WHERE ru.userid = 2 OR ru.userid IS NULL
Results in:
recipid | recipname | userid
1 | Recip1 | 2
I want to get:
recipid | recipname | userid
1 | Recip1 | 2
2 | Recip2 | NULL
How do I show all rows from recip with the userid or NULL for every row given a user id??
Thanks for your help.
Move the WHERE logic to the ON clause:
SELECT r.recipid, r.recipname, ru.userid
FROM recip r
LEFT JOIN recipuser ru
ON r.recipid = ru.recipid AND ru.userid = 2;
The problem with your current query is that the WHERE clause is filtering off the non matching record which you want to appear.

Getting the most recent row and linking it with another table?

Im trying to get the most recent row of a table
user_quiz:
+--------+-----------+-------------+-------------------+------------+
|quiz_id |userid | module_id |number_of_questions| user_score |
+--------+-----------+-------------+-------------------+-------- ---+
| 1 | 1 | 1 | 5 | 5 |
| 2 | 2 | 2 | 10 | 9 |
| 3 | 1 | 1 | 10 | 9 |
+--------+-----------+-------------+-------------------+------------+
I have used the query:
SELECT * FROM user_quiz WHERE userid = 1 ORDER BY quiz_id DESC LIMIT 1
which correctly retrieves the last row.
However I want to link the module_id with another table:
module:
+---------+------------+
|module_id|module_name |
+---------+------------+
| 1 | Forces |
| 2 | Electricity|
+---------+------------+
And retrieve the module name.
The result of the query will be used to print out the users most recent quiz:
Most recent quiz: Forces - Number of questions: 10 - User Score: 9
Is this possible using just one query?
You just need a JOIN:
SELECT uq.*, m.module_name
FROM user_quiz uq JOIN
modules m
ON uq.module_id = m.module_id
WHERE uq.userid = 1
ORDER BY uq.quiz_id DESC
LIMIT 1;
A more simple query to achieve the same would be
SELECT
user_quiz.quiz_id,
user_quiz.number_of_questions,
user_quiz.user_score,
modules .module_name
FROM user_quiz JOIN modules
ON user_quiz.module_id = modules.module_id
WHERE user_quiz.userid = 1
ORDER BY user_quiz.quiz_id DESC
LIMIT 1
If you want to get the same results for all the users, you could use a bit more sophisticated query
SELECT
user_quiz_virtual_table.userid,
user_quiz_virtual_table.quiz_id,
user_quiz_virtual_table.number_of_questions,
user_quiz_virtual_table.user_score,
modules.module_name
FROM (
SELECT
user_quiz.userid
user_quiz.quiz_id,
user_quiz.module_id
user_quiz.number_of_questions,
user_quiz.user_score
FROM user_quiz
ORDER BY user_quiz.quiz_id DESC
GROUP BY userid
) AS user_quiz_virtual_table
JOIN modules ON user_quiz_virtual_table.module_id = modules.module_id

How to get all data from a table which you also query for AVG

Hello I need help on solving the query problem below.. Thanks in advance.
I have three tables
attachments
sun_individual
sun_reviews
I want to select user names, profession and reviewed by his/her ID from sun_individual and join with his profile photo from table attachments then get his/her reviews (each review and rate) and the average RATE
TABLE : sun_individual
id|sun_id|first_name|last_name |sun_profession|sun_verified_date|sun_verified|flg
---------------------------------------------------------------------------------
20|SV-001|Alex | James | Doctor |2017-12-08 | 1 |1
21|SV-002|Jane | Rose | Programmer |2017-12-08 | 1 |1
TABLE: sun_reviews
id|user_id|rev_txt |rev_rate|rev_date |flg
----------------------------------------------------
1 |20 | the best | 4 |2017-12-09|1
2 |21 | know CLI | 2 |2017-12-09|1
3 |20 | recommend| 3 |2017-12-09|0
4 |20 | so far | 3 |2017-12-09|1
TABLE: attachments
id|user|type |path |flg
----------------------------------------
88|20 |passport|/upload/img128.jpg|1
89|21 |passport|/upload/img008.jpg|1
flg:1 means the value is active, flg:0the value is to be ignored
My Code is :
SELECT
sun_reviews.rev_txt As txtReview, sun_reviews.rev_date As dateReview,
sun_reviews.rev_rate As rateReview,
AVG(sun_reviews.rev_rate) As avgREV,
concat(sun_individual.first_name, sun_individual.last_name) As name,
sun_individual.sun_profession As profession,
sun_individual.sun_verified_date As dateVerified,
CASE when sun_verified = 1 then 'VERIFIED' else 'EXPIRED' END As status,
attachments.path As photo
FROM `sun_individual`
LEFT JOIN sun_reviews ON sun_reviews.user_id = sun_individual.id
INNER JOIN attachments ON attachments.user = sun_individual.id
WHERE attachments.type = 'passport' AND attachments.flg = 1
AND sun_reviews.flg = 1 AND sun_individual.flg = 1
AND sun_individual.sun_id LIKE '%SV-001'
What I want to archive is when someone is looking for user (let say SV-001) when the code is inputted to get result like
result for: SV-001
txtReview|dateReview|rateReview|avgREV|name |profession | dateVerified | photo
------------------------------------------------------------------------- -----------------
the best |2017-12-09|4 |3.5000|Alex James| Doctor | 2017-12-08 |/upload/img128.jpg
so far |2017-12-09|3 |3.5000|Alex James| Doctor | 2017-12-08 |/upload/img128.jpg
I want to get result like the one above, however when I ran the query I get only one review
txtReview|dateReview|rateReview|avgREV|name |profession | dateVerified | photo
------------------------------------------------------------------------- -----------------
the best |2017-12-09|4 |3.5000|Alex James| Doctor | 2017-12-08 |/upload/img128.jpg
I think there is something am doing wrong... If you know the solution to my problem kindly help me.
Thanks.
select
a.rev_txt as txtReview,
a.rev_date as dateReview,
a.rev_rate as rateReview,
d.avgRev as avgRev,
b.first_name || b.last_name as name,
b.sun_profession as profession,
b.sun_verified_date as dateVerified,
case
when sun_verified = 1 then 'VERIFIED'
else 'EXPIRED'
end as status,
c.path as photo
from
sun_reviews a
join
sun_individual b
on a.user_id = b.id
join
attachments c
on c.user_id = b.id
join
(
select
user_id,
avg(rev_rate) as avgRev
from
sun_reviews
where
flg = 1
group by
user_id
) d
on d.user_id = b.id
where
c.type = 'passport' and
c.flg = 1 and
a.flg = 1 and
b.flg = 1 and
b.sun_id LIKE '%SV-001';
txtreview | datereview | ratereview | avgrev | name | profession | dateverified | status | photo
-----------+------------+------------+--------------------+-----------+------------+--------------+----------+--------------------
the best | 2017-12-09 | 4 | 3.5000000000000000 | AlexJames | Doctor | 2017-12-08 | VERIFIED | /upload/img128.jpg
so far | 2017-12-09 | 3 | 3.5000000000000000 | AlexJames | Doctor | 2017-12-08 | VERIFIED | /upload/img128.jpg
(2 rows)
When you have a group by, all columns in the result set must be either group by columns or aggregates. I guess you are using MySQL which does not enforce this. You should enforce it yourself though, otherwise it gets very confusing.

Laravel OrderBy OneToMany Relation

I have 2 tables events_users:
Which stores information about people that have attended certain events. Many people attend many events, hence using this linking table.
+--------+---------+---------+
| eid | user_id | slot_no |
+--------+---------+---------+
| event1 | user1 | 0 |
| event1 | user2 | 1 |
| event2 | user3 | 0 |
+--------+---------+---------+
And events_user_score:
Which records the different scores of people at the events. At different events there can be lots of different score types, which is why this is in its own table, rather than as columns in the first table
+--------+---------+------------+--------+
| eid | user_id | score_type | number |
+--------+---------+------------+--------+
| event1 | user1 | 1 | 4 |
| event1 | user1 | 2 | 3 |
| event2 | user3 | 1 | 6 |
| event1 | user1 | 2 | 5 | <- this row could not exist as
+--------+---------+------------+--------+ no duplicates possible with same eid, user_id & score_type
I'd like to be able to sort a selection of the first table, based on the figures in the second table. So would love to get a join working that would get a result like this. In effect turning score_type into columns, with the number as the value.
+--------+---------+---------+---+---+
| eid | user_id | slot_no | 1 | 2 |
+--------+---------+---------+---+---+
| event1 | user1 | 0 | 4 | 3 |
| event1 | user2 | 1 | | |
| event2 | user3 | 0 | 6 | |
+--------+---------+---------+---+---+
So far i've tried using laravel's "hasMany" but i can't sort using that technique. So am looking at trying a left join of some type, but have got really confused as now i'm not getting any score back:
$attendees = Events_user::whereIn("events_users.eid", $lastWeeksEvents->pluck('eid'))->where("slot_no",">=",0)
->leftJoin('event_user_score', function ($join) {
$join->on('events_users.eid', '=', 'event_user_score.eid')
->where('events_users.uid', '=', 'event_user_score.uid');
})
->get();
Assume that $lastWeeksEvents returns a list of events with the eid's matching those in the first table.
Edit - even though ther are score_types 1 - 6. Let's stick with just trying to get types 1 & 2 as columns. If that makes it any easier?
To get the desired results you can write following in plain SQL as
select e1.*,
max(case when e2.score_type =1 then e2.number else 0 end ) score_type1,
max(case when e2.score_type =2 then e2.number else 0 end ) score_type2
from events_users e1
left join events_user_score e2 on e1.eid = e2.eid
and e1.user_id = e2.user_id
group by e1.eid, e1.user_id,e1.slot_no
Demo
The above query assumes that there can be two score_types as 1 & 2 if there are more the above query will not be feasible for such type of resultset.
The same using laravel's query builder can be written as.
$attendees = Events_user::from('events_users as e1')
->select(DB::raw("e1.*,max(case when e2.score_type =1 then e2.number else 0 end ) score_type1, max(case when e2.score_type =2 then e2.number else 0 end ) score_type2"))
->leftJoin('events_user_score as e2', function ($join) {
$join->on('e1.user_id', '=', 'e2.user_id');
})
->groupBy('e1.eid')
->groupBy('e1.user_id')
->groupBy('e1.slot_no')
->get();
The above solution join events_users table with your second table events_user_score by matching event and user. The aggregation is involed to get unique data per eid,user_id & slot_no
You could also use a subquery as follows:
Plain SQL:
select u.eid, u.user_id, u.slot_no,
(select number from events_user_score s where s.eid = u.eid and s.user_id = u.user_id and s.score_type = 1) as `1`,
(select number from events_user_score s where s.eid = u.eid and s.user_id = u.user_id and s.score_type = 2) as `2`
from events_users u
Now, using that in Laravel:
//Get all score types
$score_types = DB::table('events_user_score')
->select('score_type')
->groupBy('score_type')
->get();
$event_query = Events_user::from(DB::raw('events_users u'))
->select('eid', 'user_id', 'slot_no');
//Add score types as sub queries
foreach ($score_types as $score) {
$event_query->addSelect(DB::raw('(select number from events_user_score s where s.eid = u.eid and s.user_id = u.user_id and s.score_type = ' . $score->score_type . ') as `' . $score->score_type . '`'));
}
//Example, to sort by score type 1
$data = $event_query->orderBy('1')->get();
Remember to add use DB; at the top of your code.

Select all if no subset is present, otherwise select subset

Ok here's my problem. Assume a customer has access to a number of regions defined in a CustomerRegions table:
CustomerRegionID | CustomerID | RegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
Assume that customer 1 has three users 1, 2, and 3. For each user we can specify to which of the CustomerRegions they have access via a table UserRegions:
UserRegionID | UserID | CustomerRegionID
----------------------------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 2
So user 1 will have access to both Customerregions and user 2 will only have access to CustomerRegion 2.
If there are UserRegions specified for a given user then only those CustomerRegions are present in the result set, but if no UserRegions are specified for a given user then all CustomerRegions are present in the result. I want to get all accessible regions per user of a given customer. The result I am looking for is something like this:
CustomerID | UserID | RegionID
------------------------------
1 | 1 | 1
1 | 1 | 2
1 | 2 | 2
1 | 3 | 1
1 | 3 | 2
My question is can this be done in a single query and how?
Edit:
I seem to have it working now:
SELECT CustomerID,
UserID,
RegionID
FROM users
LEFT JOIN customerregions ON customerregions.CustomerID = users.CustomerID
LEFT JOIN userregions ON userregions.UserID = users.UserID AND userregions.CustomerRegionID = customerregions.CustomerRegionID
LEFT JOIN regions ON regions.RegionID = customerregions.RegionID
WHERE (userregions.UserID IS NOT NULL
OR (SELECT COUNT(1) FROM userregions WHERE userregions.UserID = users.UserID) = 0)
AND CustomerID = 1
The extra count query in the where seems to do the trick. Thanks #Pablo Martinez for your help. However if someone knows of a better way to do this please let me know.
I'm aggre with #diEcho, the table structure is very confusing
have you try to do a join?
Select CustomerID, UserID, RegionID
from UserRegions join CustomerRegion
on CustomerRegion.CustomerRegionID=UserRegions.CustomerRegionID
where customerID=1