How to use COALESCE in Laravel? - php

Is it possible to use COALESCE in laravel query?
$multipleitems = DB::table('votes')
->select('masterItemId',DB::raw('sum(votes) as voteSum'))
->whereBetween('voteDate',array($startDate,$endDate))
->where($condition)
->groupBy('masterItemId')
->get();
In the above code I want to get each item and its total votes. If there is no vote I want to get '0'.
But in the above code it returns items that have atleast one vote. Is there any method to get this done in laravel?

Well, the OP was not too helpful, but I will give it a shot! I assume, that the votes table contains actual votes cast by users on the items. This means, that if an item did not receive any vote, then that item id (masterItemId) does not exist in the votes table.
This means that the votes table has to be left joined on the main items table on the masterItemId field. I will call the main items table: items, and assume that it has an itemId field that matches the masterItemId field in the votes table. In SQL terms:
select items.itemId, ifnull(sum(votes.votes),0) as votesSum
from items left join votes on items.itemId=votes.masterItemId
where votes.voteDate between ... and ... and <other conditions>
group by items.itemId
I'm not familiar with Laravel, but you will need something like this, however do not treat is as copy-paste code:
$multipleitems = DB::table('items')
->leftJoin('votes','items.itemId','=','votes.masterItemId')
->select('items.itemId',DB::raw('ifnull(sum(votes.votes),0) as voteSum'))
->whereBetween('votes.voteDate',array($startDate,$endDate))
->where($condition)
->groupBy('items.temId')
->get();

Related

How can I order query by another table?

I have a long query, but I keep it simplified:
$query = $this->db->query(" SELECT id FROM oc_products ORDER BY ...? ");
Here is the problem. In this table I have all the products, but I have a second table, oc_seller_products, where I have same column ID, which match with oc_products.
I want to be ordered first id's which dont appear in oc_seller_products, and at last, appear id's which appear in oc_seller_products.
For example: in oc_products I have ID: 1,2,3,4,5
In oc_seller_products I have only ID: 4
I need to be ordered like this: 5,3,2,1 and the last: 4
I have a marketplace, so I want first my products to appear, on category page, then my sellers products.
I really have no idea how to do that.
select op.id
from oc_products op
left join oc_seller_products osp using (id)
order by osp.id is null desc, op.id desc
osp.id is null will be 1 when there is not an oc_seller_products record and 0 when there is, so sort by that, descending, first. And then after that, within those two categories, you seem to want descending id order.

Showing users who liked an item in an item list

