Laravel OrderBy OneToMany Relation - mysql

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.

Related

How do i add another COUNT statement with different condition to sql query

I have 2 tables.
1st table: duels
| duelId | user1Id | user2Id | gameId | winnerId |
2nd table: usergameprogress
| usergameprogressId | userId | gameId | gameStar |
Given an userId, I would like to get duel count, gameStar, win count for each gameId.
Example return:
| duelCount | duelWinCount | gameStar | gameId |
I have managed to get duelCount, gameStar and gameId given a userId but I couldn't add duelWinCount to my query result. How do I do that ?
My query:
SELECT
COUNT(d1.duelId) AS duelCount,
usergameprogress.gameId, usergameprogress.gameStar
FROM
duels d1
JOIN
usergameprogress ON (usergameprogress.gameId = d1.gameId)
WHERE
d1.user1Id = "gkfurcwsi033qzxg0u2bmj1ekebsklej"
OR d1.user2Id = "gkfurcwsi033qzxg0u2bmj1ekebsklej"
GROUP BY
usergameprogress.gameId
EDIT:
solved thanks to comment use sum instead of count
SELECT sum(case when d1.user1Id = 'gkfurcwsi033qzxg0u2bmj1ekebsklej' OR d1.user2Id="gkfurcwsi033qzxg0u2bmj1ekebsklej" then 1 else 0 end) AS totalDuelCount,sum(case when winnerId="gkfurcwsi033qzxg0u2bmj1ekebsklej" then 1 else 0 end) AS duelWinCount,usergameprogress.gameId,usergameprogress.gameStar FROM duels d1 JOIN usergameprogress ON (usergameprogress.gameId = d1.gameId) GROUP BY usergameprogress.gameId

Select all items and count in related table by criteria

I have tables Match and Reaction as following:
REACTION
+----------+----------+----------+----------+
| user_id | game_id | item_id | reaction |
+----------+----------+----------+----------+
| 1 | 1 | 1 | 1 |
| 1 | 1 | 2 | 1 |
| 2 | 1 | 1 | 1 |
| 2 | 1 | 2 | 0 |
+----------+----------+----------+----------+
MATCH:
+----------+----------+
| game_id | item_id |
+----------+----------+
| 1 | 1 |
| 1 | 2 |
+----------+----------+
Now I want (if possible without subqueries) to select ALL item_ids from MATCH table AND count of rows where field reaction in table Reaction is equal to 1 for user with id = 2. For example, for defined tables I want to get following results:
+----------+----------+
| item_id | count |
+----------+----------+
| 1 |  1 |
| 2 | 0 |
+----------+----------+
I've tried something like
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
WHERE reaction.reaction = 1 AND match.game_id = 2
GROUP BY match.item_id
HAVING c > 0
but it didn't work as expected. I cannot get count for particular user.
I think you are close. I think you just need to move conditions on the second table to the ON clause:
SELECT m.item_id, COUNT(r.user_id) as c
FROM match m LEFT JOIN
reaction r
ON r.item_id = m.item_id AND
r.reaction = 1 AND
r.user_id = 2
WHERE m.game_id = 2
GROUP BY m.item_id;
I'm not sure what the HAVING clause is for, because you seem to want counts of 0.
Note that this also introduces table aliases so the query is easier to write and to read.
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match JOIN reaction ON (reaction.item_id = match.item_id and reaction.reaction = 1 AND match.game_id = 2)
GROUP BY match.item_id
HAVING COUNT(reaction.user_id)
I think you need to filter 'before' join -> so use the 'on' clause.
Filters in where are applied after the join is made while filter applied on on clause are applied before the join is made
You have not game_id = 2 so this should return no value
and you should not use left joined table columns in where condition otherwise these wprk as inner join ... in these cases you shou move the related condition in ON clause
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
AND reaction.reaction = 1
WHERE match.game_id = 2
GROUP BY match.item_id
HAVING c > 0
but try also
SELECT match.item_id, COUNT(reaction.user_id) as c
FROM match
LEFT JOIN reaction ON reaction.item_id = match.item_id
AND reaction.reaction = 1
GROUP BY match.item_id

Laravel Selection Query with left join

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.

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.

Mysql search on multiple join results

