Update a column in mysql based on where statement with subselects - mysql

I have this pseudo SQL code for what I want to achieve:
UPDATE orders o
SET o.datePaid = null
WHERE
(
SELECT SUM(amount)
FROM transactions t
WHERE t.orderId = o.id
AND t.status = 'success'
AND t.type = 'refund'
)
>=
(
SELECT SUM(amount)
FROM transactions t
WHERE t.orderId = o.id
AND t.status = 'success'
AND t.type IN ('purchase', 'capture')
)
How would I do this in SQL?

I think your approach is interesting. Here is a more concise method:
UPDATE orders o
SET o.datePaid = null
WHERE (SELECT SUM(CASE WHEN t.type = 'refund' THEN amount
WHEN t.type IN ('purchase', 'capture') THEN -amount
END)
FROM transactions t
WHERE t.orderId = o.id AND
t.status = 'success'
) > 0;

Your query works fine as is. However it can be more optimally written using MySQL multi-table UPDATE syntax:
UPDATE orders o
LEFT JOIN (SELECT orderId,
COALESCE(SUM(CASE WHEN type = 'refund' THEN amount END), 0) AS refunds,
COALESCE(SUM(CASE WHEN type IN ('purchase', 'capture') THEN amount END), 0) AS pc
FROM transactions
WHERE status = 'success'
GROUP BY orderId) t ON t.orderId = o.id
SET o.datePaid = NULL
WHERE t.refunds > t.pc
Demo on dbfiddle (includes your query working as well)

Your code would probably work just as it is. Give it a try.
You could also optimize the query to avoid the need for two subqueries by using a JOIN and conditional aggregation in a single subquery:
UPDATE orders o
INNER JOIN (
SELECT orderId
FROM transactions
WHERE
status = 'success'
AND type IN ('success', 'purchase', 'capture') -- this condition might be superfuous
GROUP BY o.id
HAVING
SUM(CASE WHEN type = 'success' THEN amount ELSE 0 END)
>= SUM(CASE WHEN type IN ('purchase', 'capture') THEN amount ELSE 0 END)
) t ON t.orderId = o.id
SET o.datePaid = null
Note: WHERE condition AND type IN ('success', 'purchase', 'capture') is superfluous if that list of 3 values represents all the possible values.

Related

Query optimization needed because of too many sub queries and sub query dependency in where conditions

I'm writing code for the production report.
I had written this query
SELECT
P.*,
(
SELECT
COUNT(id) AS cnt
FROM
bales
WHERE
create_date < '2019-11-01' AND product_id = P.id AND(TYPE = 'bale' OR TYPE = 'bag')
) AS before_prod,
(
SELECT
COUNT(id) AS cnt
FROM
bales
WHERE
(
dispatched = '0' OR disp_bunch = '0'
) AND dispatch_date < '2019-11-01' AND product_id = P.id AND(TYPE = 'bale' OR TYPE = 'bag')
) AS before_dispatched,
(
SELECT
COUNT(id) AS cnt
FROM
bales
WHERE
create_date BETWEEN '2019-11-01' AND '2019-11-06' AND product_id = P.id AND(TYPE = 'bale' OR TYPE = 'bag')
) AS production,
(
SELECT
COUNT(id) AS cnt
FROM
bales
WHERE
(
dispatched = '0' OR disp_bunch = '0'
) AND dispatch_date BETWEEN '2019-11-01' AND '2019-11-06' AND product_id = P.id AND(TYPE = 'bale' OR TYPE = 'bag')
) AS production_dispatched,
C.name AS category_name
FROM
products P
INNER JOIN category C ON
C.id = P.category
This query is working but as I have too many records in all tables it takes too much time.
also, I need only records where before_prod, before_dispatched, production, production_dispatched all these subquery results should be greater than 0.
I tried to use having clause but it also takes too much time.
I have also tried php for loop, * LOGIC: first all products than in for loop its production. but it was much slower.*
How can I optimize my query?
You can use join instead and select case to sum your data that matches your conditions.
select p.*, t.*
from products p
inner join (
select t2.id, sum(case when create_date < '2019-11-01' then 1 else 0 end) as before_prod
, sum(case when (dispatched = '0' or disp_bunch = '0') and create_date < '2019-11-01' then 1 else 0 end) as before_dispatched
, sum(case when create_date between '2019-11-01' and '2019-11-06' then 1 else 0 end) as production
, sum(case when (dispatched = '0' or disp_bunch = '0') and create_date between '2019-11-01' and '2019-11-06' then 1 else 0 end) as production_dispatched
from bales t1
inner join product t2 on t2.id= t1.product_id
inner join category t3 on t3.id = t2.category
where t1.TYPE in ('bale', 'bag')
group by t2.id) t
on t.id = p.id

