SUM values or take MAX to SUM depending on value - mysql

I have a query to select some shippingcost and I want to sum them up in a special way.
Sample Data:
supplierID | articleID | sumUP | shippingCost
10 | 100 | 1 | 20
10 | 101 | 1 | 15
20 | 200 | 0 | 15
20 | 201 | 0 | 10
30 | 300 | 0 | 10
=============================================
Sum should be: 60
What I want to achive is to sum up all shippingCost values, but since sumUP on supplierID 20 and 30 is 0, i just want to have the maximum value of these suppliers.
so
supplier 10 should have 35 (sum of values)
supplier 20 should have 15 (maximum value)
supplier 30 should have 10 (maximum value)
in sum it should be 60.
I tried a lot of complex querys but always got stuck when I want to decide to sum or take max and sum all afterwards.
Is this even possible with a mysql statement? (of course subquerys in it).
Any suggestions how to solve this?

First group by supplierid to get the sum and the max of shippingcost for each supplier and then use conditional aggregation on the results:
select
sum((t.sumup = 0) * maxshippingcost + (t.sumup = 1) * sumshippingcost) total
from (
select supplierid,
max(sumup) sumup,
max(shippingcost) maxshippingcost,
sum(shippingcost) sumshippingcost
from tablename
group by supplierid
) t
See the demo.
Or with a CASE expression:
select
sum(
case t.sumup
when 0 then maxshippingcost
when 1 then sumshippingcost
end
) total
from (
select supplierid,
max(sumup) sumup,
max(shippingcost) maxshippingcost,
sum(shippingcost) sumshippingcost
from tablename
group by supplierid
) t
See the demo.

Use a case expression to either return the SUM() or the MAX():
select supplierID,
case when max(sumUP) = 1 then sum(shippingCost) else max(shippingCost) end
from tablename
group by supplierID
EDIT BY Dwza
As forpas mentioned, this statement just gives me the result that needs to be summed up. The total statement could look like:
select sum(my.result) from
(select supplierID,
case when max(sumUP) = 1 then sum(shippingCost) else max(shippingCost) end as result
from tablename
group by supplierID) as my

DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(supplier_ID INT NOT NULL
,articleID INT NOT NULL PRIMARY KEY
,sum_UP INT NOT NULL
,shippingCost INT NOT NULL
);
INSERT INTO my_table VALUES
(10,100,1,20),
(10,101,1,15),
(20,200,0,15),
(20,201,0,10),
(30,300,0,10);
SELECT SUM(x) total
FROM
( SELECT supplier_id, MAX(shippingcost) x FROM my_table WHERE sum_up = 0 GROUP BY supplier_id
UNION
SELECT supplier_id, shippingcost FROM my_table WHERE sum_up = 1
) a;
+-------+
| total |
+-------+
| 60 |
+-------+

I use case and group by
select supplier_id,
case
when sum_up = 0
then max(shipping_cost)
when sum_up = 1
then sum(shipping_cost) end as total
from table_name
group by supplier_id, sum_up;
The result as follows:
supplier_id, sum_up
20 15
10 35
30 10
Now, I can sum it
select sum(total)
from (
select supplier_id,
case
when sum_up = 0
then max(shipping_cost)
when sum_up = 1
then sum(shipping_cost) end as total
from cd.sample
group by supplier_id, sum_up
) a;

SELECT sum(A.SumShipping) as TotalSum
FROM (SELECT supplierID, if(sumup = 1, sum(shippingcost), max(shippingcost))
as SumShipping FROM tablename group by supplierID) as A;

Related

SQL counting occurrences of conditional statements using values from 2 different roles

I have a table with fields including time (UTC) and accountID.
accountID | time | ...
1 |12:00 |....
1 |12:01 |...
1 |13:00 |...
2 |14:00 |...
I need to make an sql query to return the accountID with a new field counting 'category' where 'category' can be 'a' or 'b'. If there is a row entry from the same accountID that has a positive time difference of 1 minute or less, category 'a' needs to be incremented, otherwise 'b'. The results from the above table would be
accountID| cat a count| cat b count
1 | 1 | 2
2 | 0 | 1
What approaches can I take to compare values between different rows and output occurrences of comparison outcomes?
Thanks
To compute this categories you'll need to pre-compute the findings of close rows in a "table expression". For example:
select
accountid,
sum(case when cnt > 0 then 1 else 0 end) as cat_a_count,
sum(case when cnt = 0 then 1 else 0 end) as cat_b_count
from (
select
accountid, tim,
( select count(*)
from t b
where b.accountid = t.accountid
and b.tim <> t.tim
and b.tim between t.tim and addtime(t.tim, '00:01:00')
) as cnt
from t
) x
group by accountid
Result:
accountid cat_a_count cat_b_count
--------- ----------- -----------
1 1 2
2 0 1
For reference, the data script I used is:
create table t (
accountid int,
tim time
);
insert into t (accountid, tim) values
(1, '12:00'),
(1, '12:01'),
(1, '13:00'),
(2, '14:00');
Use lag() and conditional aggregation:
select accountid,
sum(prev_time >= time - interval 1 minute) as a_count,
sum(prev_time < time - interval 1 minute or prev_time is null) as b_count
from (select t.*,
lag(time) over (partition by accountid order by time) as prev_time
from t
) t
group by accountid;

