Mysql query subselect getting NULL - mysql

I'm trying to join over three tables and get the active plan of a vendor. It is possible, that the vendor had a lot of plans in the past, but the active on is that counts.
The whole query is bigger (counting items he has aso) and because of that i did it with a subselect, but for this example it should be enough.
I always get plantitle and planstatus of NULL. How can i fix this?
Query
SELECT v.title
, plans.title AS plantitle
, uplans.status AS planstatus
, uplans.uid
, COUNT(DISTINCT obs.id) AS obj_count
, sum(case when obs.published = -1 then 1 else 0 end) trash
, sum(case when obs.published = 1 then 1 else 0 end) published
, sum(case when obs.published = 0 then 1 else 0 end) unpublished
FROM `vendors` AS v
LEFT JOIN objects AS obs ON obs.vid = v.id
LEFT JOIN `userplans` AS uplans ON uplans.uid = (
SELECT up.id
FROM `userplans`AS up
WHERE up.uid=v.uid AND status = "ACTIVE" LIMIT 1)
LEFT JOIN `plans` AS plans ON plans.id=uplans.pid
GROUP BY v.id
ORDER BY v.id asc
Tables
Vendors
id, uid, title
10, 1, Name 1
20, 4, Name 2
30, 5, Name 3
Plans
id, title
40, Plan 1
50, Plan 2
Userplans
id, uid, pid, status
1, 1, 40, CANCELED
2, 1, 50, CANCELED
3, 1, 40, CANCELED
4, 4, 50, CANCELED
5, 4, 50, CANCELED
6, 4, 50, ACTIVE
7, 1, 40, ACTIVE

