How to use case with withSum in laravel? - php

I have a posts table and a comments table. A post can have many comments. comments has a field called stars. I want to get all posts alongside the rank. Rank is basically the stars in comments where if the stars is 3 then the rank would be 5.
I tried the following,
Route::get('/', function () {
return Post::withSum(
'comments as rank'
, 'CASE WHEN stars > 3 THEN 5 ELSE 0 END')->get();
});
I get the error,
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'comments.CASE WHEN stars > 3 THEN 5 ELSE 0 END' in 'field list'
SELECT
`posts`.*,
(
SELECT
sum(
`comments`.`CASE WHEN stars > 3 THEN 5 ELSE 0 END`
)
FROM
`comments`
WHERE
`posts`.`id` = `comments`.`post_id`
) AS `rank`
FROM
`posts`
Looks like laravel is messig up the syntax comments.CASE WHEN stars > 3 THEN 5 ELSE 0 END. How do I fix it?
Repository: https://github.com/axelthat/laravel9-issue

Related

Multiple leftJoins using Laravel's Query Builder producing incorrect counts

I am using Laravel 5.4's Query Builder to perform a series of leftJoins on three tables. Here are my tables:
items
id type title visibility status created_at
-- ---- ----- ---------- ------ ----------
1 1 This is a Title 1 1 2017-06-20 06:39:20
2 1 Here's Another Item 1 1 2017-06-24 18:12:13
3 1 A Third Item 1 1 2017-06-26 10:10:34
count_loves
id items_id user_id
-- ------- -------
1 1 2
2 1 57
3 1 18
count_downloads
id items_id user_id
-- ------- -------
1 1 879
2 1 323
And here is the code I am running in Laravel:
$items_output = DB::table('items')
->leftJoin('count_loves', 'items.id', '=', 'count_loves.items_id')
->leftJoin('count_downloads', 'items.id', '=', 'count_downloads.items_id')
->where('items.visibility', '=', '1')
->where('items.status', '=', '1')
->orderBy('items.created_at', 'desc')
->select('items.*', DB::raw('count(count_loves.id) as loveCount'), DB::raw('count(count_downloads.id) as downloadCount'))
->groupBy('items.id')
->get();
When I return the results for this query, I am getting the following counts:
count_loves: 6
count_downloads: 6
As you can see, the actual count values should be:
count_loves: 3
count_downloads: 2
If I add another entry to the count_loves table, as an example, the totals move to 8. If I add another entry to the count_downloads table after that, the totals jump to 12. So, the two counts are multiplying together.
If I die and dump the query, here's what I get:
"query" => "select 'items'.*, count(count_loves.id) as loveCount,
count(count_downloads.id) as downloadCount from 'items' left join
'count_loves' on 'items'.'id' = 'count_loves'.'items_id' left join
'count_downloads' on 'items'.'id' = 'count_downloads'.'items_id'
where 'items'.'visibility' = ? and 'items'.'status' = ? group by
'items'.'id' order by 'items'.'created_at' desc"
How do I perform multiple leftJoins using Query Builder and count on several tables to return the proper sums?
NOTE:
This is intended as a HELP answer not the total absolute answer but I could not write the code in a comment. I am not asking for votes (for those who just can't wait to downvote me). I have created your tables and tried a UNION query on raw sql. I got correct results. I dont have laravel installed, but maybe you could try a UNION query in Laravel.
https://laravel.com/docs/5.4/queries#unions
select count(count_downloads.user_id)
from count_downloads
join items
on items.id = count_downloads.items_id
UNION
select count(count_loves.user_id)
from count_loves
join items
on items.id = count_loves.items_id

Average of a calculated second average in Laravel Query Builder

I have 3 tables : Appointments, Reviews, Review_ratings
Each review_ratings has a rating value. Each review has many review_ratings and finally, eeach appointment has many reviews.
What I am trying to do is to calculated the average of review_ratings per review, and then calculate the average of reviews total ratings and see if it is higher than a specific int.
Exemple:
Apointment id : 1, has 2 Reviews, each review has review_rating one of 4 and 3 and review rating number 2 of 5 and 5.
AVG(review_ratings) should return the average between average(5,5) and average(4,3) which is 3.5 then AVG(the_first_avg) should return average (3.5,5) which is 4.25.
My last tested query is :
if($minRating > 0){
$search->join('appointments','appointments.doctor_id','users.id')
->join('reviews','reviews.appointment_id','appointments.id')
->join('review_ratings','review_ratings.review_id','reviews.id')
->addSelect(array('review_ratings.id',
DB::raw('AVG(review_ratings.rating) as review_ratings_count')
))
->addSelect(
DB::raw('AVG(review_ratings_count) as ratings_avg')
)
->where('ratings_avg','>=',$minRating)
;
}
The error I'm getting is
Column not found: 1054 Unknown column 'review_ratings_count' in 'field
list'
I found the solution to be much simpler that my code, and using AVG(name_of_column) rather than using the 'AS' value
->selectRaw('AVG(review_ratings.rating) as overall_rating')
->when($minRating > 0, function ($query) use($minRating)
{
$query->havingRaw('AVG(review_ratings.rating) >= ?', [$minRating]);
})

How to edit this mySQL so as to combine the rows of the same id into 1 instead of 3?

The following mySQL query gets data from 2 tables, alerts_data and alerts_list. The first table has the data of an alert, and the second has the description of the alert. So in the alerts_data there are multiple rows with the same alerts_data_id that is the same with the alerts_id of alerts_list.
What i want to achieve, is to display something like this
alert number 51, 5 clicked , 2 closed
alert number 57, 13 clicked, 3 closed, 8 waiting
using mySQL or PHP (i do not know if i can get this through plain mySQL)
So for now with my knowledge I can not display the data of alert 51 in one row, but because of the different alerts_data_status i have to show 3 rows for each.
How can I do it as above?
SELECT COUNT( alerts_data_id ) AS total, alerts_data_id, alerts_data_status, alerts_list.alerts_title
FROM alerts_data
JOIN alerts_list ON
alerts_data.alerts_data_id = alerts_list.alerts_id
GROUP BY alerts_data_id, alerts_data_status
//output
total - alerts_data_id - alerts_data_status - alerts_title
5 - 51 - clicked - alert number 51
2 - 52 - closed - alert number 51
13 - 57 - clicked - alert number 57
3 - 57 - waiting - alert number 57
8 - 57 waiting - alert number 57
Note: the alerts number are just examples, it can be any number
// alert_data
id - alerts_data_id - alerts_data_status
// alerts_list
alerts_id - alerts_name - alerts_text
Here's a sqlfiddle: http://sqlfiddle.com/#!9/c70c2/1
This may be an application for GROUP_CONCAT().
You first want a summary of your alerts by alerts_data_id and alerts_data_status. This is a little complex, because your sqlfiddle has a whole bunch of empty alerts_data_status strings. Here, I'm replacing those empty strings with `?'. (http://sqlfiddle.com/#!9/c70c2/23/0)
SELECT COUNT(*) AS alerts_count,
alerts_data_id,
CASE WHEN LENGTH(alerts_data_status) = 0 THEN '?'
ELSE alerts_data_status END AS alerts_data_status
FROM alerts_data
GROUP BY alerts_data_id, alerts_data_status
You then want to roll that up inside another query
SELECT SUM(a.alerts_count) total,
a.alerts_data_id, b. alerts_name,
GROUP_CONCAT( CONCAT(a.alerts_count, ': ', a.alerts_data_status)
ORDER BY a.alerts_data_status
SEPARATOR "; " ) detail
FROM (
SELECT COUNT(*) AS alerts_count,
alerts_data_id,
CASE WHEN LENGTH(alerts_data_status) = 0 THEN '?'
ELSE alerts_data_status END AS alerts_data_status
FROM alerts_data
GROUP BY alerts_data_id, alerts_data_status
) a
JOIN alerts_list b ON a.alerts_data_id = b.alerts_id
GROUP BY a.alerts_data_id, b.alerts_name
This will give you one row for each distinct alerts_data_id. Each alert is identified by its count, its id, and its name. (http://sqlfiddle.com/#!9/c70c2/26/0)
Then the row will contain a semicolon-separated list of the counts of the different alert status.
If i understand you well i think here is what you need;
SELECT
COUNT( alerts_data.id ) AS total,
ad.id,
(SELECT count(*) from alert_list al where al.alerts_id=ad.id and alerts_data_status='clicked') as clicked,
(SELECT count(*) from alert_list al where al.alerts_id=ad.id and alerts_data_status='closed') as closed,
(SELECT count(*) from alert_list al where al.alerts_id=ad.id and alerts_data_status='waiting') as waiting,
FROM alerts_data ad
GROUP BY ad.id
Was checking other answers and your SQLFIDDLE and thought this might be a nicer approach:
SELECT alerts_list.alerts_title,
SUM(CASE WHEN alerts_data_status = 'clicked' THEN 1 ELSE 0 END) AS clicked,
SUM(CASE WHEN alerts_data_status = 'closed' THEN 1 ELSE 0 END) AS closed,
SUM(CASE WHEN alerts_data_status = 'waiting' THEN 1 ELSE 0 END) AS waiting
FROM alerts_data
JOIN alerts_list ON alerts_data.alerts_data_id = alerts_list.alerts_id
GROUP BY alerts_list.alerts_title

Self Join in laravel

I have read some same question but i don't get any solution. I have one table name "Category". I have three column id, parent_id, name. I want to display records with parent_id name.
Right now records are displaying like....
id parent_id name
1 0 Mobile
2 0 TV
3 1 Samsung
But I want...
id parent_id name
1 0 Mobile
2 0 TV
3 Mobile Samsung
I tried this but it display error Syntax error or access violation: 1066 Not unique table/alias: 'category'
DB::table('category')->join('category','category.id','=','category.parent_id')->where('category.parent_id','>',0)->get();
I have solved my proble by this query.....
$sql = "select category1.name as name1, category2.name as name2, category1.id,category1.parent_id";
$sql .= " from category as category1 left join category as category2 on category1.id=category2.parent_id where category2.parent_id >0";
return DB::select($sql);
Try following query
$result = DB::table('category as c1')
->leftJoin('category as c2','c1.id', '=', 'c2.parent_id')
->where('c1.parent_id','>',0)->get();
Try Below Query
$result = DB::table('category as c1')
->join('category as c2','c1.id', '=', 'c2.parent_id')
select(
'id',
'name',
DB::raw('( case when c1.parent_id > 0 then c2.name ELSE c1.parent_id End ) as "parent_id"'),
->get();

Find related posts and rank them by relevance

I have a website with different articles. The database structure is like this:
ArticleId | ArticleLocation | ArticleCategory | ArticleTopic
The actual text strings for the columns are in another table so all the columns are populated with numbers (integers)
I want to find related posts, meaning if a user reads an article with ArticleLocation = 1, ArticleCategory= 3 and ArticleTopic = 2, then I want to find top 5 of articles sharing the most of the same column values.
Any ideas of how to do this?
Thanks in advance
Assuming the table is named ArticleTable, try the following:
SELECT *, (
CASE ArticleLocation WHEN :loc THEN 1 ELSE 0 END +
CASE ArticleTopic WHEN :topic THEN 1 ELSE 0 END +
CASE ArticleCategory WHEN :cat THEN 1 ELSE 0 END) AS Relevance
FROM ArticleTable
ORDER BY Relevance DESC
LIMIT 5
with :loc, :topic and :cat set to the relevant values.

Categories