Local variable sum previous columns

I am trying to use #variable1 + #variable2 into a query but is actually given 0 as result.
MariaDB
Server Version: 10.2.21
set #start_at = '2019-01-01';
set #end_at = '2019-01-16';
set #receivable = 0;
set #invoiced = 0;
SELECT DISTINCT Customer.custnr 'Customer Number',
Address.name 'Name',
#receivable := sum(case
WHEN [condition1 <= #start_at]
AND Transactions.`key` not in [subquery]
THEN Transactions.amount
ELSE 0 END) 'Account Receivable',
#invoiced := sum(case
WHEN [condition1 between #start_at and #end_at]
AND [condition2]
AND [condition3]
AND Transactions.`key` not in [subquery]
THEN Transactions.amount
ELSE 0 END) 'Invoiced',
#receivable + #invoiced 'Total'
FROM LocalCust
INNER JOIN Customer
on Customer.`key` = LocalCust.customerkey
INNER JOIN Address
on Address.`key` = Customer.addresskey
INNER JOIN Location
on Location.`key` = LocalCust.localkey
INNER JOIN Transactions
on Transactions.localcustkey = LocalCust.`Key`
GROUP BY Transactions.localcustkey;
Result:
Use a subquery and don't use variables at all:
SELECT x.*, (Account_Receivable + Invoice) as Total
FROM (SELECT c.custnr as Customer_Number, a.name,
sum(case when condition1 <= #start_at and
t.`key` not in [subquery]
then t.amount
else 0
end) as Account_Receivable,
sum(case when condition1 between #start_at and #end_at and
[condition2] and
[condition3] and
t.`key` not in [subquery]
then t.amount
else 0
end) as Invoiced
FROM LocalCust lc JOIN
Customer c
on c.`key` = lc.customerkey JOIN
Address a
on a.`key` = c.addresskey JOIN
Location l
on l.`key` = lc.localkey join
Transactions t
on t.localcustkey = lc.`Key`
GROUP BY c.custnr, a.name
) x;
Notes:
Table aliases make the query easier to write and to read.
SELECT DISTINCT is almost never needed with GROUP BY.
The GROUP BY keys should match the unaggregated columns in the SELECT.
Choose column aliases that do not need to be escaped. That is, no spaces.
You can not just put #receivable, #invoiced and #receivable + #invoiced in the same select statement. (They will not store the value in order. They will be executed at the same time.)
First, You need to store the values in #receivable, #invoiced then use a subquery to calculate the total:
set #start_at = '2019-01-01';
set #end_at = '2019-01-16';
set #receivable = 0;
set #invoiced = 0;
SELECT *, A.[Account Receivable] + A.[Invoiced] AS TOTAL FROM (
SELECT DISTINCT Customer.custnr 'Customer Number',
Address.name 'Name',
#receivable := sum(case
WHEN [condition1 <= #start_at]
AND Transactions.`key` not in [subquery]
THEN Transactions.amount
ELSE 0 END) 'Account Receivable',
#invoiced := sum(case
WHEN [condition1 between #start_at and #end_at]
AND [condition2]
AND [condition3]
AND Transactions.`key` not in [subquery]
THEN Transactions.amount
ELSE 0 END) 'Invoiced'
FROM LocalCust
INNER JOIN Customer
on Customer.`key` = LocalCust.customerkey
INNER JOIN Address
on Address.`key` = Customer.addresskey
INNER JOIN Location
on Location.`key` = LocalCust.localkey
INNER JOIN Transactions
on Transactions.localcustkey = LocalCust.`Key`
GROUP BY Transactions.localcustkey) A;

shows mysql records twice because of inner joining

