Laravel get n results per group - php

I have two tables : tbl_properties and tbl_property_images. I need to select latest 3 images of each property along with property_id.
I tried with sub query with limit
DB::table('properties as p')
->leftjoin(DB::raw("(select property_id, property_image_id, image
from tbl_property_image
where property_image_status = 1
group by property_id
having count(*) = 3) as tbl_imgtemp") , 'imgtemp.property_id', '=', 'p.property_id')
->where('property_status',1)
->get();
This returns total 3 records. please help me how can i do that.
How can I select 3 images per property?

You use following query to get 3 images per property, I have used id column of images table to pick latest images assuming id column is set as auto increment
SELECT p.*,i.*
FROM properties p
JOIN(SELECT i1.property_id,i1.property_image_status,i1.image
FROM tbl_property_image i1
LEFT OUTER JOIN tbl_property_image i2
ON (i1.property_id = i2.property_id AND i1.property_image_id < i2.property_image_id)
WHERE i1.property_image_status = 1
GROUP BY i1.property_id,i1.property_image_status,i1.image
HAVING COUNT(*) < 3
) i
ON p.property_id = i.property_id
DEMO
Another approach using group_concat() if you need only a single column from images table
SELECT p.property_id , p.title,SUBSTRING_INDEX(GROUP_CONCAT(i.image ORDER BY i.property_image_id DESC),',',3) images
FROM properties p
JOIN tbl_property_image i ON p.property_id = i.property_id
WHERE i.property_image_status = 1
GROUP BY p.property_id , p.title
But this solution has some limitations, As per docs The result is truncated to the maximum length that is given by the group_concat_max_len system variable, which has a default value of 1024. The value can be set higher, although the effective maximum length of the return value is constrained by the value of max_allowed_packet
DEMO
I have used sample schema and data set for above queries you have to adjust these as per your needs

Related

Efficient comment system pagination query

So I've been looking around the web about any information about pagination.
From what I've seen there are 3 kinds, (LIMIT, OFFSET) a, (WHERE id > :num ORDER BY id LIMIT 10) b and (cursor pagination) c like those used on facebook and twitter.
I decided that for my project I'll go with the "b" option as it looks pretty straightforward and efficient.
I'm trying to create some kind of "facebook" like post and comment system, but not as complex.
I have a ranking system for the posts and comments and top 2 comments for each post that are fetched with the post.
The rest of the comments for each specific post are being fetched when people click on to see more comments.
This is a query for post comments:
SELECT
c.commentID,
c.externalPostID,
c.numOfLikes,
c.createdAt,
c.customerID,
c.numOfComments,
(CASE WHEN cl.customerID IS NULL THEN false ELSE true END) isLiked,
cc.text,
cu.reputation,
cu.firstName,
cu.lastName,
c.ranking
FROM
(SELECT *
FROM Comments
WHERE Comments.externalPostID = :externalPostID) c
LEFT JOIN CommentLikes cl ON cl.commentID = c.commentID AND cl.customerID = :customerID
INNER JOIN CommentContent cc ON cc.commentTextID = c.commentID
INNER JOIN Customers cu ON cu.customerID = c.customerID
ORDER BY c.weight DESC, c.createdAt ASC LIMIT 10 OFFSET 2
offset 2 is because there were 2 comments being fetched earlier as top 2 comments.
I'm looking for a way similar to this of seeking next 10 comments each time through the DB without going through all the rows like with LIMIT,OFFSET
The problem is that I have two columns that are sorting the results and I won't allow me to use this method:
SELECT * FROM Comments WHERE id > :lastId LIMIT :limit;
HUGE thanks for the helpers !
Solution So Far:
In order to to have an efficient pagination we need to have a single column with as much as possible unique values that make a sequence to help us sort the data and paginate through.
My example uses two columns to sort the data so it makes a problem.
What I did is combine time(asc sorting order) and weight of the comment(desc sorting order), weight is a total of how much that comment is being engaged by users.
I achieved it by getting the pure int number out of the DateTime format and dividing the number by the weight let's call the result,"ranking" .
this way a comment with a weight will always have a lower ranking ,than a comment without a weight.
DateTime after stripping is a 14 digit int ,so it shouldn't make a problem dividing it by another number.
So now we have one column that sorts the comments in a way that comments with engagement will be at the top and after that will come the older comments ,so on until the newly posted comments at the end.
Now we can use this high performance pagination method that scales well:
SELECT * FROM Comments WHERE ranking > :lastRanking ORDER BY ASC LIMIT :limit;
Ok i want to say about other way, in my opinion this very useful.
$rowCount = 10; //this is count of row that is fetched every time
$page = 1; //this is for calculating offset . you must increase only this value every time
$offset = ($page - 1) * $rowCount; //offset
SELECT
c.commentID,
c.externalPostID,
c.numOfLikes,
c.createdAt,
c.customerID,
c.numOfComments,
(CASE WHEN cl.customerID IS NULL THEN false ELSE true END) isLiked,
cc.text,
cu.reputation,
cu.firstName,
cu.lastName,
c.ranking
FROM
(SELECT *
FROM Comments
WHERE Comments.externalPostID = :externalPostID) c
LEFT JOIN CommentLikes cl ON cl.commentID = c.commentID AND cl.customerID = :customerID
INNER JOIN CommentContent cc ON cc.commentTextID = c.commentID
INNER JOIN Customers cu ON cu.customerID = c.customerID
ORDER BY c.ranking DESC, c.createdAt ASC LIMIT $rowCount OFFSET $offset
There can be an error because i didn't check it , please don't make it matter

How to calculate difference between values coming from the same row in mysql

I am trying to calculate the difference of values list coming from a database.
I would like to achieve it using php or mysql, but I do not know how to proceed.
I have a table named player_scores. One of its rows contains the goals scored.
Ex.
pl_date pl_scores
03/11/2014 18
02/11/2014 15
01/11/2014 10
I would like to echo the difference between the goals scored during the matches played in different dates.
Ex:
pl_date pl_scores diff
03/11/2014 18 +3
02/11/2014 15 +5
01/11/2014 10 no diff
How can I obtain the desired result?
You seem to want to compare a score against the score on a previous row.
Possibly simplest if done using a a sub query that gets the max pl_date that is less than the pl_date for the current row, then joining the results of that sub query back against the player_scores table to get the details for each date:-
SELECT ps1.pl_date, ps1.pl_scores, IF(ps2.pl_date IS NULL OR ps1.pl_scores = ps1.pl_scores, 'no diff', ps1.pl_scores - ps1.pl_scores) AS diff
FROM
(
SELECT ps1.pl_date, MAX(ps2.pl_date) prev_date
FROM player_scores ps1
LEFT OUTER JOIN player_scores ps2
ON ps1.pl_date > ps2.pl_date
GROUP BY ps1.pl_date
) sub0
INNER JOIN player_scores ps1
ON sub0.pl_date = ps1.pl_date
LEFT OUTER JOIN player_scores ps2
ON sub0.prev_date = ps2.pl_date
There are potentially other ways to do this (for example, using variables to work through the results of an ordered sub query, comparing each row with the value stored in the variable for the previous row)
SELECT score FROM TABLE WHERE DATE = TheDateYouWant
$score = $data['score'];
SELECT score FROM TABLE WHERE date = dateYouWant
$difference = $score - $data['score'];
Something like this?
You could use two queries, one to get the value to use in the comparison (in the example below is the smaller number of scores) and the second one to get the records with a dedicated column with the difference:
SELECT MIN(pl_scores);
SELECT pl_date, pl_scores, (pl_scores - minScore) as diff FROM player_scores;
Or, using a transaction (one query execution php side):
START TRANSACTION;
SELECT MIN(Importo) FROM Transazione INTO #min;
SELECT Importo, (Importo - #min) as diff FROM Transazione;
select *,
coalesce(
(SELECT concat(IF(t1.pl_scores>t2.pl_scores,'+',''),(t1.pl_scores-t2.pl_scores))
FROM tableX t2 WHERE t2.pl_date<t1.pl_date ORDER BY t2.pl_date DESC LIMIT 1)
, 'no data' ) as diff
FROM tableX t1
WHERE 1
order by t1.pl_date DESC

SQL command to get the count from an average (ie what is n)

Two tables:
1. stories, one column lists over 10,000 story titles with other columns including author, date, category etc. 'id'is the column that is a unique identifyer (auto incrememnting for each story)
2. ratings. This table records star ranking for each of the stories. So this table, has 3 columns, a auto incrementing unique id, the id from table number 1 (which in table 2 is called storyidr)/, the rank value.
So i would like to report the average rating and the total number of ratings for each story.
I've used sql JOIN and I can get the average rating to report fine.
SELECT s.*,
ROUND(AVG(r.rank),0)
AS avrank
FROM stories s
LEFT JOIN ratings
AS r
ON r.storyidr = s.id
GROUP BY s.id
ORDER BY RAND()
LIMIT 200;";
Getting the count is another story. i'm trying COUNT and UNION. Nothing is working. Is there a way to 'extract' the 'value of n' from the average value that is already being queried?
knowing that average=(sum/n)
I don't have to do it this way. If i could add additional SQL queries to the current one to get the count that would be just fine. I'm just not seeing how to add the count function to the current script?
With suggestions:
$query="SELECT s.*, COUNT(r.rank) AS rkct ROUND(AVG(r.rank),0) AS avrank FROM stories s LEFT JOIN ratings AS r ON r.storyidr = s.id GROUP BY s.id ORDER BY RAND() LIMIT 5;";$result=mysqli_query($connection,$query);
<?php while ($data = mysqli_fetch_assoc($result)):$id = $data['id'];$author = $data['author'];$email = $data['email'];$title = $data['title'];$img_link = $data['img_link'];$smaller_img = $data['smaller_img'];$story_link = $data['story_link'];$page_path = $data['page_path'];$tag_alt = $data['tag_alt'];$category = $data['category'];$avgrate = $data['avrank'];$rankcount = $data['rkct'];
The suggestions are giving me the same error: Warning: mysqli_fetch_assoc() expects parameter 1 to be mysqli_result, boolean given in intro.php on line 88.
this is line 88: $avgratep = "Avg rating: " . $avgrate . "/5";
seems like adding the count is making the avrank value nothing or non-numeric?
Just call the count function in the same way you call avg:
SELECT s.*,
ROUND(AVG(r.rank),0) AS avrank,
COUNT(*) AS countrank
FROM stories s
LEFT JOIN ratings
AS r
ON r.storyidr = s.id
GROUP BY s.id
ORDER BY RAND()
LIMIT 200;

sql join not displaying correctly

I'm currently trying to join two tables with a left join:
--portal--
id_portal (index)
id_venue
name_portal
--access--
id_access (index)
id_event
id_portal
id_tickets
scan_access
'access' contains a number of ticket types per portal for each event. I need to combine these to get the sum total of the scan_access column for each portal but include the portals that have 'null' scan_access to come up with '0'. To achieve this I've used a left join:
SELECT portal.name_portal, SUM(access.scan_access) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal
WHERE portal.id_venue = $venueId
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
which means I get the following:
Portal 1 - Null
Portal 2 - 40
Portal 3 - 33
Portal 4 - Null
but I have an issue when I need to also get the above result when taking into account the event (id_event) because when I use the following:
SELECT portal.name_portal, SUM(access.scan_access) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal
WHERE portal.id_venue = $venueId AND access.id_event = 20
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
I get:
Portal 2 - 40
Portal 3 - 33
which makes sense as those are the only two rows that have an id_event value. But how can I take this col into account without losing the other portals? also, is there a way in sql to make the 'null' a zero when returning a result? (I can fix the null after with php but wanted to see if it was possible)
By putting access.id_event = 20 in your WHERE clause, you turn your LEFT JOIN into an INNER JOIN. Move access.id_event = 20 into your join criteria to preserve your LEFT JOIN. As #echo_me mentioned, you can use COALESCE() to get rid of your zeroes. I'd put it around the SUM(), instead of inside.
SELECT portal.name_portal, COALESCE( SUM(access.scan_access), 0 ) AS total_scan
FROM portal LEFT JOIN access ON portal.id_portal = access.id_portal AND access.id_event = 20
WHERE portal.id_venue = $venueId
GROUP BY portal.id_portal
ORDER BY portal.id_portal ASC
to convert NULL to 0 use this
COALESCE(col, 0)
in your example it will be
SUM(COALESCE(access.scan_access, 0)) AS total_scan

MySQL: Is a subquery the right move?

I have several products each with several images.
I am trying to display 3 products with only 1 of their corresponding images.
With the code below i get 3 different products with only 1 image being successfully displayed.
If i take off the sub-query LIMIT 1 i get 3 of the same product just with different images.
Any help would be greatly appreciated.
SELECT pro.id,
pro.title AS product_title,
pro.price,
img.image,
img.title AS image_title,
FROM products AS pro
LEFT JOIN
(SELECT product_id,image_library_id
FROM images_products
LIMIT 1) AS ips
ON ips.product_id = pro.id
LEFT JOIN image_library AS img
ON img.id = ips.image_library_id
WHERE pro.status_id='1'
LIMIT 3
Your question-title mentions a "correlated subquery", but this actually isn't a correlated subquery; it's a regular (uncorrelated) subquery that you're joining to. So that subquery is performed first, before the join, and only returns one record from images_products at all. You'd therefore get at most one working image. I believe that the query you want is this:
SELECT pro.id,
pro.title AS product_title,
pro.price,
img.image,
img.title AS image_title
FROM products AS pro
LEFT
JOIN image_library AS img
ON img.id =
( SELECT image_library_id
FROM images_products AS ips
WHERE ips.product_id = pro.id
LIMIT 1
)
WHERE pro.status_id = '1'
LIMIT 3
;
using a true correlated subquery. (Tested.)

Categories