How to write SQL statement for this case? - php

I have 3 tables as below:
tbl_campaign_name
tbl_backer
payment_cellcard
I want to select all the campaign name from tbl_campaign_name, but with the combination of tbl_backer and payment_cellcard.
How should the combination look like?
Well, there are fields as amount in tbl_backer, but it has a status field also, which i consider 1 as approved, 0 as unapproved. payment_cellcard has no status, which means it is always approved.
The problem:
I want to select the campaign name from tbl_campaign_name to list them down in one list, with the amount of money that have been approved from tbl_backer and sum with payment_cellcard which is always approved.
below is my query, which it sum up the amount even the status from tbl_backer is not approved yet.
SELECT
tbl_campaign_name.camp_id AS camp_id,
tbl_campaign_name.is_featured AS is_featured,
SUM(ifnull(payment_cellcard.payment_cellcard_amount, 0))
AS cellcard,
SUM( ifnull(tbl_backer.total_amount, 0))
AS backed_amount,
(SUM( ifnull(payment_cellcard.payment_cellcard_amount, 0)) +
SUM( ifnull(tbl_backer.total_amount, 0)))
AS total_donation
FROM ((tbl_campaign_name
LEFT JOIN tbl_backer
ON ((tbl_campaign_name.camp_id = tbl_backer.camp_id)))
LEFT JOIN payment_cellcard
ON ((tbl_campaign_name.camp_id = payment_cellcard.camp_id)))
WHERE (tbl_campaign_name.is_featured = 1)
GROUP BY tbl_campaign_name.camp_id
ORDER BY (SUM( ifnull(payment_cellcard.payment_cellcard_amount, 0)) + SUM(ifnull(tos_backer.total_amount, 0))) DESC,
tbl_campaign_name.camp_id
The result:
I got all the project listed as I want, but it sum up even the
tbl_backer.backer_status = 0
I hope this is clear explanation of what I want.
Thanks,

Related

SQL return results for Table A, based on criteria from Table B

I have 2 tables which share a 1 to many relationship. Assume the following structure:
users users_metadata
------------- -------------
id | email id | user_id | type | score
A user can have many metadata. The users table has 100k rows, the users_metadata table has 300k rows. It'll likely grow 10x so whatever I write needs to be optimal for large amounts of data.
I need to write a sql statement that returns only user emails that pass a couple of different score conditions found in the metadata table.
// if type = 1 and if score > 75 then <1 point> else <0 points>
// if type = 2 and if score > 100 then <1 point> else <0 points>
// if type = 3 and if score > 0 then [-10 points] else <0 points>
// there are other types that we want to ignore in the score calculations
If the user passes a threshold (e.g. >= 1 point) then I want that user to be in the resultset, otherwise I want the user to be ignored.
I have tried user a stored function/cursor that takes a user_id and loops over the metadata to figure out the points, but the resulting execution was very slow (although it did work).
As it stands I have this, and it takes about 1 to 3 seconds to execute.
SELECT u.id, u.email,
(
SELECT
SUM(
IF(k.type = 1, IF(k.score > 75, 1, 0), 0) +
IF(k.type = 2, IF(k.score > 100, 1, 0), 0) +
IF(k.type = 3, IF(k.score > 0, 1, -10), 0)
)
FROM user_metadata k WHERE k.user_id = u.id
) AS total
FROM users u GROUP BY u.id HAVING total IS NOT NULL;
I feel like at 10x this is going to be even slower. a 1 to 3 second query execution time is too slow for what I need already.
What would a more optimal approach be?
If I use a language like PHP for this too, would running 2 queries, one to fetch user_ids from user_metadata of only passing users, and then a second to SELECT WHERE IN on that list of ids be better?
Try using a JOIN instead of correlated subquery.
SELECT u.id, u.email, t.total
FROM users AS u
JOIN (
SELECT user_id, SUM(CASE type
WHEN 1 THEN score > 75
WHEN 2 THEN score > 100
WHEN 3 THEN IF(k.score > 0, 1, -10)
END) AS total
FROM user_metadata
GROUP BY user_id
HAVING total >= 1
) AS t ON u.id = t.user_id
Doing the grouping and filtering in the subquery makes the join smaller, which can be a significant performance boost.
There's also no need for you to use GROUP BY u.id in your query, since that's the primary key of the table you're querying; hopefully MySQL will optimize that out.