In below query (Mentors) are 13 which shows me 26, while (SchoolSupervisor) are 5 which shows me 10 which is wrong. it is because of the Evidence which having 2 evidance, because of 2 evidence the Mentors & SchoolSupervisor values shows me double.
please help me out.
Query:
select t.c_id,t.province,t.district,t.cohort,t.duration,t.venue,t.v_date,t.review_level, t.activity,
SUM(CASE WHEN pr.p_association = "Mentor" THEN 1 ELSE 0 END) as Mentor,
SUM(CASE WHEN pr.p_association = "School Supervisor" THEN 1 ELSE 0 END) as SchoolSupervisor,
(CASE WHEN count(file_id) > 0 THEN "Yes" ELSE "No" END) as evidence
FROM review_m t , review_attndnce ra
LEFT JOIN participant_registration AS pr ON pr.p_id = ra.p_id
LEFT JOIN review_files AS rf ON rf.training_id = ra.c_id
WHERE 1=1 AND t.c_id = ra.c_id
group by t.c_id, ra.c_id order by t.c_id desc
enter image description here
You may perform the aggregations in a separate subquery, and then join to it:
SELECT
t.c_id,
t.province,
t.district,
t.cohort,
t.duration,
t.venue,
t.v_date,
t.review_level,
t.activity,
pr.Mentor,
pr.SchoolSupervisor,
rf.evidence
FROM review_m t
INNER JOIN review_attndnce ra
ON t.c_id = ra.c_id
LEFT JOIN
(
SELECT
p_id,
COUNT(CASE WHEN p_association = 'Mentor' THEN 1 END) AS Mentor,
COUNT(CASE WHEN p_association = 'School Supervisor' THEN 1 END) AS SchoolSupervisor,
FROM participant_registration
GROUP BY p_id
) pr
ON pr.p_id = ra.p_id
LEFT JOIN
(
SELECT
training_id,
CASE WHEN COUNT(file_id) > 0 THEN 'Yes' ELSE 'No' END AS evidence
FROM review_files
GROUP BY training_id
) rf
ON rf.training_id = ra.c_id
ORDER BY
t.c_id DESC;
Note that this also fixes another problem your query had, which was that you were selecting many columns which did not appear in the GROUP BY clause. Under this refactor, there is nothing wrong with your current select, because the aggregation take place in a separate subquery.
try adding this to the WHERE part of your query
AND pr.p_id IS NOT NULL AND rf.training_id IS NOT NULL
You can add a group by pr.p_id to remove the duplicate records there. Since, the group by on pr is not present as of now, there might be multiple records of same p_id for same ra
group by t.c_id, ra.c_id, pr.p_id order by t.c_id desc

MySQL select query, adding max value from another table

Trying to add another column with a new table, would appreciate any help/suggestions!
Current query:
SELECT parent.name AS parentname, a.name AS accname,
parent.code AS parentcode, a.code AS acccode,
parent.guid AS parentguid, a.guid AS accguid,
a.account_type AS accttype,
sum(case when date_format(post_date, '%Y-%m-%d') <= '2017-12-31' then (s.value_num/s.value_denom) else '0' end) AS 'value2017-12-31',
sum(case when date_format(post_date, '%Y-%m-%d') <= '2018-01-25' then (s.value_num/s.value_denom) else '0' end) AS 'value2018-01-25'
FROM transactions AS t INNER JOIN splits AS s ON s.tx_guid = t.guid
INNER JOIN accounts AS a ON a.guid = s.account_guid
INNER JOIN accounts AS parent ON parent.guid = a.parent_guid
WHERE a.hidden = 0 AND a.account_type NOT IN ('INCOME', 'EXPENSE')
AND parent.name <>''
AND a.guid = '3f3fc442a98225f481bb72e0fd526cbb'
GROUP by accname, parentname ORDER by acccode
And that works fine, gives 1 row of results. I'd now like to add another column to the results, I believe a left outer join from the prices table of the record that is the closest date before 2017-12-31. My attempt:
SELECT parent.name AS parentname, a.name AS accname,
parent.code AS parentcode, a.code AS acccode,
parent.guid AS parentguid, a.guid AS accguid,
a.account_type AS accttype,
sum(case when date_format(post_date, '%Y-%m-%d') <= '2017-12-31' then (s.value_num/s.value_denom) else '0' end) AS 'value2017-12-31',
sum(case when date_format(post_date, '%Y-%m-%d') <= '2018-01-25' then (s.value_num/s.value_denom) else '0' end) AS 'value2018-01-25',
MAX(case when date_format(p.date, '%Y-%m-%d') <= '2017-12-31' then (p.value_num/p.value_denom) else '0' end) AS 'price2017-12-31'
FROM transactions AS t INNER JOIN splits AS s ON s.tx_guid = t.guid
INNER JOIN accounts AS a ON a.guid = s.account_guid
INNER JOIN accounts AS parent ON parent.guid = a.parent_guid
LEFT OUTER JOIN prices as p ON p.commodity_guid = a.commodity_guid
WHERE a.hidden = 0 AND a.account_type NOT IN ('INCOME', 'EXPENSE')
AND parent.name <>''
AND a.guid = '3f3fc442a98225f481bb72e0fd526cbb'
GROUP by accname, parentname ORDER by acccode
It's obviously incorrect as it's now changing the values of value2017-12-31 and value2018-01-25. I get the price correctly using a separate query:
SELECT p.value_num/p.value_denom as calcprice
FROM `prices` as p
WHERE commodity_guid = '77be249d12d9889e90f08dde7c671eb0'
AND date_format(p.date, '%Y-%m-%d') <= '2017-12-31'
order
by date DESC
limit 1
Is there any way to combine them rather than using temp tables?

MySQL query taking too much time

