Display MySQL Pricing Chart in Spreadsheet-like Columns - mysql

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;

Related

Integrate calculated AVG into same query

I have some troubles and hope you will help me. I need to calculate how many contracts there are in which hcb.sum:
<= 10k
greater than 10k but < AVG
greater than AVG
In the first case, everything is simple, but the problem arises when I need to count the following two cases: "> 10 but < AVG" and "> AVG". I can't solve this problem. How to integrate the calculated AVG into this query. Hope you understand what i need. Perhaps you can help me. Thanks in advance. Select is below:
SELECT hcb.id AS 'ID'
,hcb.name AS 'Name'
,SUM(CASE WHEN hcb.kind = 'NEW' AND hcb.opt = 'CI' AND hcb.summ <= 10000 THEN 1 ELSE 0 END) AS '<= 10k'
,SUM(CASE WHEN hcb.kind = 'NEW' AND hcb.opt = 'CI' AND hcb.summ BETWEEN 10000.01 AND **AVG** THEN 1 ELSE 0 END) AS '> 10k < AVG'
,SUM(CASE WHEN hcb.kind = 'NEW' AND hcb.opt = 'CI' AND hcb.summ > **AVG** THEN 1 ELSE 0 END) AS '> AVG'
,AVG(hcb.summ) AS 'AVG summ'
FROM DBO AS hcb
WHERE hcb.stat IN (15, 20)
AND hcb.optstat <> 2999
AND hcb.opt = 'CI'
GROUP BY hcb.id, hcb.name
Use window functions. Presumably, you want the average with the where conditions, so:
SELECT hcb.id AS id,
hcb.name AS Name,
SUM(CASE WHEN hcb.kind = 'NEW' AND hcb.opt = 'CI' AND hcb.summ <= 10000
THEN 1 ELSE 0
END) AS "<= 10k",
SUM(CASE WHEN hcb.kind = 'NEW' AND hcb.opt = 'CI' AND hcb.summ > 10000 AND hcb.summ <= hcb.avg_summ
THEN 1 ELSE 0
END) AS "> 10k < AVG",
SUM(CASE WHEN hcb.kind = 'NEW' AND hcb.opt = 'CI' AND hcb.summ > hcb.avg_summ
THEN 1 ELSE 0
END) AS "> AVG",
avg_summ
FROM (SELECT hcb.*,
AVG(summ) OVER () as avg_summ
FROM DBO AS hcb
WHERE hcb.stat IN (15, 20) AND
hcb.optstat <> 2999 AND
hcb.opt = 'CI'
) hcb
GROUP BY hcb.id, hcb.name, avg_summ;
Note other changes to the query:
The column aliases are defined using double quotes not single quotes. Only use single quotes for string and date constants -- otherwise, you will one day have a confusion where you refer to a column using single quotes.
The second between is removed, replaced with >, so values between 10000 and 10000.01 are included.
If you want the overall average not subject to the filtering conditions, then move the WHERE conditions to the outer query.
I have used 3 different joins and then in SELECT statement brought up 3 different values trough something like COALESCE(MAX(d.[<10k]),0) AS '<10k'
LEFT JOIN (
SELECT r.id AS 'ID'
,COUNT(r.name) AS '<10k'
FROM DBO as r
WHERE r.stat IN (15, 20)
AND r.optstat <> 2999
AND r.opt = 'CI'
AND r.kind = 'NEW'
AND r.summ BETWEEN 10000 AND (SELECT AVG(ci.summ)
FROM DBO as ci
WHERE ci.stat IN (15, 20)
AND ci.optstat <> 2999
AND ci.opt = 'CI'
AND ci.kind = 'NEW')
GROUP BY r.id) AS d ON a.id = d.ID
Maybe it's not a best way to solve a problem, but it works for me. Thax a lot for some suggestions, they helped me. Cheers!

Mysql query subselect getting NULL

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

Show the status of daily file loads from mysql database

