Joining tables in MySQL where columns are summed - mysql

I am trying to join 3 tables. Two of the tables I am taking sums of a column. I want to apply conditions on the sums but am not producing the result I want with the below script. The sums are not summing correctly.
SELECT
account_list.Account_ID,
account_list.Account_Name,
account_list.Short_Name,
account_list.Trader,
account_list.Status,
account_list.Notes,
sum(account_commissions.Commission),
sum(connection_cost.Monthly_Cost)
FROM
account_commissions
Join
connection_cost
ON
account_commissions.Account_ID = connection_cost.Account_ID
AND
connection_cost.Cost_Date > '2013-06-01'
AND
account_commissions.TDate > '2013-06-01'
Right Join
account_list
ON
account_list.Account_ID = connection_cost.Account_ID
WHERE
account_list.status = 'Active'
GROUP BY
Account_ID;
The conditions I want on the sums are:
sum account_commissions.Commission where account_commissions.TDate > '2013-06-01 Group BY Account_ID
and
sum connection_cost.Monthy_Cost where connection_cost.Date > '2013-06-01' Group BY Account_ID.
I tried to achieve that using the above AND statements but it is not computing correctly. Any help on how to apply these conditions to the sum columns would be appreciated.

I've changed to a LEFT-JOIN as it appears you want all account list entries, and any corresponding summation of costs and commissions per account. So, the JOINs are based on sum() of each table individually, but grouped by account, THEN joined back to the main account list table.
SELECT
AL.Account_ID,
AL.Account_Name,
AL.Short_Name,
AL.Trader,
AL.Status,
AL.Notes,
coalesce( preSumCC.CC_Costs, 0 ) as MonthlyCosts,
coalesce( preSumComm.AC_Commission, 0 ) as Commissions
FROM
account_list AL
LEFT JOIN ( SELECT CC.Account_ID,
SUM( CC.Monthly_Cost ) CC_Costs
FROM
connection_cost CC
where
CC.Cost_Date > '2013-06-01'
group by
CC.Account_ID ) preSumCC
ON AL.Account_ID = preSumCC.Account_ID
LEFT JOIN ( select AC.Account_ID,
SUM( AC.Commission ) AC_Commission
FROM
account_commissions AC
where
AC.TDate > '2013-06-01'
group by
AC.Account_ID ) preSumComm
ON AL.Account_ID = preSumComm.Account_ID

You can create a conditional SUM in MySQL using IF:
SUM(IF(account_commisions.TDate >'2013-01-01',account_commissions_Commission, 0))
To be more portable, you should use CASE, as IF is not part of the SQL standard. I tend to find IF more readable though.

Related

Conditionally counting while also grouping by