This is an issue that I've deemed impractical to implement but I would like to get some feedback to confirm.
I have a product and users database, where users can like products, the like data is stored in a reference table with just pid and uid.
The client request is to show 3 users who have liked every product in the product listing.
The problem is, its not possible to get this data in one query for the product listing,
How I once implemented and subsequently un-implemented it was to perform a request for the users who have liked the products during the loop through the product list.
ie.
foreach($prods as $row):
$likers = $this->model->get_likers($row->id);
endforeach;
That works, but obviously results in not only super slow product listings, and also creates a big strain on the database/cpu.
The final solution that was implemented was to only show the latest user who has liked it (this can be gotten from a join in the products list query) and have a link showing how many people have liked, and upon clicking on it, opens a ajax list of likers.
So my question is, is there actually a technique to show likers on the product list, or is it simply not possible to execute practically? I notice actually for most social media sites, they do not show all likers on the listings, and do employ the 'click to see likers' method. However, they do show comments per items on the listing, and this is actually involves the same problem doesn't it?
Edit: mock up attached on the desired outcome. there would be 30 products per page.
By reading your comment reply to Alex.Ritna ,yes you can get the x no. of results with per group ,using GROUP_CONCAT() and the SUBSTRING_INDEX() it will show the likers seperated by comma or whatever separator you specified in the query (i have used ||).ORDER BY clause can be used in group_concat function.As there is no schema information is available so i assume you have one product table one user table and a junction table that maintains the relation of user and product.In the substring function i have used x=3
SELECT p.*,
COUNT(*) total_likes,
SUBSTRING_INDEX(
GROUP_CONCAT( CONCAT(u.firstname,' ',u.lastname) ORDER BY some_column DESC SEPARATOR '||'),
'||',3) x_no_of_likers
FROM product p
LEFT JOIN junction_table jt ON(p.id=jt.product_id)
INNER JOIN users u ON(u.id=jt.user_id)
GROUP BY p.id
Fiddle
Now at your application level you just have to loop through the products and split the x_no_of_likers by separator you the likers per product
foreach($prods as $row):
$likers=explode('||',$row['x_no_of_likers']);
$total_likes= $row['total_likes'];
foreach($likers as $user):
....
endforeach;
endforeach;
Note there is a default 1024 character limit set on GROUP_CONCAT() but you can also increase it by following the GROUP_CONCAT() manual
Edit from comments This is another way how to get n results per group, from this you can get all the fields from your user table i have used some variables to get the rank for product group ,used subquery for junction_table to get the rank and in outer select i have filtered records with this rank using HAVING jt.user_rank <=3 so it will give three users records per product ,i have also used subquery for products (SELECT * FROM product LIMIT 30 ) so the first 30 groups will have 3 results for each,for below query limit cannot be used at the end so i have used in the subquery
SELECT p.id,p.title,u.firstname,u.lastname,u.thumbnail,jt.user_rank
FROM
(SELECT * FROM `product` LIMIT 30 ) p
LEFT JOIN
( SELECT j.*,
#current_rank:= CASE WHEN #current_rank = product_id THEN #user_rank:=#user_rank +1 ELSE #user_rank:=1 END user_rank,
#current_rank:=product_id
FROM `junction_table` j ,
(SELECT #user_rank:=0,#current_rank:=0) r
ORDER BY product_id
) jt ON(jt.product_id = p.id)
LEFT JOIN `users` u ON (jt.`user_id` = u.`id`)
HAVING jt.user_rank <=3
ORDER BY p.id
Fiddle n results per group
You should be able to get a list of all users that have liked all products with this sql.
select uid,
count(pid) as liked_products
from product_user
group by uid
having liked_products = (select count(1) from products);
But as data grows this query gets slow. Better then to maintain a table with like counts that is maintained through a trigger or separately. On every like/dislike the counter is updated. This makes it easy to show the number of likes for each product. Then if the actual users that liked that product is wanted do a separate call (on user interaction) that fetches the specific likes for one product). Don't do this for all products on a page until actually requested.
I am assuming the size of both these tables is non-trivially large. You should create a new table (say LastThreeLikes), where the columns would be pid,uid_1,uid_2 and uid_3, indexed by pid. Also, add a column to your product table called numLikes.
For each "like" that you enter into your reference table, create a trigger that also populates this LastThreeLikes table if the numLikes is less than 3. You can choose to randomly update one of the values anyway if you want to show new users once in a while.
While displaying a product, simply fetch the uids from this table and display them back.
Note that you also need to maintain a trigger for the "Unlike" action (if there is any) to re-populate the LastThreeLikes table with a new user id.
Problem
The problem is the volume of data. From the point of view that you need two integer value as a answer you should forget about building a heavy query from your n<->n relations table.
Solution
Generates a storable representation using the file_put_contents() with append option each time a user likes a product. I don't have enough room to write the class in here.
public function export($file);
3D array format
array[product][line][user]
Example:
$likes[1293][1][456]=1;
$likes[82][2][656]=1;
$likes[65][3][456]=1;
.
.
.
Number of users who like this particular product:
$number_users_like_this_product = count($likes[$idProduct]);
All idUser who like this particular product:
$users_like_this_product = count($likes[$idProduct][$n]);
All likes
$all_likes = count($likes);
Deleting a like
This loop will unset the only line where $idProduct and $IdUser you want. Since all the variables are unsigned integer it is very fast.
for($n=1, $n <= count($likes[$idProduct]), $n++)
{
unset($likes[$idProduct][$n][$idUser]);
}
Conclusion
Get all likes will be easy as:
include('likes.php');
P.S If you want to give a try i will be glad to optimize my stuff and share it. I've created the class in 2012.

php mysql inner join - get a particular row