I am working on a project where we receive some files daily which are loaded into the database, their status is recorded into meta-data.
For e.g.,
There are 2 different clients from which we get three different type of files, type_a, type_b and type_c.
CLIENT_MASTER
----------------------
client_id|client_name
1|xxx
2|yyy
File status is stored in FILE_MASTER table
FILE_MASTER
-----------------------------------
file_key|client_id|filename|status
1|1|type_a_2010-10-07.csv|12
2|1|type_b_2010-10-07.csv|12
3|1|type_c_2010-10-07.csv|12
4|2|type_a_2010-10-07.csv|12
5|2|type_b_2010-10-07.csv|12
The status keys are stored in STATUS_MASTER table
STATUS_MASTER
-------------------
status_key|status
12|Completed
I want to develop a dashboard showing status of daily file loads, in the below format -
Client|type_a|type_b|type_c
xxx|Yes|Yes|Yes
yyy|Yes|Yes|No
Any help on this would be much appreciated.
This query will return result for current date:
SELECT cm.client_name,
CASE WHEN v.type_a_count > 0 THEN 'Yes' ELSE 'No' END AS type_a,
CASE WHEN v.type_b_count > 0 THEN 'Yes' ELSE 'No' END AS type_b,
CASE WHEN v.type_c_count > 0 THEN 'Yes' ELSE 'No' END AS type_c
FROM client_master cm
LEFT OUTER JOIN (
SELECT fm.client_id,
SUBSTRING(fm.client_name, 8, 10) AS file_date,
SUM(CASE WHEN SUBSTRING(fm.client_name, 1, 6) = 'type_a' THEN 1 ELSE 0 END) AS type_a_count,
SUM(CASE WHEN SUBSTRING(fm.client_name, 1, 6) = 'type_b' THEN 1 ELSE 0 END) AS type_b_count,
SUM(CASE WHEN SUBSTRING(fm.client_name, 1, 6) = 'type_c' THEN 1 ELSE 0 END) AS type_c_count
FROM file_master fm
WHERE SUBSTRING(fm.client_name, 8, 10) = CURDATE()
AND fm.status = 12
GROUP BY fm.client_id, SUBSTRING(fm.client_name, 8, 10)
) v ON cm.client_id = v.client_id
Try this query
SELECT c.name,
IF(FIND_IN_SET('type_a', GROUP_CONCAT(SUBSTRING_INDEX(f1.filename,'_',2))), 'Yes', 'No') as type_a,
IF(FIND_IN_SET('type_b', GROUP_CONCAT(SUBSTRING_INDEX(f1.filename,'_',2))), 'Yes', 'No') as type_b,
IF(FIND_IN_SET('type_c', GROUP_CONCAT(SUBSTRING_INDEX(f1.filename,'_',2))), 'Yes', 'No') as type_c
FROM client_master c
JOIN file_master f1 ON(f1.client_id = c.client_id)
JOIN status_master sm ON(sm.status = f1.status)
WHERE sm.status='Completed' AND SUBSTRING_INDEX(f1.filename,'_',-1) = CURDATE()
GROUP BY c.id

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

GROUP BY on table returning incorrect counts in MySQL with LEFT JOIN

I am trying to return multiple counts and averages from multiple tables sorting by gender and am getting incorrect data. I understand that the following is incorrect, but I am unsure of how to fix it. (Edit: Problem with group by gender. See below.)
Here is the query:
SELECT c.gender AS 'Gender',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
(SELECT COUNT(DISTINCT CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) ) AS 'Open Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Avg Open',
(SELECT COUNT(DISTINCT CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) ) AS 'Click Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Avg Click',
COUNT(DISTINCT ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM ((mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id)
LEFT JOIN mailing_recipient_click mrc ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON c.cons_id = ca.cons_id)
WHERE m.mailing_id = 1
AND gender IS NOT NULL
GROUP BY c.gender;
Here is the table which would be correct if the totals in the fields were correct:
GENDER Mailing Recipient Open Total Avg Open Click Total Avg Click Donations Avg Amt
F 105 2 0.5000 2 0.5000 105 22.5000
M 98 2 0.5000 2 0.5000 98 18.8780
EDIT: Here is an example of what I am hoping to achieve. I am certain that the above values are being repeated. The below values are just examples of what I am expecting:
GENDER Mailing Recipient Open Total Avg Open Click Total Avg Click Donations Avg Amt
F 105 8 0.0761 4 0.0380 2 22.5000
M 98 2 0.0204 1 0.0102 1 18.8000
Edit:
After playing around a bit, I thought that I had discovered that the joining the cons table was what is giving me problematic returns, but the problem is with GROUP BY when using gender. To illustrate, this query (which is grouped by mailing name instead of gender) works beautifully.
select m.mailing_name AS 'mailing',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
SUM(CASE
when mrc.mailing_recipient_click_type_id = 2 THEN 1
END)
AS 'Open Total',
AVG(CASE
WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1
ELSE 0
END) AS 'Avg Open',
SUM(CASE
WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1
END)
AS 'Click Total',
AVG(CASE
WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1
ELSE 0
END) AS 'Avg Click',
COUNT(ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM
mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id
LEFT JOIN mailing_recipient_click mrc
ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON mr.cons_id = c.cons_id
WHERE m.mailing_id = 1
GROUP BY m.mailing_name;
The statement is identical with the exception of the first and last lines.
Try this:
I'm not sure what you mean by Avg Open and Avg Click.
SELECT c.gender AS 'Gender',
COUNT(DISTINCT mr.mailing_recipient_id) AS 'Mailing Recipients',
SUM(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Open Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 2 THEN 1 ELSE 0 END) AS 'Avg Open',
SUM(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Click Total',
AVG(CASE WHEN mrc.mailing_recipient_click_type_id = 1 THEN 1 ELSE 0 END) AS 'Avg Click',
COUNT(DISTINCT ca.cons_action_contribution_id) AS Donations,
AVG(ca.transaction_amt) AS 'Avg Donation Amt'
FROM mailing m
LEFT JOIN mailing_recipient mr ON m.mailing_id = mr.mailing_id
LEFT JOIN mailing_recipient_click mrc ON mr.mailing_recipient_id = mrc.mailing_recipient_id
LEFT JOIN cons_action_contribution ca ON mr.cons_id = ca.cons_id
LEFT JOIN cons c ON c.cons_id = ca.cons_id
WHERE m.mailing_id = 1
AND gender IS NOT NULL
GROUP BY c.gender;
I also think that mrc.mailing_recipient_click_type_id = 2 means open and mrc.mailing_recipient_click_type_id = 1 mean click seems strange to me. I would expect this data to be exclusive and stored in two different fields.