How to run through mysql loop when also using group by - mysql

I have the following four tables:
members
agent | memberid | firstname | lastname
===================================================
123 | 444 | john | smith
123 | 555 | sarah | stevens
123 | 777 | harry | tabor
where agent refers to the selling agent id, member id is unique to every customer, first and last names associated with the member
selections
id | memberid | programid
=================================
1 | 444 | 1
2 | 444 | 2
3 | 444 | 3
4 | 555 | 1
5 | 555 | 2
6 | 555 | 3
7 | 777 | 1
8 | 777 | 2
9 | 777 | 3
which details the selections made by each member, for simplicity sake, each of the members listed above in this example selected to be a part of programID's 1, 2, and 3.
groups
programID | cost | type
===================================
1 | 20.00 | 7
2 | 30.00 | 8
3 | 40.00 | 9
this table details the cost and system type for each unique programID
comp_levels
level | type1 | type2 | type3
============================================
1 | 0.25 | 0.15 | 0.1
2 | 0.3 | 0.18 | 0.11
3 | 0.35 | 0.2 | 0.12
4 | 0.4 | 0.225 | 0.15
5 | 0.425 | 0.25 | 0.17
6 | 0.45 | 0.27 | 0.2
7 | 0.47 | 0.28 | 0.22
8 | 0.5 | 0.3 | 0.24
this table highlights the sales commission structure for each particular agent based on their level and using the system type from GROUPS above.
For this example, we will assume Agent 123 is at commission level 2.
Ultimately, I am trying to build a reporting system which would detail the commission structure for both a single agent and a group of agents based on their sales, respective commission level, and the type of sale they brought in.
My current sql statement (which does not satisfy my need), is:
select
a.memberid,
a.firstname,
a.lastname,
sum(c.cost) as Cost,
CASE
when c.type = '5' THEN (SUM(c.cost) * d.type1)
when c.type = '6' THEN (SUM(c.cost) * d.type2)
when c.type = '7' THEN (SUM(c.cost) * d.type3)
END as Commission
FROM
members a
left join selections b using(memberid)
left join groups c using(programid)
left join comp_levels d on d.level = '2'
where agent = '123'
GROUP BY a.memberid;
the results of this query are:
memberid | firstname | lastname | Cost | Commission
================================================================
444 | john | smith | 60.00 | 27.00
555 | sarah | stevens | 60.00 | 27.00
666 | harry | tabor | 60.00 | 27.00
The issue I face is the resultset is taking the first CASE match and applying to several rows returned when it should in fact only apply to each row, THEN group the results.
In the example above, the commission for each particular member SHOULD be $15.18 (30% of $20 + 18% of $30 + 11% of $40) but it is instead taking case 1 and applying the 30% commission to every result (30% of $90).
I have played around with several variations but am unable to figure out how to combine what I am trying to do within a single query ( this query is ONLY run from a mysql gui so using any programming language is not acceptable.

I think the primary problem is that you're using a CASE improperly in a GROUP BY. I think most SQL platforms would complain if you try something like that, but MySQL will just give you bunk/random results and smile.
At any rate, I think you have to use a subquery to get what you want due to the unusual schema. Something like this
SELECT
sq.memberid, sq.firstname, sq.lastname,
SUM(sq.cost) AS total_cost, SUM(sq.commission) AS total_commission
FROM
(
SELECT
m.memberid, m.firstname, m.lastname, g.cost,
CASE
WHEN g.type = '7' THEN (g.cost * cl.type1)
WHEN g.type = '8' THEN (g.cost * cl.type2)
WHEN g.type = '9' THEN (g.cost * cl.type3)
END as commission
FROM members m
LEFT JOIN selections s USING(memberid)
LEFT JOIN groups g USING(programid)
LEFT JOIN comp_levels cl ON cl.level = '2'
WHERE m.agent = '123'
) sq GROUP BY sq.memberid
You can run the subquery by itself to trace what's going on.
Using your data, it gives the result
+----------+-----------+----------+------------+------------------+
| memberid | firstname | lastname | total_cost | total_commission |
+----------+-----------+----------+------------+------------------+
| 444 | john | smith | 90.00 | 15.800 |
| 555 | sarah | stevens | 90.00 | 15.800 |
| 777 | harry | tabour | 90.00 | 15.800 |
+----------+-----------+----------+------------+------------------+
NB: due to the unusual (lack of) normalization, i.e. mapping the types (7,8,9) to type fields (type1,type2,type3), this an awkward problem to wrap a head around. Moreover, I think you might have had some typos in your test data description; i.e. the type values in your SQL, and the total cost in your query output and desired result.

Related

Query to lookup reference tables on sum the result

I am new to SQL, would like to have your suggestions on how to solve this problem,
I have the sales information by type
I want to sum the Prices of certain references by Type and based on the resulting sum, fetch the values from another table and populate in the Output Column.
Group Type 100000 200000 300000
1 A 1 2 3
1 B 0 1 1
2 T 2 2 4
2 U 0 2 2
3 V 2 2 3
4 N 1 1 1
From the above table 2 we find the TYPE A and B belong to same group - Group 1. So in the first table, the query should sum Prices of the references belonging to the Group 1. If the sum is >100000 and <=200000 then based on the type the corresponding value must be chosen.
Incase the sum of Prices based on group is less than 100000 or the type not found in Table 2 then it should take the values from the below table
[+------+----+---+
| Type | 1 | 2 |
+------+----+---+
| A | 50 | 2 |
| B | 60 | 5 |
| C | 65 | 2 |
| D | 65 | 3 |
| E | 65 | 4 |
+------+----+---+][3]
Thus the final output for the above datasheet would be like below,
Order ID Reference Type Price Output
101 AAA A 500000 3
101 AAB B 100000 1
101 ABC C 20000 67
101 DCE B 50000 1
101 BOD D 200000 68
101 ZYZ E 200000 69
102 AAA A 20000 52
So for the first line, its TYPE A and Type A is present under Group 1 and in Group1 we also have Type 2. So for the same order ID 101 , the overall Sales of Type A and B is 650000 > 300000, therefore for Type A we chose the value 3 from the table 2. Since Type C is not present in Table 2, I went to Table 3 and added the two values and so on
Sorry for the long post. Hope my question is clear? Would like to have your expert opinion.
Thanks,
SS
Join all tables and make sure you do LEFT JOIN as we want to keep records from the first table even we don't have corresponding data in the second or third table.
For total count, give priority to the second table, use case when to verify in which range this mrp field is falling. If lies within a range pick count from the second table otherwise pick count from the third table.
SELECT
s.order_id,
s.reference,
s.`type`,
s.mrp,
#a:= IFNULL(g_total.Total, s.mrp) AS MRP_Total, -- #a variable to use it in CASE WHEN clause
CASE
WHEN #a > 100000 AND #a <= 200000 AND sg.`type` IS NOT NULL THEN sg.price_100000
WHEN #a > 200000 AND #a <= 300000 AND sg.`type` IS NOT NULL THEN sg.price_200000
WHEN #a > 300000 AND sg.`type` IS NOT NULL THEN sg.price_300000
ELSE tp.price_1 + tp.price_2
END Total
FROM sales s
LEFT JOIN sales_group sg ON s.`type` = sg.`type`
LEFT JOIN type_prices tp ON s.`type` = tp.`type`
LEFT JOIN (
SELECT
s.order_id, sgg.`group`, SUM(mrp) as Total
FROM sales s
INNER JOIN sales_group sgg ON s.`type` = sgg.`type`
GROUP BY s.order_id, sgg.`group`
) AS g_total -- Temp table to find total MRP, order and group wise
ON s.order_id = g_total.order_id AND sg.`group` = g_total.`group`
ORDER BY s.order_id, s.`type`;
Output:
sales
---
| order_id | reference | type | mrp | MRP_Total | Total |
---------------------------------------------------------
| 101 | AAA | A | 500000 | 650000 | 3 |
| 101 | DCE | B | 50000 | 650000 | 1 |
| 101 | AAB | B | 100000 | 650000 | 1 |
| 101 | ABC | C | 200000 | 200000 | 67 |
| 101 | BOD | D | 200000 | 200000 | 68 |
| 101 | ZYZ | E | 200000 | 200000 | 69 |
| 102 | AAA | A | 20000 | 20000 | 52 |
Note: sg.type IS NOT NULL is added in CASE WHEN clause because if we don't have any mapping in the second table, we should move to ELSE part which refers to the third table.

connecting tables through split fields

I have two tables;
- The Items table - that has basic information about products: name, the basic price of the product, and if the product has any attributes - they will be specified there in the following format: attribute's option ID - attribute's value ID. (an example of an option would be a colour or size, an example of value would be red).
For example:
items table
id | name |attributes |price
1 | a |13-49 | 5.00
2 | b |5-101,13-77| 5.00
3 | c | | 5.00
4 | b |5-102,13-70| 5.00
The second table has every option and value assigned to products (id_option and id_value - meaning the same as in the previous table) as well as information if the attribute value changes the basic price of the product (change) and what the change should be (value).
items_attributes
id |id_item|id_option|id_value |change | value
1 | 1 | 13 | 49 | 1 |10.00
2 | 2 | 5 | 101 | 1 | 5.50
3 | 2 | 13 | 77 | 1 | 0.50
4 | 4 | 5 | 102 | 0 | 0
5 | 4 | 13 | 70 | 1 | 1.00
I want to get a table witch is the same as the first, but with the price calculated according to changes noted in items_attributes table.
id | name |attributes | price
1 | a |13-49 | 15.00
2 | b |5-101,13-77| 11.00
3 | c | | 5.00
4 | b |5-102,13-70| 6.00
How do I split the attributes from the items table and use it to join the two tables?
You can do this with a join using find_in_set():
select i.id, i.name, i.attributes,
i.price + coalesce(sum(ia.value)) as price
from items i left join
items_attributes ia
on find_in_set(concat(ia.id_option, '-', ia.id_attribute), i.attributes) > 0
group by i.id;
Unfortunately, you cannot improve performance of this query using indexes. For better performance, you need a better data structure.

SELECT all the latest non null records available based on date range in another table

Following on from this question SELECT all the newest record distinct keyword with a non null value in one column
I now have a problem where I have this data
PRODUCT:
id| product | amount| ownershipid
1 | ipod | 200 | 2
2 | ipod | 250 | 3
3 | ipod | 150 | 4
4 | apple | 100 | 1
5 | apple | 98 | 2
6 | apple | 500 | 3
7 | itunes | NULL | 1
8 | itunes | 50 | 2
9 | itunes | NULL | 3
10 | itunes | NULL | 4
OWNERSHIP:
ownershipid| start | end
1 | 2011-01-01 | 2011-12-31
2 | 2012-01-01 | 2012-12-31
3 | 2014-01-01 | 2014-12-31
4 | 2013-01-01 | 2013-12-31
I need the most recent amount available for each product. I can not do an order by on ownershipId as the most recent data is from 2014. not from 2013. OwnershipId is Autoincrement and we accept historic data.
So, my result should return rows 2, 6 and 8.
Assuming most recent is defined by end, you can do a somewhat convoluted join to get the result;
SELECT p.product, p.amount
FROM product p JOIN ownership o ON p.ownershipid = o.ownershipid
JOIN (
SELECT p.product, MAX(o.end) end
FROM product p JOIN ownership o ON p.ownershipid = o.ownershipid
WHERE p.amount IS NOT NULL GROUP BY p.product) z
ON p.product = z.product AND o.end = z.end;
An SQLfiddle to test with.
The inner query gets the max date that has a non null amount per item.
The outer query gets the amount for that time/product.

grouping relationships of many-to-many-to many

I have a requirement to map a tax rate to a person based on the country the person was resident at that time.
tbl: person
| p_id | name_first | name_last |
=======++========================
| 1 | john | smith |
| 2 | joanne | smyth |
tbl: person_in_country
| p_id | iso | arrived |
===========================
| 1 | GB | 1980-01-01 |
| 2 | FR | 1987-03-21 |
| 1 | FR | 2003-06-17 |
| 1 | JP | 2008-07-02 |
| 2 | GB | 2008-10-01 |
| 1 | GB | 2009-01-10 |
tbl: country
| iso | ctry_name |
========================
| GB | United Kingdom |
| FR | France |
| JP | Japan |
tbl: tax_rates
| iso | tax_rate | tax_date |
===============================
| GB | 17.5 | 1970-01-01 |
| FR | 15.0 | 1977-03-21 |
| JP | 12.0 | 1977-06-17 |
| FR | 15.0 | 1994-03-21 |
| JP | 18.5 | 2008-07-02 |
| GB | 15 | 2008-04-01 |
| GB | 20 | 2010-05-01 |
So I need tuples containing the person in the country and the tax rate that they should have at a given time..
Something along the lines of:
select p.p_id, p.name_first, p.name_last,
pic.arrived,
c.iso, c.ctry_name,
t.tax_rate
from people p
left join (select * from person_in_country order by arrived desc) pic using (p_id)
left join country c on c.iso = pic.iso
left join (select * from tax_rates order by tax_date desc) t on t.iso = c.iso
where t.tax_date <= NOW()
group by p.pid, pic.arrived, t.tax_date
Hope this make sense... and many thanks in advance
Actually, you have to do a query basicly in three steps. First one you are going to retrieve a kind of "raw data" with all desired columns, joining the relating tables, whatever these columns will be used to join or to retrieve choosen data.
After that, you have to group data in order to turn only last dates from the matching join.
Finally, you have to query again tax table to retrieve tax in the tax date current at the moment of arrival.
It is possible that there is a more easy or ellegant way to do so, but this query is working. Check your system performance depending on the query demmands. It seems a bit hard at a first glance, but it isn't when taking a more carefull look. The SQL code:
SELECT
c02.iso,
c02.p_id,
c02.name_first,
c02.name_last,
c02.ctry_name,
c02.arrived,
c02.mtax_date,
tax_rates.tax_rate
FROM (
SELECT
c01.iso,
c01.p_id,
c01.name_first,
c01.name_last,
c01.ctry_name,
c01.arrived,
Max(c01.tax_date) AS mtax_date
FROM (
SELECT
country.iso,
person.p_id,
person.name_first,
person.name_last,
country.ctry_name,
person_in_country.arrived,
tax_rates.tax_date
FROM
tax_rates
INNER JOIN (
country
INNER JOIN (
person
INNER JOIN
person_in_country
ON
person.p_id = person_in_country.p_id
)
ON
country.iso = person_in_country.iso
)
ON
tax_rates.iso = person_in_country.iso
GROUP BY
country.iso,
person.p_id,
person.name_first,
person.name_last,
country.ctry_name,
person_in_country.arrived,
tax_rates.tax_date
HAVING (((tax_rates.tax_date)<=[arrived]))
) as c01
GROUP BY
c01.iso,
c01.p_id,
c01.name_first,
c01.name_last,
c01.ctry_name,
c01.arrived
) as c02
INNER JOIN
tax_rates ON (
c02.mtax_date = tax_rates.tax_date
)
AND
(
c02.iso = tax_rates.iso
);
Output:
iso p_id name_first name_last ctry_name arrived mtax_date tax_rate
GB 1 john smith United Kindom 01/01/1980 01/01/1970 18
FR 2 joanne smyth France 21/03/1987 21/03/1977 15
FR 1 john smith France 17/06/2003 21/03/1994 15
JP 1 john smith Japan 02/07/2008 02/07/2008 18
GB 1 john smith United Kindom 10/01/2009 01/04/2008 15
GB 2 joanne smyth United Kindom 01/10/2008 01/04/2008 15

select query to calculate number of occurrence as well as total cost

I have one report page which displays summarized data of other report.I have used php and mysqli. Let me explain you in deep.
I have a web application of store, where you can add product details. Using these product details you can generate packaging list report of products. And based on the generated packaging list report I need to generate one other report which contains summarized data of the packaging list.
below are my tables:
product table:
id | name | desc_id | purity | style_no | type | duty
1 | ABC | 1 | 18 | TEST123 | R | 100
2 | XYZ | 2 | 14 | TEST456 | B | 80
3 | DEF | 1 | 14 | TEST122 | R | 80
4 | PQR | 1 | 18 | TEST124 | R | 120
5 | HJK | 3 | 18 | TEST134 | B | 300
Description table:
id | descrip
1 | Gold Diamond Ring
2 | Gold Diamond Pendant
3 | Gold Diamond Earring
packaging_master table
id | name
1 | pkg_1
2 | pkg_2
packging_details table
id | pkg_id | prod_id
1 | 1 | 1
2 | 1 | 2
3 | 1 | 3
4 | 1 | 4
5 | 1 | 5
I have used below query to generate the packaging list report for specific id, which works correctly.
SELECT id, (SELECT descrip FROM description WHERE id = desc_id ) AS descrip,
style_no, type , purity, duty FROM product WHERE id IN ( SELECT prod_id FROM packaging_list_details WHERE pkg_id =1 ) ORDER BY descrip ASC , purity ASC
which displays below result:
id | descrip | style_no | type | purity | duty
1 |Gold Diamond Ring | TEST123 | R | 18 | 100
4 |Gold Diamond Ring | TEST124 | R | 18 | 120
3 |Gold Diamond Ring | TEST122 | R | 14 | 80
2 |Gold Diamond Pendant| TEST456 | B | 14 | 80
5 |Gold Diamond Earring| TEST134 | B | 18 | 300
Now I want summarized data of above result using query.
Like:
id | descrip | purity | qty | duty
1 |Gold Diamond Ring | 18 | 2 | 220
2 |Gold Diamond Ring | 14 | 1 | 80
3 |Gold Diamond Pendant| 14 | 1 | 80
4 |Gold Diamond Earring| 18 | 1 | 300
How can I achieve this?
You need to use the GROUP_BY statement - See MySql docs for more info.
This will translate the query to such
SELECT d.descrip, p.purity, count(p.purity) as qty, sum(p.duty)
FROM product p
INNER JOIN Description d ON p.desc_id = d.id
LEFT OUTER JOIN packaging_details pg on pg.prod_id = p.id
GROUP BY d.descrip, p.purity
ORDER BY d.descrip desc, p.purity desc
You can also use the sub select methodology you were using, but I prefer using joins. INNER JOIN will link both tables so that all their records are returned. OUTER JOIN will return all rows from the tables on the LEFT of the statement and matches them to values from the tables on the RIGHT.
See a full SQL Fiddle sample.
NOTE: I am not sure where you are getting the values for Id in your sample - Are they simply row numbers?
I think you should rewrite your query using JOINs:
SELECT
P.id
,D.descrip
,P.style_no
,P.type
,P.purity
,P.duty
FROM
packaging_list_details PLD
JOIN
product P ON
(P.id = PLD.prod_id)
LEFT JOIN
description D on
(D.desc_id = P.id)
WHERE
(PLID.pkg_id = 1)
That should give you the same result you already have. To get the totals, you can write a new query, similar to the above:
SELECT
P.id
,D.descrip
,P.type
,P.purity
,COUNT(p.id) as total_products
,SUM(P.duty) as total_duty
FROM
packaging_list_details PLD
JOIN
product P ON
(P.id = PLD.prod_id)
LEFT JOIN
description D on
(D.desc_id = P.id)
WHERE
(PLID.pkg_id = 1)
GROUP BY
P.id
,D.descrip
,P.type
,P.purity
The second query gives you the totals you are looking for.