Lets get the object counts 1st as the associations to other tables may be 1-M which would result in larger counts. then join to the other needed information.
This still assumes that a the combination of a user and plan in userPlan can only have 1 active record. If it can have more than 1 I still need to know which active userPlan to select.
Also why the left joins? are you after all vendors regardless of plans and objects and userplans? Is it possible that a vendor HAS no active plans in which case the title would be null?
SELECT v.title
, P.title AS plantitle
, UP.status AS planstatus
, up.uid
, O.obj_count
, O.trash
, O.published
, O.unpublished
FROM vendors v
LEFT JOIN userplans UP
ON V.uid = UP.UID
AND UP.status = 'ACTIVE'
LEFT JOIN (SELECT obs.VID
,COUNT(DISTINCT obs.id) AS obj_count
,sum(case when obs.published = -1 then 1 else 0 end) trash
,sum(case when obs.published = 1 then 1 else 0 end) published
,sum(case when obs.published = 0 then 1 else 0 end) unpublished
FROM OBJECTS obs
GROUP BY obs.VID) O
ON O.vid = v.id
LEFT JOIN `plans` P
ON P.id=UP.pid
ORDER BY v.id asc
And to address the comment to get the "Latest" Plan regardless of status (assuming latest would have the highest ID in the userPlans table.
SELECT v.title
, P.title AS plantitle
, UP.status AS planstatus
, up.uid
, O.obj_count
, O.trash
, O.published
, O.unpublished
FROM vendors v
LEFT JOIN (SELECT * -- though really we should just pull in the columns needed.
FROM USERPLANS U1
INNER JOIN (SELECT max(ID) ID, PID, UID
FROM UserPlans
GROUP BY PID, UID) U2
on U1.ID = U2.ID) UP
ON V.uid = UP.UID
LEFT JOIN (SELECT obs.VID
,COUNT(DISTINCT obs.id) AS obj_count
,sum(case when obs.published = -1 then 1 else 0 end) trash
,sum(case when obs.published = 1 then 1 else 0 end) published
,sum(case when obs.published = 0 then 1 else 0 end) unpublished
FROM OBJECTS obs
GROUP BY obs.VID) O
ON O.vid = v.id
LEFT JOIN `plans` P
ON P.id=UP.pid
ORDER BY v.id asc

In your join of userplans - you are joining uplans.uid with the selected id from the same table - you need to join on the same column - change the line to :
LEFT JOIN `userplans` AS uplans ON uplans.id = (

Something like this might work:
SELECT Vendors.title, Plans.title, Userplans.status, Userplans.uid FROM Vendors, Plans, Userplans
WHERE Vendors.uid = Userplans.uid AND Plans.id = Userplans.pid AND Userplans.status = 'Active'
This assumes that you can only ever have one Active per user

Related

Count if avg is below/above X

I am trying to get the number of 'critics' and 'promoters' from average of ratings from a joined table on a specific group of questions
SELECT category
, SUM( IF( round(avg(items.value) ) <= 6, 1, 0) ) AS critics
, SUM( IF( round(avg(items.value) ) >= 9, 1, 0) ) AS promoters
FROM reviews
INNER JOIN items
ON reviews.id = items.review_id
AND items.question_id in (1, 2, 4)
GROUP BY category
However I get the error:
General error: 1111 Invalid use of group function
I think you should try with using having with it, something like below:
SELECT
category,
COUNT(items.id) AS critics
FROM reviews
INNER JOIN items ON reviews.id = items.review_id AND
items.question_id IN (1, 2, 4)
GROUP BY category
HAVING ROUND(AVG(items.value)) <= 6
First retrieve category wise rounded average value and then apply condition either it is critics and promoters.
-- MySQL
SELECT t.category
, CASE WHEN t.avg_value <= 6
THEN 1
ELSE 0
END critics
, CASE WHEN t.avg_value >= 9
THEN 1
ELSE 0
END promoters
FROM (SELECT category
, ROUND(AVG(items.value)) avg_value
FROM reviews
INNER JOIN items
ON reviews.id = items.review_id
AND items.question_id IN (1, 2, 4)
GROUP BY category) t
Please check this url for finding out pseudocode https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=2679b2be50c3059c73ab9754c612179c
First retrieve category and review_id wise rounded average value and then apply condition either it is critics and promoters.
SELECT t.category
, SUM(CASE WHEN t.avg_value <= 6
THEN 1
ELSE 0
END) critics
, SUM(CASE WHEN t.avg_value >= 9
THEN 1
ELSE 0
END) promoters
FROM (SELECT category
, items.review_id
, ROUND(AVG(items.value)) avg_value
FROM reviews
INNER JOIN items
ON reviews.id = items.review_id
AND items.question_id IN (1, 2, 4)
GROUP BY category
, items.review_id) t
GROUP BY t.category

MySQL - Slow Query when adding multiple derived tables - Optimization

For my query, the two derived tables at the bottom are causing a crazy slow up for this query. The query, as is, takes about 45-55 seconds to execute.. NOW, when i remove just one of those derived tables (it does not matter which one) the query goes down to 0.1 - 0.3 seconds. My questions; Is there an issue with having multiple derived tables? Is there a better way to execute this? My indexes all seem to be correct, I will also include the explain from this query.
select t.name as team, u.name as "REP NAME",
count(distinct activity.id) as "TOTAL VISITS",
count(distinct activity.account_id) as "UNIQUE VISITS",
count(distinct placement.id) as "COMMITMENTS ADDED",
CASE WHEN
count(distinct activity.account_id) = 0 THEN (count(distinct
placement.id) / 1)
else (cast(count(distinct placement.id) as decimal(10,2)) /
cast(count(distinct activity.account_id) as decimal(10,2)))
end as "UNIQUE VISIT TO COMMITMENT %",
case when o.mode='basic' then count(distinct placement.id) else
count(distinct(case when placement.commitmentstatus='fullfilled'
then placement.id else 0 end))
end as "COMMITMENTS FULFILLED",
case when o.mode='basic' then 1 else
(CASE WHEN
count(distinct placement.id) = 0 THEN (count(distinct(case when
placement.commitmentstatus='fullfilled' then placement.id else 0
end)) / 1)
else (cast(count(distinct(case when
placement.commitmentstatus='fullfilled' then placement.id else 0
end)) as decimal(10,2)) / cast(count(distinct placement.id) as
decimal(10,2)))
end) end as "COMMITMENT TO FULFILLMENT %"
from lpmysqldb.users u
left join lpmysqldb.teams t on t.team_id=u.team_id
left join lpmysqldb.organizations o on o.id=t.org_id
left join (select * from lpmysqldb.activity where
org_id='555b918ae4b07b6ac5050852' and completed_at>='2018-05-01' and
completed_at<='2018-06-01' and tag='visit' and accountname is not
null and (status='active' or status='true' or status='1')) as
activity on activity.user_id=u.id
left join (select * from lpmysqldb.placements where
orgid='555b918ae4b07b6ac5050852' and placementdate>='2018-05-01' and
placementdate<='2018-06-01' and (status IN ('1','active','true') or
status is null)) as placement on placement.userid=u.id
where u.org_id='555b918ae4b07b6ac5050852'
and (u.status='active' or u.status='true' or u.status='1')
and istestuser!='1'
group by u.org_id, t.name, u.id, u.name, o.mode
order by count(distinct activity.id) desc
Thank you for assistance!
I have edited below with changing the two bottom joins from joining on subqueries to joining on the table directly. Still yielding the same result.
This is a SLIGHTLY restructured query of your same. Might be simplified as the last two subqueries are all pre-aggregated for your respective counts and count distincts so you can use those column names directly instead of showing all the count( distinct ) embedded throughout the query.
I also tried to simplify the division by multiplying a given count by 1.00 to force decimal-based precision as result.
select
t.name as team,
u.name as "REP NAME",
Activity.DistIdCnt as "TOTAL VISITS",
Activity.UniqAccountCnt as "UNIQUE VISITS",
Placement.DistIdCnt as "COMMITMENTS ADDED",
Placement.DistIdCnt /
CASE WHEN Activity.UniqAccountCnt = 0
THEN 1.00
ELSE Activity.UniqAccountCnt * 1.00
end as "UNIQUE VISIT TO COMMITMENT %",
case when o.mode = 'basic'
then Placement.DistIdCnt
else Placement.DistFulfillCnt
end as "COMMITMENTS FULFILLED",
case when o.mode = 'basic'
then 1
else ( Placement.DistFulfillCnt /
CASE when Placement.DistIdCnt = 0
then 1.00
ELSE Placement.DistIdCnt * 1.00
END TRANSACTION )
END as "COMMITMENT TO FULFILLMENT %"
from
lpmysqldb.users u
left join lpmysqldb.teams t
on u.team_id = t.team_id
left join lpmysqldb.organizations o
on t.org_id = o.id
left join
( select
user_id,
count(*) as AllRecs,
count( distinct id ) DistIdCnt,
count( distinct account_id) as UniqAccountCnt
from
lpmysqldb.activity
where
org_id = '555b918ae4b07b6ac5050852'
and completed_at>='2018-05-01'
and completed_at<='2018-06-01'
and tag='visit'
and accountname is not null
and status IN ( '1', 'active', 'true')
group by
user_id ) activity
on u.id = activity.user_id
left join
( select
userid,
count(*) AllRecs,
count(distinct id) as DistIdCnt,
count(distinct( case when commitmentstatus = 'fullfilled'
then id
else 0 end )) DistFulfillCnt
from
lpmysqldb.placements
where
orgid = '555b918ae4b07b6ac5050852'
and placementdate >= '2018-05-01'
and placementdate <= '2018-06-01'
and ( status is null OR status IN ('1','active','true')
group by
userid ) as placement
on u.id = placement.userid
where
u.org_id = '555b918ae4b07b6ac5050852'
and u.status IN ( 'active', 'true', '1')
and istestuser != '1'
group by
u.org_id,
t.name,
u.id,
u.name,
o.mode
order by
activity.DistIdCnt desc
FINALLY, your inner queries are querying for ALL users. If you have a large count of users that are NOT active, you MIGHT exclude those users from each inner query by adding those join/criteria there too such as...
( ...
from
lpmysqldb.placements
JOIN lpmysqldb.users u2
on placements.userid = u2.id
and u2.status IN ( 'active', 'true', '1')
and u2.istestuser != '1'
where … ) as placement

Display MySQL Pricing Chart in Spreadsheet-like Columns

I'm trying to output a friendly price table in MySQL for export/import into a spreadsheet. Let's use fruits and their price breaks as an example.
Here's a fiddle for the schema I'm referring to:
http://sqlfiddle.com/#!9/c526e3/4
Simply:
Table: fruit
id
name
Table: fruit_pricing
id
fruit_id
min_quantity
max_quantity
price
When executing the query:
SELECT
F.name,
IF(FP.min_quantity = 1, FP.price, '0') as qty_1,
IF(FP.min_quantity = 10, FP.price, '0') as qty_10,
IF(FP.min_quantity = 25, FP.price, '0') as qty_25,
IF(FP.min_quantity = 50, FP.price, '0') as qty_50,
IF(FP.min_quantity = 100, FP.price, '0') as qty_100
FROM Fruit F
LEFT JOIN FruitPricing FP ON FP.fruit_id = F.id
It displays the results like this:
What I'd like to do is group the fruit names so there are only three rows: Apple, Grape, and Orange. Then, I'd like all the 0 values to be replaced with the appropriate quantities. I'm trying to get the same output as the spreadsheet in this screenshot:
Are there any nice tricks for accomplishing this? I'm unsure of the sql-tech-speak for this particular question, making it difficult to search for an answer. I'd be happy to update my question subject if I can and somebody has a better suggestion for it.
SELECT f.name
, SUM(CASE WHEN fp.min_quantity = 1 THEN fp.price ELSE 0 END) qty_1
, SUM(CASE WHEN fp.min_quantity = 10 THEN fp.price ELSE 0 END) qty_10
, SUM(CASE WHEN fp.min_quantity = 25 THEN fp.price ELSE 0 END) qty_25
, SUM(CASE WHEN fp.min_quantity = 50 THEN fp.price ELSE 0 END) qty_50
, SUM(CASE WHEN fp.min_quantity = 100 THEN fp.price ELSE 0 END) qty_100
FROM fruit f
LEFT
JOIN fruitpricing fp
ON fp.fruit_id = f.id
GROUP
BY name;
Although, if it was me, I'd probably just do the following, and handle any remaining display issues in the presentation layer...
SELECT f.name
, fp.min_quantity
, SUM(fp.price) qty
FROM fruit f
LEFT
JOIN fruitpricing fp
ON fp.fruit_id = f.id
GROUP
BY name
, min_quantity;

How to use user variable as counter with inner join queries that contains GROUP BY statement?

I have 2 tables odds and matches :
matches : has match_id and match_date
odds : has id, timestamp, result, odd_value, user_id, match_id
I had a query that get the following information from those tables for each user:
winnings : the winning bets for each user. (when odds.result = 1)
loses : the lost bets for each user.(when odds.result != 1)
points : the points of each user.(the sum of the odds.odd_value) for each user.
bonus : for each continuous 5 winnings i want to add extra bonus to this variable. (for each user)
How to calculate bonus?
I tried to use this query and I faced a problem : (you can check it here SQL Fiddle)
the calculated bonus are not right for all the users :
first user:(winnings:13, bonus=2).
second user:(winnings:8, bonus=2)bonus here should be 1.
third user:(winnings:14, bonus=3)bonus here should be 2.
why does the query not calculate the bonus correctly?
select d.user_id,
sum(case when d.result = 1 then 1 else 0 end) as winnings,
sum(case when d.result = 2 then 1 else 0 end) as loses,
sum(case when d.result = 1 then d.odd_value else 0 end) as points,
f.bonus
FROM odds d
INNER JOIN
(
SELECT
user_id,SUM(CASE WHEN F1=5 THEN 1 ELSE 0 END) AS bonus
FROM
(
SELECT
user_id,
CASE WHEN result=1 and #counter<5 THEN #counter:=#counter+1 WHEN result=1 and #counter=5 THEN #counter:=1 ELSE #counter:=0 END AS F1
FROM odds o
cross join (SELECT #counter:=0) AS t
INNER JOIN matches mc on mc.match_id = o.match_id
WHERE MONTH(STR_TO_DATE(mc.match_date, '%Y-%m-%d')) = 2 AND
YEAR(STR_TO_DATE(mc.match_date, '%Y-%m-%d')) = 2015 AND
(YEAR(o.timestamp)=2015 AND MONTH(o.timestamp) = 02)
) Temp
group by user_id
)as f on f.user_id = d.user_id
group by d.user_id
I am not sure how your result related to matches table,
you can add back WHERE / INNER JOIN clause if you need.
Here is link to fiddle
and the last iteration according to your comments:
And here is a query:
SET #user:=0;
select d.user_id,
sum(case when d.result = 1 then 1 else 0 end) as winnings,
sum(case when d.result = 2 then 1 else 0 end) as loses,
sum(case when d.result = 1 then d.odd_value else 0 end) as points,
f.bonus
FROM odds d
INNER JOIN
(
SELECT
user_id,SUM(bonus) AS bonus
FROM
(
SELECT
user_id,
CASE WHEN result=1 and #counter<5 AND #user=user_id THEN #counter:=#counter+1
WHEN result=1 and #counter=5 AND #user=user_id THEN #counter:=1
WHEN result=1 and #user<>user_id THEN #counter:=1
ELSE
#counter:=0
END AS F1,
#user:=user_id,
CASE WHEN #counter=5 THEN 1 ELSE 0 END AS bonus
FROM odds o
ORDER BY user_id , match_id
) Temp
group by user_id
)as f on f.user_id = d.user_id
group by d.user_id

mySQL - Limit the number of rows returned in one side of JOIN statement?

Everyone,
I am just curious if there is a way to do this sort of limiting with a query on a mySQL database:
Here are my tables:
Events
event_id event_title creation_time
Images
image_id src event_id
Comments
event_comment_id event_comment event_id
I would like to fetch events sorted by creation time, and get only 3 images and 3 comments for each event.
Any help, resources, or criticism is welcome. Thank you
Here's one approach. Basically, get the rownumber associated with each group of comments/images and only display up to 3:
SELECT E.*,
MAX(CASE WHEN I.rn = 1 THEN I.Image_Id END) Image1,
MAX(CASE WHEN I.rn = 2 THEN I.Image_Id END) Image2,
MAX(CASE WHEN I.rn = 3 THEN I.Image_Id END) Image3,
MAX(CASE WHEN C.rn = 1 THEN C.event_comment_id END) Comment1,
MAX(CASE WHEN C.rn = 2 THEN C.event_comment_id END) Comment2,
MAX(CASE WHEN C.rn = 3 THEN C.event_comment_id END) Comment3
FROM Events E
LEFT JOIN (SELECT #curRow:=IF(#prevRow = event_id, #curRow + 1, 1) rn,
Image_Id, src, event_id, #prevRow:= event_id
FROM Images
JOIN (SELECT #curRow := 0) r
) I ON E.event_id = I.Event_id
LEFT JOIN (SELECT #curRow2:=IF(#prevRow2 = event_id, #curRow2 + 1, 1) rn,
event_comment_id, event_comment, event_id, #prevRow2:= event_id
FROM Comments
JOIN (SELECT #curRow2 := 0) r
) C ON E.event_id = C.Event_id
GROUP BY E.Event_Id
ORDER BY E.Event_Id, E.creation_time DESC
And here is the SQL Fiddle.