How to sum subqueries in MySQL - mysql

I have the following MySQL statement that works perfectly. However I now need a total as designated in bold square brackets/parenthesis below.
SELECT f.fid, r.tid, r.tbd, r.tbc, r.tc, r.tname, r.category,
SUM(`time`) AS `totaltime`,
SUM(`points`) AS `points`,
(SELECT IFNULL(SUM(points),0) FROM Primes WHERE zid = r.zid AND wid = r.wid AND s_id = 38) AS `cl`,
(SELECT IFNULL(SUM(points),0) FROM Primes WHERE zid = r.zid AND wid = r.wid AND s_id = 34) AS `sp`,
**[sum points + climb + sprint] AS pointstotal**
FROM teams f
JOIN results r
ON f.wid = r.wid
WHERE r.rank <= 3 AND r.id = '254293' AND r.category = 'C'
AND r.fin = '1'
GROUP BY f.fid
ORDER BY pointstotal DESC
I have tried many different permutations etc and multiple references in other questions but I just cant get it to work.

The simplest way is to move your query into a subquery, and then add those columns.
SELECT fid, tid, tbd, tbc, tc, tname, category, totaltime,
points, cl, sp, points + cl + sp AS pointstotal
FROM (
SELECT f.fid, r.tid, r.tbd, r.tbc, r.tc, r.tname, r.category,
SUM(`time`) AS `totaltime`,
SUM(`points`) AS `points`,
(SELECT SUM(points) FROM Primes WHERE zid = r.zid AND wid = r.wid AND s_id = 38) AS `cl`,
(SELECT SUM(points) FROM Primes WHERE zid = r.zid AND wid = r.wid AND s_id = 34) AS `sp`
FROM teams f
JOIN results r
ON f.wid = r.wid
WHERE r.rank <= 3 AND r.id = '254293' AND r.category = 'C'
AND r.fin = '1'
GROUP BY f.fid
) AS x
ORDER BY pointstotal DESC
But I generally dislike correlated subqueries, I prefer joins, and then you don't need another level
SELECT SELECT f.fid, r.tid, r.tbd, r.tbc, r.tc, r.tname, r.category,
SUM(time) AS totaltime, SUM(points) AS points,
cl, sp, SUM(POINTS) + cl + sp AS totalpoints
FROM teams AS f
JOIN results AS r ON f.wid = r.wid
JOIN (
SELECT zid, wid, SUM(IF(s_id = 38, points, 0)) AS cl, SUM(IF(s_id = 34, points, 0)) AS sp
FROM Primes
GROUP BY zid, wid
) AS p ON p.zid = r.zid AND p.wid = r.wid
WHERE r.rank <= 3 AND r.id = '24293' AND r.category = 'C' AND r.fin = '1'
GROUP BY f.fid
ORDER BY pointstotal DESC

Related

how can i modify my query to not create duplicates after i have introduce the order_activities table?