This is my query to get model information from one table and a single picture from another table. What changes do I have to make to this query in order for it to get the picture where ORDER BY sort DESC? In the table of the pictures, there is a field by the name "sort". The default value for the field for each row is 0. But one random row has the value of 1. I want to get that particular row. I don't, however, want to use WHERE sort=1 because then even in the case where no row has the sort value 1, one row should still get fetched.
$sql="SELECT tc.id,tc.alias,tc.firstname,tci.imagename
FROM ".$pre."models tc
INNER JOIN ".$pre."model_images tci ON tc.id=tci.userid
WHERE activated=1 AND sex=$sex AND city=$city AND published=1
GROUP BY tc.id ORDER BY firstname ASC";
Thank you in advance!
Solved using:
SELECT tc.id,tc.alias,tc.firstname,
(SELECT imagename FROM ".$pre."model_images WHERE userid= tc.id
ORDER BY sort DESC LIMIT 1) AS imagename
FROM ".$pre."models tc
WHERE tc.activated=1 AND tc.sex=1 AND tc.city=2 AND tc.published=1
ORDER BY tc.firstname ASC
You should place that in your WHERE clause aswell. One t hing to note though is to be carefull with the way you're using the column names. It's better to tell to which table they belong.
So this:
WHERE activated=1 AND sex=$sex AND city=$city AND published=1
Should be:
WHERE tc.activated=1 AND tc.sex=$sex AND tc.city=$city AND tc.published=1
And then simply add the 'sort' column to it:
WHERE tc.activated=1 AND tc.sex=$sex AND tc.city=$city AND tc.published=1 AND tci.sort=1
If no results are returned, then make sure that there are records that meet the required conditions. Because there's nothing wrong with the query. Try to print your query to the screen etc. to see if every variables has a value.
edit:
You should lose the GROUP BY.
SELECT tc.id,tc.alias,tc.firstname,tci.imagename
FROM ".$pre."models tc
INNER JOIN ".$pre."model_images tci ON tc.id=tci.userid
WHERE tc.activated=1 AND tc.sex=$sex AND tc.city=$city AND tc.published=1 AND tci.sort=1

Getting SQL result as nested array in PHP

I've a ('courses') table that has a HABTM relationship with ('instructors') table through another table...
I want to get the data of an instructor with all related courses in one query..
Currently, I have the following SQL:
SELECT *
FROM `instructors` AS `instructor`
LEFT JOIN `courses` AS `course`
ON `course`.`id` IN (
SELECT `course_id`
FROM `course_instructors`
WHERE `course_instructors`.`instructor_id` = `instructor`.`id`
)
WHERE `instructor`.`id` = 1
This SQL does what it should be doing, the only "problem" I have is that I get multiple rows for each joined rows.
My question is:
Can I get the result I want in one query? Or do I have to manipulate the data in PHP?
I'm using PHP and MySQL.
Each record of a query result set has the same format: same number of fields, same fields, same order of fields. You cannot change that.
SELECT *
FROM instructors AS instructor
LEFT JOIN
course_instructors
ON
instructor.id= course_instructors.instructor_id
LEFT JOIN
courses
ON
course_instructors.course_id = course.id
WHERE instructor.id = 1
This assumes the PK of course_instructors is (instructor_id,course_id)
Explanation of query:
First join + WHERE make sure you get the relevant instructor
Second join matches ALL the entries from the course_instructor table that belongs to this instructor. If none found, will return one row with NULL in all fields
Last join matches all relevant courses from the entries found from course_instructor If none would will return one record with NULL in all fields.
Again: important to use the right constraints to avoid duplicate data.
That's the nature of relational databases. You need to get the instructor first and then get the related courses. That's how I would do it and that's how I've been doing it. I'm not sure if there is a "hack" to it.

Compare database value to number of rows in another table

For each item in the first table, there is a 'numberOf' field. The value of this field must have an identical number of rows in a related table. These are like reservation rows so multiple users can book the item at the same time. This syncronisation sometimes goes out and there are more rows than the 'numberOf' field variable, and vice versa.
So I want to display a table that outputs the 'numberOf' from the first table, and the amount of rows that correspond to it from the other table. They are linked by the Item ID. Hope this isn't too confusing. The query is output with a do while loop. Here is the query I have so far anyway:
$querySync = sprintf("SELECT
COUNT(reserve_id), item_id, details, numberOf
FROM
reservations
JOIN
items ON item_id = itemID_reserved
WHERE
itemID_reserved = 1 ");
So at the moment it counts the number of rows in the reservations table. It then joins the items table so I can display the description and numberOf etc. Of course at the moment it only outputs the item with ID 1. But I can't seem to get it to go though each item, check its numberOf, and compare it to the number of rows in reservations table.
The idea is to have it all on one column and at the end of the row print if it is out of sync etc. I then need to rebuild the rows in the reservations table to match the numberOf.
Sorry thats a long one!
SELECT COUNT(reserve_id), item_id, details, numberOf,
COUNT(reserve_id) > numberOf AS overbook
FROM items
LEFT JOIN
reservations
ON itemID_reserved = item_id
GROUP BY
item_id
It might be easier to just directly calculate which items are "out of sync":
select i.item_id
from reservations r JOIN items i on (i.item_id = r.itemID_reserved)
group by i.item_id
having count(r.itemID_reserved) > i.numberOf
I'm making some assumptions there about which tables have which fields, but it should be sufficiently illustrative.

Categories