query taking 1 minute to fetch results
SELECT
`jp`.`id`,
`jp`.`title` AS game_title,
`jp`.`game_type`,
`jp`.`state_abb` AS game_state,
`jp`.`location` AS game_city,
`jp`.`zipcode` AS game_zipcode,
`jp`.`modified_on`,
`jp`.`posted_on`,
`jp`.`game_referal_amount`,
`jp`.`games_referal_amount_type`,
`jp`.`status`,
`jp`.`is_flaged`,
`u`.`id` AS employer_id,
`u`.`email` AS employer_email,
`u`.`name` AS employer_name,
`jf`.`name` AS game_function,
`jp`.`game_freeze_status`,
`jp`.`game_statistics`,
`jp`.`ats_value`,
`jp`.`integration_id`,
`u`.`account_manager_id`,
`jp`.`model_game`,
`jp`.`group_id`,
(CASE
WHEN jp.group_id != '0' THEN gm.group_name
ELSE 'NA'
END) AS group_name,
`jp`.`priority_game`,
(CASE
WHEN jp.country != 'US' THEN jp.country_name
ELSE ''
END) AS game_country,
IFNULL((CASE
WHEN
`jp`.`account_manager_id` IS NULL
OR `jp`.`account_manager_id` = 0
THEN
(SELECT
(CASE
WHEN
account_manager_id IS NULL
OR account_manager_id = 0
THEN
`u`.`account_manager_id`
ELSE account_manager_id
END) AS account_manager_id
FROM
user_user
WHERE
id = (SELECT
user_id
FROM
game_user_assigned
WHERE
game_id = `jp`.`id`
LIMIT 1))
ELSE `jp`.`account_manager_id`
END),
`u`.`account_manager_id`) AS acc,
(SELECT
COUNT(recach_limit_id)
FROM
recach_limit
WHERE
recach_limit = '1'
AND recach_limit_game_id = rpr.recach_limit_game_id) AS somewhatgame,
(SELECT
COUNT(recach_limit_id)
FROM
recach_limit
WHERE
recach_limit = '2'
AND recach_limit_game_id = rpr.recach_limit_game_id) AS verygamecommitted,
(SELECT
COUNT(recach_limit_id)
FROM
recach_limit
WHERE
recach_limit = '3'
AND recach_limit_game_id = rpr.recach_limit_game_id) AS notgame,
(SELECT
COUNT(joa.id) AS applicationcount
FROM
game_refer_to_member jrmm
INNER JOIN
game_refer jrr ON jrr.id = jrmm.rid
INNER JOIN
game_applied joa ON jrmm.id = joa.referred_by
WHERE
jrmm.STATUS = '1'
AND jrr.referby_user_id IN (SELECT
ab_testing_user_id
FROM
ab_testing)
AND joa.game_post_id = rpr.recach_limit_game_id
AND (rpr.recach_limit = 1
OR rpr.recach_limit = 2)) AS gamecount
FROM
(`game_post` AS jp)
JOIN
`user_info` AS u ON `jp`.`user_user_id` = `u`.`id`
JOIN
`game_functional` jf ON `jp`.`game_functional_id` = `jf`.`id`
LEFT JOIN
`group_musesm` gm ON `gm`.`group_id` = `jp`.`group_id`
LEFT JOIN
`recach_limit` rpr ON `jp`.`id` = `rpr`.`recach_limit_game_id`
WHERE
`jp`.`status` != '3'
GROUP BY `jp`.`id`
ORDER BY `posted_on` DESC
LIMIT 10
I would first suggest not nesting select statements because this will cause an n^x performance hit on every xth level and I see at least 3 levels of selects inside this query.
Add index
INDEX(status, posted_on)
Move LIMIT inside
Then, instead of saying
FROM (`game_post` AS jp)
say
FROM ( SELECT id FROM game_post
WHERE status != 3
ORDER BY posted_on DESC
LIMIT 10 ) AS ids
JOIN game_post AS jp USING(id)
(I am assuming that the PK of jp is (id)?)
That should efficiently use the new index to get the 10 ids needed. Then it will reach back into game_post to get the other columns.
LEFT
Also, don't say LEFT unless you need it. It costs something to generate NULLs that you may not be needing.
Is GROUP BY necessary?
If you remove the GROUP BY, does it show dup ids? The above changes may have eliminated the need.
IN(SELECT) may optimize poorly
Change
AND jrr.referby_user_id IN ( SELECT ab_testing_user_id
FROM ab_testing )
to
AND EXISTS ( SELECT * FROM ab_testing
WHERE ab_testing_user_id = jrr.referby_user_id )
(This change may or may not help, depending on the version you are running.)
More
Please provide EXPLAIN SELECT if you need further assistance.