I've a table "products" and a table where are store some attributes of a product:
zd_products
----------
|ID|title|
----------
| 1| Test|
| 2| Prod|
| 3| Colr|
zd_product_attached_attributes
------------------
|attrid|pid|value|
------------------
|1 | 1 | A |
|2 | 1 | 10 |
|3 | 1 | AB |
|1 | 2 | B |
|2 | 2 | 22 |
|3 | 2 | BB |
|1 | 3 | A |
|2 | 3 | 10 |
|3 | 3 | CC |
I want to search in zd_products only the products that have some attributes values, for exam place
Get the product when the attribute 1 is A and the attribute 3 is AB
Get the product when the attribute 2 is 10 and the attribute 3 is CC
etc
How can i do this using a join ?
Oh, the Joys of the EAV model!
One way is to use a separate JOIN operation for each attribute value. For example:
SELECT p.id
, p.title
FROM zd_products p
JOIN zd_product_attached_attributes a1
ON a1.pid = p.id
AND a1.attrid = 1
AND a1.value = 'A'
JOIN zd_product_attached_attributes a3
ON a3.pid = p.id
AND a3.attrid = 3
AND a3.value = 'AB'
With appropriate indexes, that's likely going to be the most efficient approach. This isn't the only query that will return the specified result, but this one does make use of JOIN operations.
Another, less intuitive approach
If id is unique in the zd_products table, and we have guarantee that the (attrid,pid,value) tuple is unique in the zd_product_attached_attributes table, then this:
SELECT p.id
, p.title
FROM zd_products p
JOIN zd_product_attached_attributes a
ON a.pid = p.id
AND ( (a.attrid = 1 AND a.value = 'A')
OR (a.attrid = 3 AND a.value = 'AB')
)
GROUP
BY p.id
, p.title
HAVING COUNT(1) > 1
will return an equivalent result. The latter query is of a form that is particularly suitable for matching two criteria out of three, where we don't need a match on ALL of the attributes, but just some of them. For example, finding a product that matches any two of:
color = 'yellow'
size = 'bigger'
special = 'on fire'
And of course there are other approaches that don't make use of a JOIN.
FOLLOWUP
Q: And if I want to the same but using OR operator? I mean get ONLY if the attribute 1 is A or the attribute 2 is AB otherwise don't select the record.
A: A query of the form like the second one in my answer (above) is more conducive to the OR condition.
If you want XOR (exclusive OR), where one of the attributes has a matching value but the other one doesn't, just change the HAVING COUNT(1) > 1 to HAVING COUNT(1) = 1. Only rows from products that find one "matching" row in the attributes table will be returned. To match exactly 2 (out of several), HAVING COUNT(1) = 2, etc.
A query like the first one in my answer can be modified to use OUTER joins, to find matches, and then do a conditional test in the WHERE clause, to determine if a match was found.
SELECT p.id
, p.title
FROM zd_products p
LEFT
JOIN zd_product_attached_attributes a1
ON a1.pid = p.id
AND a1.attrid = 1
AND a1.value = 'A'
LEFT
JOIN zd_product_attached_attributes a3
ON a3.pid = p.id
AND a3.attrid = 3
AND a3.value = 'AB'
WHERE a1.pid IS NOT NULL
OR a3.pid IS NOT NULL
I've just added the LEFT keyword, to specify an outer join; rows from products will be returned with matching rows from a1 and a3, along with rows from products that don't have any matching rows found in a1 or a3.
The WHERE clause tests a column from a1 and a3 to see whether a matching row was returned. If a matching row was found in a1, we are guaranteed that the pid column from a1 will be non-NULL. That column will be returned as NULL only if a matching row was not found.
If we replaced the OR with an AND, we'd be negating the "outerness" of both joins, making it essentially equivalent to the first query above.
To get an XOR type operation (exclusive OR) where we find one matching attribute but not the other, we could change the WHERE clause to read:
WHERE (a1.pid IS NOT NULL AND a3.pid IS NULL)
OR (a3.pid IS NOT NULL AND a1.pid IS NULL)
Use a pivot
You can do this type of query using a pivot. As far as I know, MySQL doesn't have a native, built in pivot, but you can achieve this by transposing the rows and columns of your zd_product_attached_attributes table using:
SELECT pid,
MAX(CASE WHEN attrid = 1 THEN value END) `attrid_1`,
MAX(CASE WHEN attrid = 2 THEN value END) `attrid_2`,
MAX(CASE WHEN attrid = 3 THEN value END) `attrid_3`
FROM zd_product_attached_attributes
GROUP BY pid
This will pivot your table as shown:
+----+---------+-------+ +----+----------+----------+----------+
| attrid | pid | value | | pid| attrid_1 | attrid_2 | attrid_3 |
+----+---+-------------+ +----+----------+----------+----------+
| 1 | 1 | A | | 1 | A | 10 | AB |
| 2 | 1 | 10 | => | 2 | B | 22 | BB |
| 3 | 1 | AB | | 3 | A | 10 | CC |
| 1 | 2 | B | +----+----------+----------+----------+
| 2 | 2 | 22 |
| 3 | 2 | BB |
| 1 | 3 | A |
| 2 | 3 | 10 |
| 3 | 3 | CC |
+--------+---------+---+
So you can select the products id and title using:
SELECT id, title FROM zd_products
LEFT JOIN
(
SELECT pid,
MAX(CASE WHEN attrid = 1 THEN value END) `attrid_1`,
MAX(CASE WHEN attrid = 2 THEN value END) `attrid_2`,
MAX(CASE WHEN attrid = 3 THEN value END) `attrid_3`
FROM zd_product_attached_attributes
GROUP BY pid
) AS attrib_search
ON id = pid
WHERE ( attrib_1 = 'A' AND attrib_3 = 'AB' )
OR ( attrib_2 = 10 AND attrib_3 = 'CC' )
Note: You can use this type of query when you have guaranteed uniqueness on (pid, attrid)
(thanks #spencer7593)
I haven't tested this, but I think it should work:
select title
from zd_products p
join zd_product_attached_attributes a ON a.pid = p.id
where ( attrid = 1 and value = 'A' )
or ( attrid = 3 and value = 'AB' );
If you want to tack on more "searches" you could append more lines similar to the last one (ie. or "or" statements)