Usually when joining between two tables where there is a one-to-many relationship there will be duplicates created
Which i am joining with order-activities table
query = f"""
SELECT {time_period_column_name},
store_id,
st.name as store_name,
shopper_id,
first_names,
last_name,
total_orders,
total_items_picked,
shopper_request_time,
shopper_accepted_time,
shopping_completed_time,
ROUND(total_shopping_minutes, 2) as total_shopping_minutes,
ROUND(min_shopping_minutes, 2) AS min_shopping_minutes,
ROUND(max_shopping_minutes, 2) AS max_shopping_minutes,
ROUND(average_shopping_minutes, 2) as average_shopping_minutes,
ROUND(total_shopping_minutes / total_orders, 2) as minutes_per_order,
ROUND(total_items_picked / total_shopping_minutes, 2) as shopping_efficiency,
ROUND(average_order_zar, 2) as average_order_zar
FROM (
SELECT o.shopper_id,
o.store_id,
{time_period_column_name},
Count(1) AS total_orders,
TIMEDIFF(Max(o.shopper_request_stop), Max(o.shopper_request_start)) AS shopper_request_time,
TIMEDIFF(Max(o.shopper_accepted_stop), Max(o.shopper_accepted_start)) AS shopper_accepted_time,
TIMEDIFF(Max(o.shopping_completed_stop), Max(o.shopping_completed_start)) AS shopping_completed_time,
Sum(order_items_picked) AS total_items_picked,
AVG(o.total) / 100.0 AS average_order_zar,
Min(shopping_minutes) AS min_shopping_minutes,
Max(shopping_minutes) AS max_shopping_minutes,
Avg(shopping_minutes) AS average_shopping_minutes,
Sum(shopping_minutes) AS total_shopping_minutes
FROM (
SELECT oo.shopper_id,
oo.store_id,
oo.total,
order_item_counts.order_items_picked,
date_format(oo.created_at, '{time_period_format_str}') AS {time_period_column_name},
IF(oa.status_code = 'shopper-request', oa.created_at, NULL) AS shopper_request_start,
IF(oa.prev_status_code = 'shopper-request', oa.created_at, NULL) AS shopper_request_stop,
IF(oa.status_code = 'shopper-accepted', oa.created_at, NULL) AS shopper_accepted_start,
IF(oa.prev_status_code = 'shopper-accepted', oa.created_at, NULL) AS shopper_accepted_stop,
IF(oa.status_code = 'shopping-completed', oa.created_at, NULL) AS shopping_completed_start,
IF(oa.prev_status_code = 'shopping-completed', oa.created_at, NULL) AS shopping_completed_stop,
(TIMESTAMPDIFF(MICROSECOND, oo.started_shopping_at,
oo.finished_shopping_at) / 6E7) AS shopping_minutes
FROM {self.market_db}.orders as oo
LEFT JOIN (
SELECT order_id, Sum(picked) as order_items_picked FROM {self.market_db}.order_items oi
WHERE oi.store_id IN ({store_ids_str})
GROUP BY oi.order_id
) order_item_counts ON oo.id = order_item_counts.order_id
LEFT JOIN {self.market_db}.order_activities as oa ON oo.id = oa.order_id
WHERE (oa.prev_status_code IN ('shopper-request',
'shopper-accepted',
'shopping',
'shopping-completed',
'collected',
'delivery')
OR oa.status_code IN ('shopper-request',
'shopper-accepted',
'shopping',
'shopping-completed',
'collected',
'delivery'))
AND oa.created_at >= '{start_date}'
AND oa.created_at < '{end_date}'
AND oo.started_shopping_at IS NOT NULL
AND oo.finished_shopping_at IS NOT NULL
AND oa.status_code <> oa.prev_status_code
AND oo.store_id IN ({store_ids_str})
) AS o
GROUP BY shopper_id, {time_period_column_name}, store_id
HAVING total_orders >= 1
) AS oc
INNER JOIN {self.users_db}.users us
ON us.id = oc.shopper_id
INNER JOIN {self.market_db}.stores st
ON st.id = oc.store_id
ORDER BY store_id DESC, {time_period_column_name} DESC, total_orders DESC;
"""
rows = self.run_market_query(query=query)
return rows

mysql: simpler and more efficient query for getting unique products