How to run sql query recursively?

For example, there is a accounts table has:
account_id | ......
000 | ......
001 | ......
004 | ......
010 | ......
.....
198 | ......
I want to get the distribution of account id, instead of running following query again and again, is there any smarter way to get id count for 000-010, 010-020, ..., 190-200? Thanks
SELECT count(account_id)
FROM accounts
WHERE account_id >= '000' AND account_id <= '010';
You can divide the account_id by 10 to create a range and then group by the divided result to get the result you want:
SELECT CONCAT(LPAD(FLOOR(account_id/10)*10,3, '0'), '-', LPAD(FLOOR(account_id/10)*10+9, 3, '0')) AS `range`,
COUNT(*) AS number
FROM accounts
GROUP BY `range`
Output (for some sample data in my demo):
range number
000-009 3
010-019 2
020-029 1
030-039 1
040-049 1
050-059 2
Demo on dbfiddle
You would use group by:
select (case when account_id >= '000' and account_id <= '010' then '000-010'
when account_id >= '011' and account_id <= '020' then '011-020'
when account_id >= '021' and account_id <= '030' then '021-030'
. . .
end) as account_id_grp,
count(*)
from accounts
group by account_id_grp
order by account_id_grp;
select t1.account_id ||'-'||t2.
account_id,count(*) from
table t1 where account_id IN (Select account_id from
table t2 where t2.account_id-t1.account_id=10)`
I tried like taking difference of the account ids in the tables via corelated subquery

mysql query lowest option sum

I've got a query returning the following:
ID | Price
---------------
1 | 20
1 | 30
1 | 15
2 | 10
2 | 12
2 | 20
3 | 1
3 | 0
3 | 0
4 | 0
4 | 0
4 | 7
I'm wondering if there's a way I can get the sum of the lowest value for each ID. So in this case it would return 25.
15+10+0+0
You can use a subquery selecting the min price for each id, then sum those values:
select sum(minprice) as overallprice
from (
select min(price) minprice
from yourtable
group by id) t
You can create a sub-query that finds the lowest price per id and take the results from that and sum them together. In pseudo-code:
select
sum(lowest_price)
from (select id, min(price) as lowest_price from prices group by id) lowest_prices
You can do a query like below
Select sum (a) from
(
Select min (price) as a from yourtable
Group by id
) t
Another approach using partition without using group by statement
select sum(price.min_price) from
(select distinct id,min(price) over(partition by id) as min_price from prices) price
Some other approaches would be to use MySQL user variables or a self left join..
MySQL user variable solution
Query
SELECT
SUM(prices.Price)
FROM (
SELECT
prices.Price
, CASE
WHEN #id != prices.id
THEN 1
ELSE 0
END AS isMinGroupValue
, (#id := prices.id)
FROM
prices
CROSS JOIN (
SELECT
#id := 0
) AS init_user_params
ORDER BY
prices.ID ASC
, prices.price ASC
) AS prices
WHERE
prices.isMinGroupValue = 1
see demo https://www.db-fiddle.com/f/nzWqMQAxd7mvq589R7WuZ8/0
Self left join solution
Query
SELECT
SUM(prices1.Price)
FROM
prices prices1
LEFT JOIN
prices prices2
ON
prices1.ID = prices2.ID
AND
prices1.price > prices2.price
WHERE
prices2.ID IS NULL
see demo https://www.db-fiddle.com/f/nzWqMQAxd7mvq589R7WuZ8/1
I would use correlation subquery :
select sum(t.price) as overallprice
from table t
where price = (select min(price) from table t1 where t1.id = t.id);

MySQL - SELECT MAX(), and get corresponding fields

The task is: get the list with ID of every employee and the ID of the last department where he worked. It's becoming more complicated cause one person can work in different departments at one time, so we need to get his last department where he has the max rate.
table:
ID_employee| ID_department | end_date | rate
1 22 2016-01-01 1
2 25 NULL 0.3
2 27 NULL 1
3 22 2013-12-12 0.5
3 22 2014-05-05 0.5
end_date is the last day when employee worked, and NULL value means that his contract is actual today.
The result must look like:
ID_employee | ID_department | end_date | rate
1 22 2016-01-01 1
2 27 NULL 1
3 22 2014-05-05 0.5
I found out how to select max() with corresponding fields by using join:
SELECT table.id_employee, id_department
FROM table
JOIN ( SELECT id_employee,
IF (MAX( end_date IS NULL ) = 1 , "0000-00-00", MAX( end_date )) as max_end_date
FROM table GROUP BY id_employee) maxs ON maxs.id_employee = table.id_employee
WHERE maxs.max_end_date = IFNULL(table.end_date, "0000-00-00")
GROUP BY table.id_employee
However, there are ALL corresponding rows in the result:
ID_employee | ID_department | end_date | rate
1 22 2016-01-01 1
2 25 NULL 0.3
2 27 NULL 1
3 22 2014-05-05 0.5
The question is, how to get NOT JUST corresponding rows to MAX(end_date), but with MAX(rate) too? I assume that HAVING might help, but I still don't know what exactly must be there.
And maybe there are other ways to solve problem with better performance, because this query works about 16s while the table has ~30 000 rows.
Could you try with the query below:
SELECT T1.ID_employee,
T1.ID_department,
CASE WHEN maxs.max_end_date = "0000-00-00" THEN NULL ELSE maxs.max_end_date END AS end_date,
T1.rate
FROM TestTable T1
JOIN ( SELECT id_employee,
MAX(ID_department) AS ID_department,
IF (MAX( end_date IS NULL ) = 1, "0000-00-00", MAX( end_date )) AS max_end_date
FROM TestTable
GROUP BY id_employee ) maxs ON maxs.id_employee = T1.id_employee AND maxs.ID_department = T1.ID_department
WHERE maxs.max_end_date = IFNULL(T1.end_date, "0000-00-00")
GROUP BY T1.id_employee
Please find the Live Demo
UPDATE:
As per the comments the following query helped to achieve the result:
SET #CurrentDate := CURDATE();
SELECT T2.ID_employee,
T2.ID_department,
CASE WHEN MR.Max_end_date = #CurrentDate THEN NULL ELSE T2.end_date END AS end_date,
MR.MaxRate AS rate
FROM TestTable T2
JOIN (
SELECT T1.ID_employee, MAX(T1.rate) AS MaxRate, MD.Max_end_date
FROM TestTable T1
JOIN (
SELECT ID_employee,
MAX(CASE WHEN end_date IS NULL THEN #CurrentDate ELSE end_date END) AS Max_end_date
FROM TestTable
GROUP BY ID_employee
) MD ON MD.ID_employee = T1.ID_employee
WHERE MD.Max_end_date = IFNULL(T1.end_date, #CurrentDate)
GROUP BY T1.ID_employee
) MR ON MR.ID_employee = T2.ID_employee AND MR.MaxRate = T2.rate
WHERE MR.Max_end_date = IFNULL(T2.end_date, #CurrentDate)
Working Demo
I think this query will work for you.
SELECT ID_employee, ID_department, end_date, MAX(rate)
FROM test_max
GROUP BY ID_employee

Divide with the rollup value

I have this code where it sums up the hours of the employee and uses rollup to get the total of the hours:
SELECT IFNULL(users, 'Total') AS Employee,
SUM(actual) AS Amount
FROM table1
WHERE name = "ProjectName"
GROUP BY users
WITH ROLLUP
Employee | Amount
A | 15
B | 10
C | 10
Total | 35
What I would like to do for my third column (Percent) is to divide the sum(actual) to the value of the total to get the percentage.
But for that Percent column I don't need to get the Total Percent.
The total value is not constant to just 35.
Employee | Amount | Percent
A | 15 | 42.85
B | 10 | 28.57
C | 10 | 28.57
Total | 35 |
How can I do that?
Here's the sqlfiddle: http://sqlfiddle.com/#!2/4543b/5
This works as desired:
SET #project_name = 'ProjectName';
SELECT IFNULL(users, 'Total') AS Employee, SUM(actual) AS Amount,
IF(ISNULL(users), '', TRUNCATE(SUM(actual) / sum_table.amount_sum * 100, 2)
) AS Percent
FROM Table1
INNER JOIN (
SELECT SUM(actual) AS amount_sum
FROM Table1
WHERE name = #project_name
) AS sum_table
WHERE name = #project_name
GROUP BY users
WITH ROLLUP;
DEMO # SQL Fiddle
Perhaps a job best left to the logic tier of your application, but if you absolutely must do it in the data tier then you merely need to join your query with another that finds the overall total:
SELECT IFNULL(users, 'Total') AS Employee,
SUM(actual) AS Amount,
SUM(actual)/t.Total AS Percent
FROM Table1, (
SELECT SUM(actual) AS Total
FROM Table1
WHERE name = 'ProjectName'
) t
WHERE name = 'ProjectName'
GROUP BY users WITH ROLLUP
SELECT if(users is NULL,'Total',users) as Employee, sum(actual) as Amount,
(CASE
WHEN users is not null THEN CAST(sum(actual)/sum.sumAmt * 100 as DECIMAL(10,2))
END) as Percent
FROM Table1, (SELECT sum(actual) as sumAmt FROM Table1
WHERE name = 'ProjectName') sum
WHERE name = "ProjectName"
GROUP BY users
WITH ROLLUP
DEMO