How can I write this in a more optimized way?
SELECT week_day
, SUM(min_0 + min_1 + min_2 + min_3)
/ (SELECT SUM(min_0 + min_1 + min_2 + min_3)
FROM Hotel.RegIn) * 100 AS MIN_PERCENTAGE
FROM Hotel.RegIn
WHERE week_day = "Wednesday"
GROUP
BY week_day;
I'd write it as below:
SELECT
"Wednesday",
100 * SUM((week_day = "Wednesday") * (min_0 + min_1 + min_2 + min_3))
/ SUM(min_0 + min_1 + min_2 + min_3) AS MIN_PERCENTAGE
FROM Hotel.RegIn
;
or if you can use multiple statements, another one with easier filters:
SET #var_all_day_total :=
(SELECT SUM(min_0 + min_1 + min_2 + min_3) FROM Hotel.RegIn) / 100;
SELECT
week_day,
SUM(min_0 + min_1 + min_2 + min_3) / #var_all_day_total AS MIN_PERCENTAGE
FROM Hotel.RegIn
-- WHERE week_day = "Wednesday"
GROUP BY
week_day
;
The idea usually is not to redundantly calculate the same values more than once.
You should use derived table in the FROM clause. This way the subquery will be evaluated only once.
SELECT
week_day,
SUM(min_0 + min_1 + min_2 + min_3) / RegInSum.sum_all * 100 AS MIN_PERCENTAGE
FROM
Hotel.RegIn,
(SELECT
SUM(min_0 + min_1 + min_2 + min_3) as sum_all
FROM
Hotel.RegIn) as RegInSum
WHERE week_day = "Wednesday"
GROUP BY week_day;
Note that as far as you restrict your query only to one day of week, you won't get any performance gain by rewriting the query, because the subquery will be evaluated only once in both cases.
See CTE (Common Table Expressions) for more readable syntax of derived tables in newer versions of MySQL.
Related
Can anybody tell me why the second LEFT JOIN in the query below (previous_spend) is returning double the amount it should?
It returns the correct amount if I remove the first LEFT JOIN, so I assume it's doubling the result due to the first LEFT JOIN, but I'm not sure how to rewrite the query to avoid that.
Any help would be much appreciated.
Update: I've created a slightly easier to understand version here: Here's a live example: http://sqlfiddle.com/#!9/6a9358/1 - as you can see 'current_spend' should return £300.
SELECT
COALESCE(SUM(current_spend.total_spend), 0) AS total_spend,
COALESCE(SUM(previous_spend.total_previous_spend), 0) AS total_previous_spend,
COALESCE(SUM(current_spend.total_spend), 0) - COALESCE(SUM(previous_spend.total_previous_spend), 0) AS total_spend_diff,
100 * (COALESCE(SUM(current_spend.total_spend), 0) - COALESCE(SUM(previous_spend.total_previous_spend), 0)) / COALESCE(SUM(previous_spend.total_previous_spend), 0) AS total_spend_diff_perc
FROM customer_scheme
LEFT JOIN (
SELECT SUM(spend_1 + spend_2) AS total_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2017'
GROUP BY user_id
UNION ALL
SELECT SUM(spend_1 + spend_2 + spend_3 + spend_4 + spend_5 + spend_6 + spend_7 + spend_8 + spend_9 + spend_10 + spend_11 + spend_12) AS total_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2018'
GROUP BY user_id
UNION ALL
SELECT SUM(spend_1 + spend_2 + spend_3 + spend_4 + spend_5 + spend_6 + spend_7 + spend_8 + spend_9 + spend_10 + spend_11 + spend_12) AS total_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2019'
GROUP BY user_id
UNION ALL
SELECT SUM(spend_1 + spend_2) AS total_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2020'
GROUP BY user_id
) as current_spend
ON current_spend.user_id = customer_scheme.user_id
LEFT JOIN
(
SELECT SUM(spend_1 + spend_2) AS total_previous_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2013'
GROUP BY user_id
UNION ALL
SELECT SUM(spend_1 + spend_2 + spend_3 + spend_4 + spend_5 + spend_6 + spend_7 + spend_8 + spend_9 + spend_10 + spend_11 + spend_12) AS total_previous_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2014'
GROUP BY user_id
UNION ALL
SELECT SUM(spend_1 + spend_2 + spend_3 + spend_4 + spend_5 + spend_6 + spend_7 + spend_8 + spend_9 + spend_10 + spend_11 + spend_12) AS total_previous_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2015'
GROUP BY user_id
UNION ALL
SELECT SUM(spend_1 + spend_2) AS total_previous_spend, user_id
FROM customer_spend
WHERE customer_spend.spend_year = '2016'
GROUP BY user_id
) as previous_spend
ON previous_spend.user_id = customer_scheme.user_id
LEFT JOIN user
ON customer_scheme.user_id = user.user_id
WHERE customer_scheme.scheme_id = 36
AND customer_scheme.customer_scheme_access = 'Yes'
AND user.user_deleted_at IS NULL
AND user_type = 'Customer'
AND user.user_status IN (1)
ORDER BY total_spend_diff DESC
The summary of your query is
(customer_scheme LEFT JOIN current_spend) appended with (customer_scheme LEFT JOIN previous_spend)
If you remove the LEFT JOIN with current_spend :
Query would be customer_scheme LEFT JOIN previous_spend
That means, for one user in customer_scheme, you will get one row for previous_spend for 2013, one row for 2014, one row for 2015, one row for 2016
While summing, you will sum all the above said 4 rows.
If you have the LEFT JOIN with current_spend :
For first LEFT JOIN with current_spend, you will get one row for 2017, one for 2018, one for 2019 and one for 2020.
So, for one user_id , you will get 4 rows. Now, you are going to join previous_spend with these 4 rows (where as previously, you will join with only one row). While summing, this makes the difference.
I will try to provide much better insights if you can share an sql fiddle with some test data.
Try this:
SELECT
SUM(IFNULL(current_spend.total_spend, 0)) AS total_spend,
SUM(IFNULL(previous_spend.total_previous_spend, 0)) AS total_previous_spend,
SUM(IFNULL(current_spend.total_spend, 0)) - SUM(IFNULL(previous_spend.total_previous_spend, 0)) AS total_spend_diff,
100 * (SUM(IFNULL(current_spend.total_spend, 0)) - SUM(IFNULL(previous_spend.total_previous_spend, 0))) / SUM(IFNULL(previous_spend.total_previous_spend, 0)) AS total_spend_diff_perc
FROM
customer_scheme
LEFT JOIN
(
SELECT SUM(spend_1 + spend_2 + spend_3 + spend_4 + spend_5 + spend_6 + spend_7 + spend_8 + spend_9 + spend_10 + spend_11 + spend_12) AS total_spend, user_id, customer_spend.spend_year
FROM customer_spend
WHERE customer_spend.spend_year in ('2017','2018','2019','2020')
GROUP BY user_id, customer_spend.spend_year
) as current_spend
ON current_spend.user_id = customer_scheme.user_id
INNER JOIN
(
SELECT SUM(spend_1 + spend_2 + spend_3 + spend_4 + spend_5 + spend_6 + spend_7 + spend_8 + spend_9 + spend_10 + spend_11 + spend_12) AS total_previous_spend, user_id, customer_spend.spend_year
FROM customer_spend
WHERE customer_spend.spend_year in ('2013','2014','2015','2016')
GROUP BY user_id, customer_spend.spend_year
) as previous_spend
ON previous_spend.user_id = customer_scheme.user_id
LEFT JOIN user
ON customer_scheme.user_id = user.user_id
WHERE customer_scheme.scheme_id = 36
AND customer_scheme.customer_scheme_access = 'Yes'
AND user.user_deleted_at IS NULL
AND user_type = 'Customer'
AND user.user_status IN (1)
ORDER BY total_spend_diff DESC;
I have this data..
+------+--------------+------------+
+ id + position_id + name +
+------+--------------+------------+
+ 1 + 1 + name_1 +
+ 2 + 5 + name_2 +
+ 3 + 2 + name_3 +
+ 4 + 2 + name_4 +
+ 5 + 2 + name_5 +
+ 6 + 3 + name_6 +
+ 7 + 4 + name_7 +
+ 8 + 3 + name_8 +
+ 9 + 2 + name_9 +
+------+--------------+------------+
..then I want the the results is like
+--------------+-----------+----------+----------+-----------+
+ position_id + result1 + result2 + result3 + result4 +
+--------------+-----------+----------+----------+-----------+
+ 1 + name_1 + # + # + # +
+ 2 + name_3 + name_4 + name_5 + name_9 +
+ 3 + name_6 + name_8 + # + # +
+ 4 + name_7 + # + # + # +
+ 5 + name_2 + # + # + # +
+--------------+-----------+----------+----------+-----------+
I have some case for resulting data, this data is for my school reports. The data results must be dynamic following primary of position and if the result is empty will sowing #.
For more data or information you can ask with the following command
If a fixed number of columns then you could do something like this:-
SELECT a.position_id,
COALESCE(MIN(a.name), '#') AS result1,
COALESCE(MIN(b.name), '#') AS result2,
COALESCE(MIN(c.name), '#') AS result3,
COALESCE(MIN(d.name), '#') AS result4
FROM some_table a
LEFT OUTER JOIN some_table b ON a.position_id = b.position_id AND a.id < b.id
LEFT OUTER JOIN some_table c ON a.position_id = c.position_id AND b.id < c.id
LEFT OUTER JOIN some_table d ON a.position_id = d.position_id AND c.id < d.id
GROUP BY a.position_id
With a variable number of columns it isn't going to be possible really without dynamically creating the SQL based on the number of columns, or doing something nasty with GROUP_CONCAT.
But this isn't likely to be efficient.
It would probably be better to do a query to get the first results and then sort out the formatting in the calling script.
EDIT
Time for some nasty code, and i still needs polishing!
First bit is a stored procedure. This gets the max number of columns (gets it slightly wrong, but should be easy to fix with a bit of effort, and works for now) and dynamically builds up the SQL to create a temp table with this number of columns, and then populates it.
DELIMITER ;;
CREATE DEFINER=CURRENT_USER PROCEDURE stored_procedure_name()
BEGIN
DECLARE sql1 TEXT;
DECLARE sql2 TEXT;
DECLARE sql3 TEXT;
SET ##group_concat_max_len = 32000;
SELECT
GROUP_CONCAT(CONCAT('MIN(a', (1 + units.iCnt + 10 * tens.iCnt), '.name) AS result', (1 + units.iCnt + 10 * tens.iCnt)) ORDER BY (1 + units.iCnt + 10 * tens.iCnt)),
GROUP_CONCAT(CONCAT('LEFT OUTER JOIN some_table a', (1 + units.iCnt + 10 * tens.iCnt), ' ON a', (units.iCnt + 10 * tens.iCnt), '.position_id = a', (1 + units.iCnt + 10 * tens.iCnt), '.position_id AND a', (units.iCnt + 10 * tens.iCnt), '.id < a', (1 + units.iCnt + 10 * tens.iCnt), '.id') ORDER BY (1 + units.iCnt + 10 * tens.iCnt) SEPARATOR ' '),
GROUP_CONCAT(CONCAT('result',(1 + units.iCnt + 10 * tens.iCnt), ' VARCHAR(255)') ORDER BY (1 + units.iCnt + 10 * tens.iCnt))
INTO sql1, sql2, sql3
FROM
(
SELECT MAX(count_name) as max_count_name
FROM
(
SELECT COUNT(name) as count_name
FROM some_table
GROUP BY position_id
) sub0
) sub1,
(SELECT 1 iCnt UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) units,
(SELECT 1 iCnt UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 UNION SELECT 7 UNION SELECT 8 UNION SELECT 9 UNION SELECT 0) tens
WHERE max_count_name >= (units.iCnt + 10 * tens.iCnt);
DROP TEMPORARY TABLE IF EXISTS temp1;
SET #sqlmain1 = CONCAT('CREATE TEMPORARY TABLE temp1(position_id INT, result0 VARCHAR(255), ', sql3, ')');
PREPARE stmt FROM #sqlmain1;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #sqlmain2 = CONCAT('INSERT INTO temp1 SELECT a0.position_id, MIN(a0.name) AS result0,', sql1, ' FROM some_table a0 ', sql2, ' GROUP BY a0.position_id ');
PREPARE stmt FROM #sqlmain2;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END;;
DELIMITER ;
You can then execute this and then select from the resulting temp table. Note that both statements must be done in the same SQL session, otherwise the temp table will have disappeared by the time you do the select:-
CALL stored_procedure_name();
SELECT * FROM temp1
Hopefully you can pass these both to Jasper.
I have the mysql statement below all works fine until I add (sub_total - discount_total) AS total,.
I'm guessing this is because I am calling on aliases that are not defined yet (sub_total and discount_total).
How do I define the aliases so it doesn't throw an error #1054 - Unknown column 'sub_total' in 'field list'
SELECT SQL_CALC_FOUND_ROWS
`bookings`.`id`,
`bookings`.`client_id`,
`bookings`.`vehicle_id`,
`bookings`.`vehicle_hire`,
(insurance_driver_1 + insurance_driver_2 + insurance_driver_3) AS insurance_total,
`bookings`.`bedding_qty`,
`bookings`.`bedding_price`,
`bookings`.`promo_discount`,
`bookings`.`promo_is_percent`,
`bookings`.`promo_code`,
(vehicle_hire + insurance_driver_1 + insurance_driver_2 + insurance_driver_3) + (bedding_qty * bedding_price) AS sub_total,
CASE
WHEN promo_is_percent = 1
THEN (((vehicle_hire + insurance_driver_1 + insurance_driver_2 + insurance_driver_3) + (bedding_qty * bedding_price)) / 100) * promo_discount
WHEN promo_is_percent = 0
THEN promo_discount
END as discount_total,
(sub_total - discount_total) AS total
FROM `bookings`
WHERE `bookings`.`status` = 'Quote'
ORDER BY `bookings`.`id` desc
LIMIT 0, 10
Thanks
Update
Working example below,
`SELECT SQL_CALC_FOUND_ROWS *,
(sub_total - discount_total) AS total
FROM (
SELECT
`bookings`.`id`,
`bookings`.`client_id`,
`bookings`.`vehicle_id`,
`bookings`.`vehicle_hire`,
(insurance_driver_1 + insurance_driver_2 + insurance_driver_3) AS insurance_total,
`bookings`.`bedding_qty`,
`bookings`.`bedding_price`,
`bookings`.`promo_discount`,
`bookings`.`promo_is_percent`,
`bookings`.`promo_code`,
(vehicle_hire + insurance_driver_1 + insurance_driver_2 + insurance_driver_3) + (bedding_qty * bedding_price) AS sub_total,
CASE
WHEN promo_is_percent = 1
THEN (((vehicle_hire + insurance_driver_1 + insurance_driver_2 + insurance_driver_3) + (bedding_qty * bedding_price)) / 100) * promo_discount
WHEN promo_is_percent = 0
THEN promo_discount
END as discount_total
FROM `bookings`
WHERE `bookings`.`status` = 'Quote'
) AS src
ORDER BY src.`id` desc
LIMIT 0, 10`
For your question I would solve it like so:
SELECT *,
(sub_total - discount_total) AS total
FROM (
SELECT SQL_CALC_FOUND_ROWS
`bookings`.`id`,
`bookings`.`client_id`,
`bookings`.`vehicle_id`,
`bookings`.`vehicle_hire`,
(insurance_driver_1 + insurance_driver_2 + insurance_driver_3) AS insurance_total,
`bookings`.`bedding_qty`,
`bookings`.`bedding_price`,
`bookings`.`promo_discount`,
`bookings`.`promo_is_percent`,
`bookings`.`promo_code`,
(vehicle_hire + insurance_driver_1 + insurance_driver_2 + insurance_driver_3) + (bedding_qty * bedding_price) AS sub_total,
CASE
WHEN promo_is_percent = 1
THEN (((vehicle_hire + insurance_driver_1 + insurance_driver_2 + insurance_driver_3) + (bedding_qty * bedding_price)) / 100) * promo_discount
WHEN promo_is_percent = 0
THEN promo_discount
END as discount_total
FROM `bookings`
WHERE `bookings`.`status` = 'Quote'
) AS src
ORDER BY src.`id` desc
LIMIT 0, 10
but this is only if you're definitely trying to avoid retyping the whole formula for your column.
have you also considered creating a calculated column in mysql?
SELECT *,
(SUM(`q6`) /
(`q1` *
(`q1` + `q2` + `q3` + `q4` + `q5` + `q6`) / 6)
* 100) AS percent
FROM table
WHERE field2 = 'xxx'
ORDER BY `percent` ASC
The code above is returning me the correct value for "percent" but only returns one row. I need it to return all the rows, 15 of them.
In my equation I am trying to get the value "percent" for each row and ORDER by the highest percent.
It seems, as soon as I add the sum() to my select statement, that it then returns only one row.
Assuming all comments, you should get something like:
SELECT
*, (SUM(`q6`) / (`q1` * (`q1` + `q2` + `q3` + `q4` + `q5` + `q6`) / 6) * 100)
AS percent
FROM table
WHERE field2 = 'xxx'
GROUP BY id ORDER BY `percent` DESC
In MySQL you can include columns in the select that are not aggregated and not in the group by. Because your query has an aggregation function, MySQL recognizes it as an aggregation query, and only returns one row.
Actually, you need to bring in the total value for a calculation on each row. Here is one way using a subquery in the select statement:
SELECT *,
(select sum(q6) as sumq6 from table where field2 = 'xxx') / (`q1` * (`q1` + `q2` + `q3` + `q4` + `q5` + `q6`) / 6)
* 100) AS percent
FROM table
WHERE field2 = 'xxx'
ORDER BY `percent` ASC;
Here is another way, using a cross join:
SELECT *,
(sumq6 / (`q1` * (`q1` + `q2` + `q3` + `q4` + `q5` + `q6`) / 6)
FROM table cross join
(select sum(q6) as sumq6 from table where field2 = 'xxx') as const
WHERE field2 = 'xxx'
ORDER BY `percent` ASC;
I have one problem with this query; I can't seem to get ((total + rec_host) / 2) AS total2 to work. How would I go about this procedure without doing:
((((rank_ur + rank_scs + rank_tsk + rank_csb + rank_vfm + rank_orr) / 6) + rec_host ) / 2)
Here's my Query:
SELECT host_name,
SUM(rank_ur) AS cnt1,
SUM(rank_scs) AS cnt2,
SUM(rank_tsk) AS cnt3,
SUM(rank_csb) AS cnt4,
SUM(rank_vfm) AS cnt5,
SUM(rank_orr) AS cnt6,
SUM(IF(rec_host = 1,1,0)) AS rh1,
SUM(IF(rec_host = 0,1,0)) AS rh2,
((rank_ur + rank_scs + rank_tsk + rank_csb + rank_vfm + rank_orr) / 6) AS total,
((total + rec_host) / 2) AS total2
FROM lhr_reviews
GROUP BY host_name
ORDER BY total
DESC LIMIT 0,10
Use a subquery like so:
SELECT
host_name,
cnt1,
cnt2,
cnt3,
cnt4,
cnt5,
cnt6,
rh1,
rh2,
total,
((total + rec_host) / 2) AS total2
FROM
(
SELECT host_name,
rec_host,
SUM(rank_ur) AS cnt1,
SUM(rank_scs) AS cnt2,
SUM(rank_tsk) AS cnt3,
SUM(rank_csb) AS cnt4,
SUM(rank_vfm) AS cnt5,
SUM(rank_orr) AS cnt6,
SUM(IF(rec_host = 1,1,0)) AS rh1,
SUM(IF(rec_host = 0,1,0)) AS rh2,
((rank_ur + rank_scs + rank_tsk +
rank_csb + rank_vfm + rank_orr
) / 6) AS total
FROM lhr_reviews
GROUP BY host_name, rec_host
) t
ORDER BY total
DESC LIMIT 0,10;
What you could do is this:
select x.*, ((x.total + rec_host) / 2) AS total2
from (
SELECT host_name, rec_host,
SUM(rank_ur) AS cnt1,
SUM(rank_scs) AS cnt2,
SUM(rank_tsk) AS cnt3,
SUM(rank_csb) AS cnt4,
SUM(rank_vfm) AS cnt5,
SUM(rank_orr) AS cnt6,
SUM(IF(rec_host = 1,1,0)) AS rh1,
SUM(IF(rec_host = 0,1,0)) AS rh2,
((rank_ur + rank_scs + rank_tsk + rank_csb + rank_vfm + rank_orr) / 6) AS total
FROM lhr_reviews
GROUP BY host_name
ORDER BY total
DESC LIMIT 0,10
) as x
;
You cannot use the column as an alias when the alias and other column are in the same level of SELECT. So you can use a derived query which lets you basically rename your columns and/or name any computed columns.Check on Rubens Farias and Rob Van Dam answer here
PS: will search for a better article to update the answer :)