Min and Max on a SUM column - mysql

I have a table like:
Phrase | qty
phrase_1 | 4
phrase_1 | 1
phrase_1 | 8
phrase_2 | 2
phrase_3 | 3
phrase_3 | 2
What I initially return is:
phrase_1 | 13
phrase_3 | 5
phrase_2 | 2
Using:
SELECT phrase, sum(qty) as total
FROM mytable
GROUP By phrase
ORDER BY total DESC
What I need, and can't figure out, is how to return the min and max with the results.
so I would get:
phrase, qty, min, max
phrase_1 | 13 | 2 | 13
phrase_3 | 5 | 2 | 13
phrase_2 | 2 | 2 | 13
Because I want to run a normalization on the resultset and return a new order based on values between 1 and 0.
Something like (this doesn't work):
SELECT phrase, sum(qty) as total, (total - min(total)/max(total) - min(total)) AS rank
FROM mytable
GROUP By phrase
ORDER BY rank DESC
The above statement is ultimiately what I'm looking to do and not sure if it's possible.

With some subqueries you can achieve your goal, but pretty it will never get
CREATE TABLE mytable (
`Phrase` VARCHAR(8),
`qty` INTEGER
);
INSERT INTO mytable
(`Phrase`, `qty`)
VALUES
('phrase_1', '4'),
('phrase_1', '1'),
('phrase_1', '8'),
('phrase_2', '2'),
('phrase_3', '3'),
('phrase_3', '2');
SELECT phrase,total,(total - mi/ma - mi) AS rank
FROM
(SELECT phrase, sum(qty) as total
FROM mytable
GROUP By phrase
ORDER BY total DESC) t1 CROSS JOIN (SELECT MIN(total) mi,MAX(total) ma
FROM
(SELECT phrase, sum(qty) as total
FROM mytable
GROUP By phrase
ORDER BY total DESC) t1) t2
phrase | total | rank
:------- | ----: | ------:
phrase_1 | 13 | 10.8462
phrase_3 | 5 | 2.8462
phrase_2 | 2 | -0.1538
db<>fiddle here

You want window functions:
SELECT phrase, sum(qty) as total,
MIN(SUM(qty)) OVER () as min_total,
MAX(SUM(qty)) OVER () as max_total
FROM mytable
GROUP By phrase
ORDER BY total DESC

You can use code below :
SELECT phrase, sum(qty) as total,
MIN(SUM(qty)) OVER () as min_total,
MAX(SUM(qty)) OVER () as max_total
into #temp
FROM mytable
GROUP By phrase
ORDER BY total DESC
Select *,(total - min_total/max_total - min_total) AS rank From #temp
Drop Table #temp

Related

Getting Percentage and Total from SELECT GROUP BY Mysql

After multiple searches I couldn't find a fit solution that would work in my case.
I have the following table:
id category entry_date
1 Suggestion 01/01/2019
2 Suggestion 05/01/2019
3 Compliment 05/01/2019
4 Complaint 12/02/2019
5 Suggestion 09/10/2019
6 Compliment 23/11/2019
I need to show the number of each category and the percentage of each (based on the total entries). The 'where' will limit a date range - but I'm assuming that here it doesn't matter. This is my expected result:
Category Totals % of Total Entries
Compliment 2 ˜34%
Complaint 1 16%
Suggestion 3 60%
Here is the query I'm currently using:
SELECT category,
COUNT(*) AS total,
ROUND(COUNT(category)*100 / (SELECT COUNT(*))) AS pct
FROM (Mytable)
WHERE `entry_date` >= '2018-01-01' AND `entry_date` <= '2019-12-31'
GROUP BY category ORDER BY category ASC
With this the pct is relative to each category (so: 200,100,300), and not the total.
I'm using MySQL 5.7. Thank you all!
SELECT count can take time, so it will not be fast on big tables
CREATE TABLE Table1
(`id` int, `category` varchar(10), `entry_date` varchar(10))
;
INSERT INTO Table1
(`id`, `category`, `entry_date`)
VALUES
(1, 'Suggestion', '01/01/2019'),
(2, 'Suggestion', '05/01/2019'),
(3, 'Compliment', '05/01/2019'),
(4, 'Complaint', '12/02/2019'),
(5, 'Suggestion', '09/10/2019'),
(6, 'Compliment', '23/11/2019')
;
SELECT `category`, COUNT(*) AS cnt,
100.0 * COUNT(*) / (SELECT COUNT(*) FROM Table1) AS percent FROM Table1 GROUP BY `category`
category | cnt | percent
:--------- | --: | -------:
Suggestion | 3 | 50.00000
Compliment | 2 | 33.33333
Complaint | 1 | 16.66667
db<>fiddle here
You'd need to do this with window functions in MySQL 8.0:
with cte as (
select category, count(*) over (partition by category) as subtotal,
count(*) over () as total from following_table )
select category, subtotal,
concat(round(subtotal*100/total), '%') as `% of Total Entries`
from cte group by category;
Output:
+------------+----------+--------------------+
| category | subtotal | % of Total Entries |
+------------+----------+--------------------+
| Complaint | 1 | 17% |
| Compliment | 2 | 33% |
| Suggestion | 3 | 50% |
+------------+----------+--------------------+
In MySQL 5.7, do it with two queries. The first to get the total, then the second to use that result to calculate the percentage per group.

Group all rows after nth row together

I have the current table:
+----------+-------+
| salesman | sales |
+----------+-------+
| 1 | 142 |
| 2 | 120 |
| 3 | 176 |
| 4 | 140 |
| 5 | 113 |
| 6 | 137 |
| 7 | 152 |
+----------+-------+
I would like to make a query to retrieve the 3 top salesman, and an "Other" column, that would be the sum of everyone else. The expected output would be:
+----------+-------+
| salesman | sales |
+----------+-------+
| 3 | 176 |
| 7 | 152 |
| 1 | 142 |
| Others | 510 |
+----------+-------+
I am using MySQL, and I am experienced about it, but i can't imagine a way of doing this kind of GROUP BY.
A tried UNION with 2 SELECT, one for the top 3 salesman and another select for the "Others", but I couldn't figure a way of excluding the top 3 from the 2nd SELECT
You can do this by LEFT JOINing your table to a list of the top 3 salesmen, and then grouping on the COALESCEd salesman number from the top 3 table (which will be NULL if the salesman is not in the top 3).
SELECT COALESCE(top.sman, 'Others') AS saleman,
SUM(sales) AS sales
FROM test
LEFT JOIN (SELECT salesman AS sman
FROM test
ORDER BY sales DESC
LIMIT 3) top ON top.sman = test.salesman
GROUP BY saleman
ORDER BY saleman = 'Others', sales DESC
Output:
saleman sales
3 176
7 152
1 142
Others 510
Demo on dbfiddle
Using UNION, ORDER BY, LIMIT, OFFSET AND GROUP BY statements you should do the trick:
SELECT salesman, sales
FROM t
ORDER BY sales DESC LIMIT 3
UNION
SELECT 'Others', SUM(sales)
FROM (SELECT salesman, sales
FROM t
ORDER BY sales DESC LIMIT 3, 18446744073709551615) AS tt;
The big number at the end is the way to apply limit until the end of the table, as suggested here
This is a pain in MySQL:
(select salesman, count(*) as cnt
from t
group by salesman
order by count(*), salesman
limit 3
) union all
(select 'Others', count(*)
from t left join
(select salesman, count(*) as cnt
from t
group by salesman
order by count(*)
limit 3
) t3
on t3.salesman = t.salesman
where t3.salesman is null
);
This should be the fastest one if appropriate indexes are present:
(
SELECT salesman, sales
FROM t
ORDER BY sales DESC
LIMIT 3
)
UNION ALL
(
SELECT 'Other', SUM(sales) - (
SELECT SUM(sales)
FROM (
SELECT sales
FROM t
ORDER BY sales DESC
LIMIT 3
) AS top3
)
FROM t
)
ORDER BY CASE WHEN salesman = 'Other' THEN NULL ELSE sales END DESC
this will work:
select salesman,sales from tablename a where a.salesman in (3,7,1)
union all
select 'others' as others,sum(a.sales) as sum_of_others from tablename a where
a.salesman not in (3,7,1) group by others;
check https://www.db-fiddle.com/f/73GjFXL3KsZsYnN26g3rS2/0

how to return last row in specific sum?

imagine we have 1 row which is students that contain, id, name, marks and rank. write query that return the last name of student where marks is equal to 100 ordered by grade.
example
- id | name | marks | grade |
- 01 | Jeff | 40 | 1 |
- 02 | Annie| 40 | 3 |
- 03 | Ramy | 20 | 5 |
- 04 | Jenny| 20 | 2 |
so the result should return
Annie
because Annie is the last row of the sum of marks where marks is equal to 100. Jeff is the first cause based on grade he's equal to 1 so he should be entered first, second is Jenny and third is Annie. Jeff(40)+Jenny(20)+Annie(40) = 100
You can make a running sum MySQL's user variable.
This query should work from MySQL 5.1 and up.
Query
SELECT
Table1_alias.name
FROM (
SELECT
Table1.name
, (#running_marks_sum := #running_marks_sum + Table1.marks) AS running_marks_sum
FROM
Table1
CROSS JOIN (SELECT #running_marks_sum := 0) AS init_user_param
ORDER BY
Table1.grade ASC
) AS Table1_alias
WHERE
Table1_alias.running_marks_sum = 100
Result
| name |
| ----- |
| Annie |
View on DB Fiddle
MySQL 8.0+ only
Query
SELECT
Table1_alias.name
FROM (
SELECT
Table1.name
, SUM(Table1.marks) OVER(ORDER BY Table1.grade) AS running_marks_sum
FROM
Table1
) AS Table1_alias
WHERE
Table1_alias.running_marks_sum = 100;
Result
| name |
| ----- |
| Annie |
View on DB Fiddle
Keep the cumulative sum of marks to a variable. And use this as a sub-query and select the row having the total is 100. But if no row having the cumulative total as 100, then wont't get any result.
Query
set #total := 0;
select `id`, `name`, `marks`, `grade` from(
select `id`, `name`, `marks`, `grade`, (#total := #total + `marks`) as `total`
from `your_table_name`
order by `grade`
) as `t`
where `t`.`total` = 100;
As mentioned the database structure above, Below is one of the way to get the output
select name from (select * from (SELECT id,name,grade,marks, #total := #total + marks AS total FROM (stud, (select #total := 0) t) order by grade ) t WHERE total <=100 ) final_view order by grade desc limit 1

Select last inserted value of each month for every year from DATETIME

I got a DATETIME to store when the values where introduced, like this example shows:
CREATE TABLE IF NOT EXISTS salary (
change_id INT(11) NOT NULL AUTO_INCREMENT,
emp_salary FLOAT(8,2),
change_date DATETIME,
PRIMARY KEY (change_id)
);
I gonna fill the example like this:
+-----------+------------+---------------------+
| change_id | emp_salary | change_date |
+-----------+------------+---------------------+
| 1 | 200.00 | 2018-06-18 13:17:17 |
| 2 | 700.00 | 2018-06-25 15:20:30 |
| 3 | 300.00 | 2018-07-02 12:17:17 |
+-----------+------------+---------------------+
I want to get the last inserted value of each month for every year.
So for the example I made, this should be the output of the Select:
+-----------+------------+---------------------+
| change_id | emp_salary | change_date |
+-----------+------------+---------------------+
| 2 | 700.00 | 2018-06-25 15:20:30 |
| 3 | 300.00 | 2018-07-02 12:17:17 |
+-----------+------------+---------------------+
1 won't appear because is an outdated version of 2
You could use a self join to pick group wise maximum row, In inner query select max of change_date by grouping your data month and year wise
select t.*
from your_table t
join (
select max(change_date) max_change_date
from your_table
group by date_format(change_date, '%Y-%m')
) t1
on t.change_date = t1.max_change_date
Demo
If you could use Mysql 8 which has support for window functions you could use common table expression and rank() function to pick row with highest change_date for each year and month
with cte as(
select *,
rank() over (partition by date_format(change_date, '%Y-%m') order by change_date desc ) rnk
from your_table
)
select * from cte where rnk = 1;
Demo
The below query should work for you.
It uses group by on month and year to find max record for each month and year.
SELECT s1.*
FROM salary s1
INNER JOIN (
SELECT MAX(change_date) maxDate
FROM salary
GROUP BY MONTH(change_date), YEAR(change_date)
) s2 ON s2.maxDate = s1.change_date;
Fiddle link : http://sqlfiddle.com/#!9/1bc20b/15

SUM a pair of COUNTs from two tables based on a time variable

Been searching for an answer to this for the better part of an hour without much luck. I have two regional tables laid out with the same column names and I can put out a result list for either table based on the following query (swap Table2 for Table1):
SELECT Table1.YEAR, FORMAT(COUNT(Table1.id),0) AS Total
FROM Table1
WHERE Table1.variable='Y'
GROUP BY Table1.YEAR
Ideally I'd like to get a result that gives me a total sum of the counts by year, so instead of:
| REGION 1 | | REGION 2 |
| YEAR | Total | | YEAR | Total |
| 2010 | 5 | | 2010 | 1 |
| 2009 | 2 | | 2009 | 3 |
| | | | 2008 | 4 |
I'd have:
| MERGED |
| YEAR | Total |
| 2010 | 6 |
| 2009 | 5 |
| 2008 | 4 |
I've tried a variety of JOINs and other ideas but I think I'm caught up on the SUM and COUNT issue. Any help would be appreciated, thanks!
SELECT `YEAR`, FORMAT(SUM(`count`), 0) AS `Total`
FROM (
SELECT `Table1`.`YEAR`, COUNT(*) AS `count`
WHERE `Table1`.`variable` = 'Y'
GROUP BY `Table1`.`YEAR`
UNION ALL
SELECT `Table2`.`YEAR`, COUNT(*) AS `count`
WHERE `Table2`.`variable` = 'Y'
GROUP BY `Table2`.`YEAR`
) AS `union`
GROUP BY `YEAR`
You should use an UNION:
SELECT
t.YEAR,
COUNT(*) as TOTAL
FROM (
SELECT *
FROM Table1
UNION ALL
SELECT *
FROM Table2
) t
WHERE t.variable='Y'
GROUP BY t.YEAR;
Select year, sum(counts) from (
SELECT Table1.YEAR, FORMAT(COUNT(Table1.id),0) AS Total
FROM Table1
WHERE Table1.variable='Y'
GROUP BY Table1.YEAR
UNION ALL
SELECT Table2.YEAR, FORMAT(COUNT(Table2.id),0) AS Total
FROM Table2
WHERE Table2.variable='Y'
GROUP BY Table2.YEAR ) GROUP BY year
To improve upon Shehzad's answer:
SELECT YEAR, FORMAT(SUM(counts),0) AS total FROM (
SELECT Table1.YEAR, COUNT(Table1.id) AS counts
FROM Table1
WHERE Table1.variable='Y'
GROUP BY Table1.YEAR
UNION ALL
SELECT Table2.YEAR, COUNT(Table2.id) AS counts
FROM Table2
WHERE Table2.variable='Y'
GROUP BY Table2.YEAR ) AS newTable GROUP BY YEAR