SQL - Get AVG value from other table

My question sounds really easy, but I'm stuck.
Sample Data:
Listing:
id title State
1 Hotel with nice view Arizona
2 Hotel to stay Arizona
Review:
id listing_id rating mail_approved
1 1 4(stars) 1
2 1 4(stars) 0
3 1 3(stars) 1
4 2 5(stars) 1
So now I get the AVG value of the listings, but I want to get only the value of each listing when the review is mail_approved = 1. But when there is none review or no review with mail_approved = 1 it should give me the listing back just with 0.0 review points. So I would like to get all listing back if they have a review just calculate the AVG of those reviews with mail_approved = 1
How can I do this?
Do I have to rewrite the whole query?
Here is my query:
SELECT
ls.id,
title,
state,
ROUND(AVG(rating),2) avg_rating
FROM listing ls
JOIN review rv
ON ls.id = rv.listing_id
WHERE ls.state = '$get_state'
GROUP BY ls.id,
title,
state
ORDER BY avg_rating DESC
You used join, which is short for inner join. This type of join only gives results if a matching record exists in both tables. Change it to left join (short for left outer join), to also include listings without reviews.
You will need to move the state check and any other check to the join condition too, otherwise those listings without review will be dropped from the result again.
Lastly, you can coalesce the average value to get 0 instead of null for those records.
SELECT
ls.id,
title,
state,
COALESCE(ROUND(AVG(rating),2), 0) avg_rating
FROM listing ls
LEFT JOIN review rv
ON ls.id = rv.listing_id
AND ls.state = '$get_state'
AND ls.mail_approved = 1
GROUP BY ls.id,
title,
state
ORDER BY avg_rating DESC
As a side note, please check prepared statements (for PDO or MySQLi) for the proper way to pass input parameters to your query instead of concatenating with variables like $get_state. Concatting is error prone, and makes you more vulnerable for SQL injection.
Outer join the avarage ratings to the hotels:
select
l.id,
l.title,
l.state,
coalesce(r.avg_rating, 0)
from listing l
left join
(
select
listing_id,
round(avg(rating), 2) as avg_rating
from review
where mail_approved = 1
group by listing_id
) r on r.listing_id = l.id
where l.state = '$get_state'
order by avg_rating desc;

Joins and correlated subquery

Cannot figure out query for situation where I want to display only customers with unverified order but do not include customers who already have at least one verified order. One customer can have more records in DB since for every order also new record in customers table is made so the only way how to track specific user is by customer_number.
My DB structure (simplified):
customers:
id | customer_number
orders:
id | customer_id | isVerified
I would probably need to combine join and correlated queries (to search records in customers table for every customer_number and check isVerified column for false) which in the end could be really slow especially for thousands of records.
I use Laravel so Eloquent ORM is available if this can make things easier.
(Second thought: Or maybe it would be faster and more efficient to rewrite that part to create only one user record for orders of specific user.)
Any ideas? Thank you
There are probably a few ways to do this, but you can achieve this result with a join, aggregation and conditional sum:
select a.customer_id,
sum( case when isVerified = 1 then 1 else 0 end ) as Num_Verified,
sum( case when isVerified = 0 then 1 else 0 end ) as Num_unVerified
from customers as a
left join
orders as b
on a.customer_id = b.customer_id
group by a.customer_id
having Num_Verified = 0
and Num_unVerified > 0
SQLfiddle here
You can achieve like this:
$customer_id = Customer::join('orders','customers.id','orders.cutomer_id')
->where('isVerified',true)
->select('orders.*')
->groupBy('customer_id')
->pluck('customer_id');
This will give customers with at least one verified order.
Now get customers with unverified orders as:
$customers = Customer::join('orders','customers.id','orders.customer_id')
->where('isVerified',false)
->whereNotIn('customer_id',$customer_id)
->select('customers.customer_number','orders.*')
->groupBy('customer_id')
->pluck('customer_id');
How about this one?
$customer_list = customers::where('customer_number',$customer_number)->with('orders',function($query){
$query->where('isVerified',0);
})->get();
One method is an aggregation query:
select c.customer_number
from customers c join
orders o
on c.customer_id = o.customer_id
group by c.customer_number
having sum(isVerified = 1) = 0 and
sum(isVerified = 0) > 0;
This structure assumes that isVerified is a number that takes on the values of 0 for false and 1 for true.