I am trying to join two tables
ad_data_grouped
adID, adDate (date), totalViews
This is data that has already been grouped by both adID and adDate.
The second table is
leads
leadID, DateOfBirth, adID, state, createdAt(dateTime)
What I'm struggling with is joining these two tables so I can have a column that counts the number of leads when it shares the same adID and where the adDate = createdAt
The problem I'm running into is that when the counts are all the same for all groupings of adID....I have a few other things I'm trying to do, but it's based on similar similar conditional counting.
Query:(I know the temp table is probably overkill, but I'm trying to break this up into small pieces where I can understand what each piece does)
CREATE TEMPORARY TABLE ad_stats_grouped
SELECT * FROM `ad_stats`
LIMIT 0;
INSERT INTO ad_stats_grouped(AdID, adDate, DailyViews)
SELECT
AdID,
adDate,
sum(DailyViews)
FROM `ad_stats`
GROUP BY adID, adDate;
SELECT
ad_stats_grouped.adID,
ad_stats_grouped.adDate,
COUNT(case when ad_stats_grouped.adDate = Date(Leads.CreatedAt) THEN 1 ELSE 0 END)
FROM `ad_stats_grouped` INNER JOIN `LEADS` ON
ad_stats_grouped.adID = Leads.AdID
GROUP BY adID, adDate;
The problem with your original query is the logic in the COUNT(). This aggregate functions takes in account all non-null values, so it counts 0 and 1s. One solution would be to change COUNT() to SUM().
But I think that the query can be furtermore improved by moving the date condition on the date to the on part of a left join:
select
g.adid,
g.addate,
count(l.adid)
from `ad_stats_grouped` g
left join `leads` l
on g.adid = l.adid
and l.createdat >= g.addate
and l.createdat < g.ad_stats + interval 1 day
group by g.adid, g.addate;

Getting all value from every month, put zero if no data of that month

i'm trying to get data for each month, if there is no data found for a particular month, I will put zero. I already created a calendar table so I can left join it, but I still can't get zero.
Here's my query
SELECT calendar.month, IFNULL(SUM(transaction_payment.total),0) AS total
FROM `transaction`
JOIN `transaction_payment` ON `transaction_payment`.`trans_id` =
`transaction`.`trans_id`
LEFT JOIN `calendar` ON MONTH(transaction.date_created) = calendar.month
WHERE`date_created` LIKE '2017%' ESCAPE '!'
GROUP BY calendar.month
ORDER BY `date_created` ASC
the value in my calendar tables are 1-12(Jan-Dec) int
Result should be something like this
month total
1 0
2 20
3 0
4 2
..
11 0
12 10
UPDATE
The problem seems to be the SUM function
SELECT c.month, COALESCE(t.trans_id, 0) AS total
FROM calendar c
LEFT JOIN transaction t ON month(t.date_created) = c.month AND year(t.date_created) = '2018'
LEFT JOIN transaction_payment tp ON tp.trans_id = t.trans_id
ORDER BY c.month ASC
I tried displaying the ID only and it's running well. but when I add back this function. I can only get months with values.
COALESCE(SUM(tp.total), 0);
This fixes the issues with your query:
SELECT c.month, COALESCE(SUM(tp.total), 0) AS total
FROM calendar c LEFT JOIN
transaction t
ON month(t.date_created) = month(c.month) AND
year(t.date_created) = '2017' LEFT JOIN
transaction_payment tp
ON tp.trans_id = t.trans_id
GROUP BY c.month
ORDER BY MIN(t.date_created) ASC;
This will only work if the "calendar" table has one row per month -- that seems odd, but that might be your data structure.
Note the changes:
Start with the calendar table, because those are the rows you want to keep.
Do not use LIKE with dates. MySQL has proper date functions. Use them.
The filtering conditions on all but the first table should be in the ON clause rather than the WHERE clause.
I prefer COALESCE() to IFNULL() because COALESCE() is ANSI standard.
You need to use right as per your query because you calendar table is present at right side
SELECT calendar.month, IFNULL(SUM(transaction_payment.total),0) AS total
FROM `transaction`
JOIN `transaction_payment` ON `transaction_payment`.`trans_id` =
`transaction`.`trans_id`
RIGHT JOIN `calendar` ON MONTH(transaction.date_created) = calendar.month
WHERE`date_created` LIKE '2017%' ESCAPE '!'
GROUP BY calendar.month
ORDER BY `date_created` ASC

Best way to subtract SUM and COUNT with 2 table select?

I have came up with solution to count total of fields based on specific group, but it looks quite lengthy to get to the result i expect.
I have some basic knowledge when it comes to sql.
Is there obvious improvements to be made and why?
Why i would like to shorten this: Easier to implement in ORM type systems.
Changing scheme is not an option.
Schema and sample data: http://sqlfiddle.com/#!9/62df6
Query i'm using:
SELECT s.release_id,
(s.shipments_total - IFNULL(sh.shipment_entries, 0)) AS shipments_left
FROM
( SELECT release_id,
SUM(shipments) AS shipments_total
FROM subscriptions
WHERE is_paid = 1
AND shipments > 1
GROUP BY release_id ) AS s
LEFT JOIN
( SELECT release_id,
COUNT(*) AS shipment_entries
FROM shipments
GROUP BY release_id ) AS sh ON s.release_id = sh.release_id
Expected result on sample data is in sqlfiddle.
If you bring the condition in-line and remove the group by, then you don't need ifnull():
SELECT s.release_id,
(SUM(s.Shipments) -
(SELECT COUNT(*)
FROM shipments sh
WHERE sh.release_id = s.release_id
)
) AS shipments_left
FROM subscriptions s
WHERE is_paid = 1 AND shipments > 1
GROUP BY s.release_id;
The subquery returns 0 if nothing matches, not NULL (with the GROUP BY, it would return NULL). I am not sure if this is easier with your ORM model. Your original version is fine from a SQL point of view.
You can bring the join inline instead:
SELECT s.release_id,
SUM(s.Shipments) - IFNULL(( SELECT COUNT(*) AS shipment_entries
FROM shipments sh
WHERE sh.release_id = s.release_id
GROUP BY sh.release_id ), 0) AS shipments_left
FROM subscriptions s
WHERE is_paid = 1
AND shipments > 1
GROUP BY s.release_id
The execution plan for this is more performant too.

Getting Incorrect SUM for left joins and GROUP BY

I am getting wrong results in the sum of total deposits.
I want to output a report of total deposits per campaign_name
and eventually inside a date range.
SELECT IFNULL(campaign_name,'DIRECT'),
IFNULL(TotalDeposit,0)
FROM trackings
LEFT JOIN
(SELECT deposit_amount,
sum(deposit_amount) AS TotalDeposit,
uuid
FROM conversions
LEFT JOIN transactions ON conversions.trader_id = transactions.trader_id
WHERE aff_id =3
AND TYPE='deposit'
GROUP BY transactions.trader_id) AS conversions ON trackings.uuid = conversions.uuid
WHERE aff_id=3
GROUP BY campaign_name
results: missing 200 from trynow campaign??
campaign_name,TotalDeposit
DIRECT,0.00
new_campaign_name,0.00
test march,500.00
testing,0.00
trynow,800.00
expected results:
campaign_name,TotalDeposit
DIRECT,0.00
new_campaign_name,0.00
test march,500.00
testing,0.00
trynow,1000.00
I think your data isn't quite right - using the data that you've supplied, the deposit of 500 for test march is never going to be returned, as it is linked to trader_id 7506, who has no records in the conversions table.
However, the following query is simpler and easier to understand, and correctly returns 1000 for trynow
SELECT
IFNULL(SUM(t.deposit_amount),0) AS total_deposits
, IFNULL(tr.campaign_name,'DIRECT') AS campaign
FROM
trackings tr LEFT JOIN
conversions c ON
tr.uuid = c.uuid LEFT JOIN
transactions t ON
c.trader_id = t.trader_id AND
tr.`aff_id` = t.aff_id AND
t.type = 'Deposit'
WHERE
tr.aff_id = 3 AND
tr.updated_at >= '2015-03-01' AND tr.updated_at < '2015-04-01'
GROUP BY
IFNULL(tr.campaign_name,'DIRECT')
If you can check the test data supplied or otherwise point me in the right direction, I might be able to improve the query to return exactly what you want.
For date filtering, see the addition to the where clause above. NOte that if you need to filter on a date in the transactions table, the date filtering clause must be part of the "on" statement instead (as this table is left-joined, so we can't filter in the main where clause).

Using alias in subquery

I'm running the following query to get the open positions on a portfolio:
SELECT SUM(trades.quantity) as total_quantity, SUM(trades.price) as total_cost, SUM(trades.price)/SUM(trades.quantity) as cost_per_share,
trades.ticker, tickers.code
FROM (trades)
LEFT JOIN tickers
ON trades.ticker = tickers.id
GROUP BY tickers.code
HAVING total_quantity > 0
ORDER BY tickers.code
I'd like to add an extra column to show the weightening of a position, i.e.:
total_cost/SUM(total_cost) -- Dividing any given position cost by the total cost of the portfolio
Since aliases cannot be used in calculations, I thought I'd need to use a sub-query. I've tried a few things but couldn't make it to work.
Can anyone shed some light on this? Is sub-query the way to go? Is there any other better way to do it?
Not sure on your query (you appear to be doing a GROUP BY on a field from a LEFT JOINed table, which could be null for non found matching rows), but maybe cross join to a subselect to get the total of all prices
SELECT total_quantity, total_cost, cost_per_share, trades.ticker, tickers.code, total_cost/total_of_prices
FROM
(
SELECT SUM(trades.quantity) as total_quantity, SUM(trades.price) as total_cost, SUM(trades.price)/SUM(trades.quantity) as cost_per_share,
trades.ticker, tickers.code
FROM trades
LEFT JOIN tickers
ON trades.ticker = tickers.id
GROUP BY tickers.code
HAVING total_quantity > 0
) Sub1
CROSS JOIN
(
SELECT SUM(price) as total_of_prices
FROM trades
WHERE quantity > 0
) Sub2
ORDER BY tickers.code