I would like to optimize my database query but I am not sure how to do this.
I want to get a list of stores' products opinions, ordered by opinion dates (from newest to oldest ones), but the products need to be unique.
For example, there are 3 users: U1, U2, U3.
There are 2 stores in the city:
S1 (with products P11, P12, P13, P14)
S2 (with products P21, P22, P23, P24)
Users added some opinions (the newest on the top, the oldest on the bottom):
U1: P22
U1: P13
U2: P21
U3: P13
U2: P23
U1: P23
What I want to achieve is:
U1: P22
U1: P13
U2: P21
U2: P23
The query I created is very long and a bit complicated. Could I simplify it somehow?
$sql_query = "
SELECT a.*
, b.name AS 'store_name'
, b.city AS 'store_city'
, c.name AS 'product_name'
FROM `app_products_opinion` AS a
JOIN `app_products_stores` AS b
ON a.store_ID = b.ID
JOIN `app_products` AS c
ON a.product_ID = c.ID
WHERE a.created_on IN
(
SELECT max(created_on) as created_on
FROM app_products_opinion
WHERE show_on_list='1' AND (added_by='".$_SESSION["CMSUserID"]."' OR status = '1')
GROUP by product_ID
ORDER by created_on DESC
)
AND a.show_on_list='1'
AND a.store_ID='".$id_store['ID']."' $addtosql
AND a.photo != ''
AND (a.added_by='".$_SESSION["CMSUserID"]."' OR a.status='1')
ORDER BY a.created_on DESC
";
You could try grouping by product_id and also joining by product_ID and date
(simplified code)
SELECT a.user_id, a.product_ID
from app_products_opinion a
INNER JOIN (
SELECT product_ID, max(created_on) as created_on
FROM app_products_opinion
WHERE show_on_list='1' AND (added_by='".$_SESSION["CMSUserID"]."' OR status = '1')
GROUP by product_ID
ORDER by created_on DESC
) t on a.created_on = t.created_on
AND a.product_ID = t.product_ID
I don't know if you think it's simpler (and ignoring, $addtosql) but you could do this...
SELECT a.*
, b.name AS 'store_name'
, b.city AS 'store_city'
, c.name AS 'product_name'
FROM `app_products_opinion` AS a
JOIN `app_products_stores` AS b
ON a.store_ID = b.ID
JOIN `app_products` AS c
ON a.product_ID = c.ID
JOIN
(
SELECT product_id
, max(created_on) created_on
FROM app_products_opinion
WHERE show_on_list = 1
AND (added_by = 'M' OR status = 1)
GROUP
by product_ID
) x
ON a.created_on = x.created_on
AND a.product_id = x.product_id
AND a.show_on_list = 1
AND a.store_ID = 'N'
AND a.photo != ''
AND (a.added_by = 'Z' OR a.status = 1)

mysql : query three times on the same table

I am currently writing a query.
Retrieves information from users, posts, and additional information tables in posts (post_views_info).
SELECT
u.email,
u.user_nm,
p.pid,
p.post_ttl,
p.date,
p.ref_level,
p.ref_origin,
p.ref_step,
date(p.date) = date(now()) AS is_today,
(SELECT category_path FROM post_category WHERE category_id = p.category_id) as category_full_path,
(SELECT COUNT(*) FROM post_status_info AS sub_i WHERE sub_i.pid = p.pid AND sub_i.status = 'A') AS recommendCount,
(SELECT COUNT(*) FROM post_status_info AS sub_i WHERE sub_i.pid = p.pid AND sub_i.status = 'B') AS oppositeCount,
(SELECT COUNT(*) FROM post_status_info AS sub_i WHERE sub_i.pid = p.pid AND sub_i.status = 'C') AS reportCount
FROM
(
SELECT *
FROM post as p
WHERE
p.is_enable = 1
ORDER BY
p.ref_origin DESC,
p.ref_step ASC
) as p,
user AS u
WHERE
p.uid = u.uid
ORDER BY
ref_origin DESC,
ref_step ASC
In the above query, we query the same table three times to get the number of posts 'A', 'B', 'C'.
To solve this problem, I changed the query as follows.
SELECT
u.email,
u.user_nm,
p.pid,
p.post_ttl,
p.date,
p.ref_level,
p.ref_origin,
psi.reportCount,
psi.recommendCount,
psi.oppositeCount,
p.ref_step,
date(p.date) = date(now()) AS is_today,
(SELECT category_path FROM post_category WHERE category_id = p.category_id) as category_full_path
FROM
user AS u,
(
SELECT *
FROM post as p
WHERE
p.is_enable = 1
ORDER BY
p.ref_origin DESC,
p.ref_step ASC
LIMIT 0, 15
) as p left join
(
SELECT
pid,
COUNT(if(status = 'A', 1, null)) AS reportCount,
COUNT(if(status = 'B', 1, null)) AS recommendCount,
COUNT(if(status = 'C', 1, null)) AS oppositeCount
FROM post_status_info
group by pid
) AS psi
on
psi.pid = p.pid
WHERE
p.uid = u.uid
ORDER BY
ref_origin DESC,
ref_step ASC
I think it would be better to query the same table three times.
Which code is better in terms of performance?
Thanks.
I think second option is more fruitful in terms of performance. Because here we have less number of queries to execute.
You can also do it by using CASE.
SELECT
u.email,
u.user_nm,
p.pid,
p.post_ttl,
p.date,
p.ref_level,
p.ref_origin,
p.ref_step,
date(p.date) = date(now()) AS is_today,
(SELECT category_path FROM post_category WHERE category_id = p.category_id) as category_full_path,
(SUM(CASE WHEN sub_i.status = 'A' THEN 1 ELSE 0 END)) AS recommendCount,
(SUM(CASE WHEN sub_i.status = 'B' THEN 1 ELSE 0 END)) AS oppositeCount,
(SUM(CASE WHEN sub_i.status = 'C' THEN 1 ELSE 0 END)) AS reportCount
FROM
(
SELECT *
FROM post as p
WHERE
p.is_enable = 1
ORDER BY
p.ref_origin DESC,
p.ref_step ASC
) as p,
INNER JOIN user AS u ON u.uid = p.uid
INNER JOIN post_status_info as sub_i ON p.pid = sub_i.pid
GROUP BY p.pid
ORDER BY
ref_origin DESC,
ref_step ASC