Sorting data from MySQL by SUM with Grouping By - not working properly

I have a problem with mysql - i'm kind new to it, byt looking to improve my skills :)
Have a code like this:
if($where > 0) $query = mysql_query("SELECT img.*, user.user as owner_name, cat.name as cat_name FROM tentego_img AS img LEFT JOIN tablicacms_users AS user ON user.id = img.owner LEFT JOIN tentego_img_cat AS cat ON cat.id = img.cat WHERE img.`is_waiting` LIKE ".$where.$cat." INNER JOIN tentego_img_vote ON tentego_img.id = tentego_img_vote.object_id GROUP BY tentego_img_vote.object_id ORDER BY SUM ( (CASE WHEN tentego_img_vote.vote = '0' THEN '-1' ELSE '1' END) ) DESC LIMIT ".$page.",".$objPerPage);
I need to make sorting by number of votes, sorted descending.
Still it makes results sorted by it own way.
In table I have rows:
ID - vote id for table purpose
object_id- id of object joined with another table to show results.
User ID - user id
Vote - where values are 0 for dislike and 1 for like (so -1 for 0, and +1 for 1)
So, as I understand i need to sum up all records for each of unique object_id, then sort by sum of vote values of each.
This code worked before my script provider decide to upgrade it, so right now i dont know how to fix it :(

Nested count() function with % percentage operation

I’m designing a program for my school to keep student attendance records. So far I have the following query working fine and now I would like to add an IF statement to perform a percentage operation when a certain condition is given. As it is, the query is using INNER JOIN to search for data from two different tables (oxadmain and stuattend) and it’s displaying the results well on a results table:
SELECT o.name
, o.year
, o.photoID
, o.thumbs
, s.ID
, s.studid
, s.date
, s.teacher
, s.subject
, s.attendance
FROM stuattend s
JOIN oxadmain o
ON s.studid = o.stuid
ORDER
BY name ASC
Now I would like to add an “if” statement that
1) finds when stuattend.attendance is = Absent, calculates the percentage of absences the students may have in any given period of time, and then stores that (%) value in “percentage” and
2) ELSE assigns the value of 100% to “Percentage”.
So far I’ve been trying with the following:
<?php $_GET['studentID'] = $_row_RepeatedRS['WADAstuattend']; ?>
SELECT oxadmain.name , oxadmain.year , oxadmain.photoID , oxadmain.thumbs , stuattend.ID , stuattend.studid , stuattend.date , stuattend.teacher, stuattend.subject , stuattend.attendance
CASE
WHEN stuattend.attendance = Absent THEN SELECT Count (studentID) AS ClassDays, (SELECT Count(*) FROM stuattend WHERE studentID = stuattend.studid AND Absent = 1) AS ClassAbsent, ROUND ((ClassAbsent/ClassDays)*100, 2) AS Percentage
ELSE
Percentage = 100
END
FROM stuattend INNER JOIN oxadmain ON stuattend.studid=oxadmain.stuid
ORDER BY name ASC
Any suggestions on how to do this well?
Thank you for your attention
The base idea would be:
select stuattend.studid, sum(stuattend.attendance = `absent`) / count(*)
from stuattend
group by stuaddend.studid;
This very much depends on exactly one entry per student and per day, and of course gets 0 if no absence and 1 if always absent.
To make this a bit more stable I would suggest to write a calendar day table, which simply keeps a list of all days and a column if this is a school day, so workday=1 means they should have been there and workday=0 means sunday or holiday. Then you could left join from this table to the presence and absence days, and even would give good results when presence is not contained in your table.
Just ask if you decide which way to go.

Categories