Buyers structure by registration date query optimisation

I would like to show buyers structure by their registration date e.g.:
H12016 10.000 buyers
from which
2.000 registered in H12014
4.000 registered in H22014
etc.
I have two queries for that:
Number 1 (buyers from H12016 (about 50k records)):
SELECT DISTINCT
r.idUsera as id_usera
FROM
rezerwacje r
WHERE
r.dataZalozenia between '2016-01-01' and '2016-07-01'
and r.`status` = 'zabookowana'
ORDER BY
id_usera
Number 2 (users_ids and their registration (insert) date (about 3,8M users)):
SELECT
m.user_id,
date(m.action_date) as data_insert
FROM
mwids m
WHERE
m.`type` = 'insert'
Both queries separately run fine, but when I try to combine them like so:
SELECT DISTINCT
r.idUsera as id_usera,
t1.data_insert
FROM
rezerwacje r
LEFT JOIN
(
SELECT
m.user_id,
date(m.action_date) as data_insert
FROM
mwids m
WHERE
m.`type` = 'insert'
) t1 ON t1.user_id = r.idUsera
WHERE
r.dataZalozenia between '2016-01-01' and '2016-07-01'
and r.`status` = 'zabookowana'
ORDER BY
id_usera
this query runs "indefinetely" and I have to kill it after some time.
I do not belive it should run that long. If the query Number 2 was smaller i.e. about 1M users I could combine results in Excel in matter of seconds. So why is it not possible inside the database? What am I doing wrong?
SELECT DISTINCT
r.idUsera as id_usera,
t1.data_insert
FROM
rezerwacje r
INNER JOIN
(
SELECT
m.user_id,
date(m.action_date) as data_insert
FROM
mwids m
WHERE
m.`type` = 'insert'
) t1 ON t1.user_id = r.idUsera
WHERE
r.dataZalozenia between '2016-01-01' and '2016-07-01'
and r.`status` = 'zabookowana'
ORDER BY
id_usera
Try with INNER JOIN.
Query 1 needs
INDEX(status, dataZalozenia, id_usera)
Query 3: Rewrite thus:
If there is only one row in mwids for 'insert' per user:
SELECT r.idUsera as id_usera, DATE(m.action_date) AS data_insert
FROM rezerwacje r
LEFT JOIN mwids m ON m.user_id = r.idUsera
AND m.`type` = 'insert'
WHERE r.dataZalozenia >= '2016-01-01'
AND r.dataZalozenia < '2016-01-01' + 12 MONTH
and r.`status` = 'zabookowana'
ORDER BY r.idUsera
with
INDEX(status, dataZalozenia, isUsera) -- on r
INDEX(type, user_id, action_date) -- on m
If there can be multiple rows, do this:
SELECT r.idUsera as id_usera,
( SELECT DATE(m.action_date)
FROM mwids m
WHERE m.user_id = r.idUsera
AND m.`type` = 'insert'
LIMIT 1
) AS data_insert
FROM rezerwacje r
LEFT JOIN mwids m ON m.user_id = r.idUsera
AND m.`type` = 'insert'
WHERE r.dataZalozenia >= '2016-01-01'
AND r.dataZalozenia < '2016-01-01' + 12 MONTH
and r.`status` = 'zabookowana'
ORDER BY r.idUsera
But you will be getting a random action_date. So maybe you want MIN() or MAX()?

Joining 2 SQL queries into 1 result

I have two SQL queries that I would like to join into one:
select d.full_month, COUNT(*) amount
from fact_ticket t
join dim_queue q on t.queue_id = q.queue_id
join vt_scopes s on t.scope_id = s.scope_id
join dim_date d on t.create_date_id = d.date_id
where q.name = 'Support'
and year(GETDATE()) = YEAR(t.create_date)
and s.statusname not in ('discarded', 'closed')
group by d.full_month
order by 1;
and
select d.full_month, COUNT(*) amount
from fact_ticket t
join dim_queue q on t.queue_id = q.queue_id
join vt_scopes s on t.scope_id = s.scope_id
join dim_date d on t.create_date_id = d.date_id
where q.name = 'Support'
and year(GETDATE()) = YEAR(t.create_date)
and s.statusname in ('closed')
group by d.full_month
order by 1;
Both gives me now a result with a date column and an amount column, but I would like to get everything in one query where I would get date, amount 1, amount 2.
Is there an easy to do this?
You can use below query-
SELECT d.full_month,
COUNT(IF(s.statusname NOT IN ('discarded', 'closed'),1,NULL)) amount1,
COUNT(IF(s.statusname IN ('closed'),1,NULL)) amount2
FROM fact_ticket t
JOIN dim_queue q ON t.queue_id = q.queue_id
JOIN vt_scopes s ON t.scope_id = s.scope_id
JOIN dim_date d ON t.create_date_id = d.date_id
WHERE q.name = 'Support'
AND YEAR(GETDATE()) = YEAR(t.create_date)
GROUP BY d.full_month
ORDER BY 1;
2nd Edition: Even you can get benefit of index if exist on create_date by below query-
SELECT d.full_month,
COUNT(IF(s.statusname NOT IN ('discarded', 'closed'),1,NULL)) amount1,
COUNT(IF(s.statusname IN ('closed'),1,NULL)) amount2
FROM fact_ticket t
JOIN dim_queue q ON t.queue_id = q.queue_id
JOIN vt_scopes s ON t.scope_id = s.scope_id
JOIN dim_date d ON t.create_date_id = d.date_id
WHERE q.name = 'Support'
AND t.create_date>= DATE_FORMAT(NOW(),'%Y-01-01 00:00:00') AND t.create_date <= DATE_FORMAT(NOW(),'%Y-12-31 23:59:59');
GROUP BY d.full_month
ORDER BY 1;
Another way using sum function
SELECT
d.full_month,
SUM(s.statusname NOT IN ('discarded', 'closed')) amount,
SUM(s.statusname = 'closed') amount_closed
FROM
fact_ticket t
JOIN dim_queue q
ON t.queue_id = q.queue_id
JOIN vt_scopes s
ON t.scope_id = s.scope_id
JOIN dim_date d
ON t.create_date_id = d.date_id
WHERE q.name = 'Support'
AND YEAR(GETDATE ()) = YEAR(t.create_date)
GROUP BY d.full_month
ORDER BY 1 ;