Issue with Sum Calculation for multiple different IDs - mysql

The SQL code I have is shown below. The issue I am having is with calculating the sum of the Value of each Number. Doing them separately is okay and works when the Number is specified in the code but not when the number is not specified and I want the sum calculation to work for all separate Numbers.
{ Looks to calculate the value at the point of Tax. So all values before the tax date are ignored and all unit entries that occurred after the tax date are subtracted off the total units. The tax units are also subtracted off. The total units at point of tax, for each fund, is multiplied by the price at point of tax. The values of the funds are summed giving a total value for the number. }
This is the code I have done:
SELECT DISTINCT
A.Number,
CAST(ROUND((SELECT SUM(Val)
FROM (SELECT((S.TotalUnits -
(SELECT SUM(Units)
FROM TableIH AS F WHERE F.Fund = S.Fund AND F.Number = S.Number
AND F.Date >= (SELECT MIN(Date) FROM TableIH AS D
WHERE D.Fund= F.Fund AND D.Number = F.Number AND D.Entry = 'Tax' )
)
) * T.Price
) AS Val
FROM TableIH T
INNER JOIN TableID S
ON T.Number = S.Number
WHERE S.TotalUnits > 0 AND T.Fund = S.Fund
AND T.Price = ANY (SELECT (Price)
FROM TableIH AS E WHERE E.Entry = 'Tax' AND E.Number = T.Number)
)t
)
,2)
AS DECIMAL (25,2)) AS "Value"
FROM
TableIH A
INNER JOIN TableID C
ON C.Number = A.Number
group by A.Number
Tables are:
TableIH:
Number Fund Entry Units Price Date
12 YY RE 6 0.2 2015-02-02
12 YY Tax -10 0.1 2015-01-13
13 XX RE 6 0.2 2015-02-12
13 XX Tax -20 0.05 2014-12-11
13 MM Tax -25 0.6 2014-12-10
13 MM RE 8 0.2 2013-11-02
TableID
Number Fund TotalUnits
12 YY 2000
13 XX 1500
13 MM 500
DESIRED RESULT:
Number Value
12 [ ((2000) - (6) - (-10)) * 0.1 ] = 200.4
13 [ ((1500) - (6) - (-20)) * 0.05 ] + [ ((500) - (-25)) * 0.6 ] = 390.7
BUT GETTING: - Result is summing all instead of dividing it between each number.
Number Value
12 [ (200.4 + 390.7] = 591.1
13 [ (200.4 + 390.7] = 591.1
Any help with the errors would be greatly appreciated

Good evening, The problem is in your third nested subquery, you have no group on your sum, so it's summing all units, so you'll only get one total:
SELECT SUM(Units)
FROM TableIH AS F WHERE F.Fund = S.Fund AND F.Number = S.Number
AND F.Date >= (SELECT MIN(Date) FROM TableIH AS D
WHERE D.Fund= F.Fund AND D.Number = F.Number AND D.Entry = 'Tax'

Related

Get COUNT/SUM of records based on another column in SQL

How do I get the count/sum of the rows (COUNT () or SUM ()) based on another column (of the Type: weekly or yearly)? I have two tables:
Stores:
Id
Name
Type
1
Store 1
Weekly
2
Store 2
Yearly
3
Store 3
Weekly
4
Store 4
Weekly
Orders:
Id
StoreId
OrderDate
Qty
1
1
2022-01-31
2
2
1
2022-12-31
5*
3
2
2022-01-28
30*
4
2
2022-06-30
50*
5
2
2022-12-31
70*
6
3
2022-06-15
8
7
3
2022-12-27
9*
8
3
2022-12-31
3*
a) If I pass the date range (by weekly,2022-12-26 ~ 2023-01-01), the expected result should look like this:
Id
Name
Count of orders
Total Qty
1
Store 1
1
5
2
Store 2
3
150 (sum by the year when the store's type equals "Yearly": 30+50+70)
3
Store 3
2
12 (sum by the selected week: 9+3)
4
Store 4
0
0
If the Store type is Yearly then all orders will be summed up based on StoreId & year of OrderDate, if Weekly then based on StoreId & selected OrderDate.
b) I tried using CASE in SELECT statement, but no luck, here are part of my codes:
SELECT s.Id,
s.Name,
COUNT(o.Id) AS 'Count of orders',
sum(o.Qty) AS 'Total Qty'
FROM Stores AS s
LEFT JOIN Orders AS o
ON o.StoreId = s.id
AND (OrderDate >= '2022-12-26' AND OrderDate <= '2023-01-01')
GROUP BY s.Id, OrderDate
ORDER BY OrderDate DESC
You could use conditional aggregation as the following:
SELECT s.Id,
s.Name,
COUNT(CASE
WHEN s.Type = 'Yearly' THEN
o.Id
ELSE
CASE
WHEN OrderDate >= '2022-12-26' AND OrderDate <= '2023-01-01' THEN
o.Id
END
END) As 'Count of orders',
SUM(CASE
WHEN s.Type = 'Yearly' THEN
o.Qty
ELSE
CASE
WHEN OrderDate >= '2022-12-26' AND OrderDate <= '2023-01-01' THEN
o.Qty
ELSE
0
END
END) AS 'Total Qty'
FROM Stores AS s
LEFT JOIN Orders AS o
ON o.StoreId = s.id
GROUP BY s.Id, s.Name
ORDER BY MAX(OrderDate) DESC
See demo.
You can do in this way.
Please take note that, type is a keyword in MySQL.
SELECT s.id,
s.name,
s.type,
COUNT(s.name) AS total_count,
SUM(o.qty) AS total_qty
FROM stores s
LEFT JOIN orders o
ON s.id = o.storeid
WHERE (o.orderdate >= '2022-12-26' AND o.orderDate <= '2023-01-01'
AND s.type = 'Weekly')
OR s.type = 'Yearly'
GROUP BY s.id, s.name, s.type
From the description, calculate count(Orders.Id) and sum(Orders.Qty)
Stores.Type = 'Weekly': Orders.OrderDate between #start_date and #end_date
Stores.Type = 'Yearly': Orders.OrderDate in the year of #start_date (...all orders will be summed up based on StoreId & year of OrderDate.)
Thus, the first step is to have where clause to filter out Orders and then aggregate to Store.Id level. Then, 2nd step is to left join from Stores table to the result of first step so that stores without sales in specified date ranges are reported.
set #start_date = '2022-12-26', #end_date = '2023-01-01';
with cte_store_sales as (
select s.Id,
count(o.Id) as order_count,
sum(o.Qty) as total_qty
from stores s
left
join orders o
on s.Id = o.StoreId
where (s.type = 'Weekly' and o.OrderDate between #start_date and #end_date)
or (s.type = 'Yearly' and o.OrderDate between makedate(year(#start_date),1)
and date_sub(date_add(makedate(year(#start_date),1), interval 1 year), interval 1 day))
group by s.Id)
select s.Id,
s.Name,
coalesce(ss.order_count, 0) as "Count of Orders",
coalesce(ss.total_qty, 0) as "Total Qty"
from stores s
left
join cte_store_sales ss
on s.Id = ss.Id
order by s.Id;
Output:
Id|Name |Count of Orders|Total Qty|
--+-------+---------------+---------+
1|Store 1| 1| 5|
2|Store 2| 3| 150| <-- Store sales in year 2022
3|Store 3| 2| 12|
4|Store 4| 0| 0| <-- Report stores without sales
First of all, we shall extract the raw data matching the orderdate table condition, which can be used for the sake of aggregation later. Note,here I treat the date range as inclusive. Therefore, it shall be year 2022 and 2023 for 2022-12-26 ~ 2023-01-01 if the type is yearly.
select s.id id, name,
(case when type='weekly' and orderdate between '2022-12-26' and '2023-01-01' then qty
when type='yearly' and year(orderdate) between year('2022-12-26') and year('2023-01-01') then qty
end) as qt
from Stores s
left join Orders o
on s.id=o.storeid;
-- result set:
# id, name, qt
1, Store 1, 5
2, Store 2, 30
2, Store 2, 50
2, Store 2, 70
3, Store 3,
3, Store 3, 9
3, Store 3, 3
4, Store 4,
The rest is to do the summarisation job using the derived table. Note: Since the column name is not in the group by list, but it's actually unique for a specific storeid, we can use the any_value function to bypass the restriction which might be enforced due to the SQL_MODE system variable.
select id,any_value(name) as'Name',count(qt) as 'Count of orders', ifnull(sum(qt),0) as 'Total Qty'
from
(select s.id id, name,
(case when type='weekly' and orderdate between '2022-12-26' and '2023-01-01' then qty
when type='yearly' and year(orderdate) between year('2022-12-26') and year('2023-01-01') then qty
end) as qt
from Stores s
left join Orders o
on s.id=o.storeid) tb
group by id
order by id
;
-- result set:
# id, Name, Count of orders, Total Qty
1, Store 1, 1, 5
2, Store 2, 3, 150
3, Store 3, 2, 12
4, Store 4, 0, 0

Get total cost for a customer call

I have 2 tables = customer and their call history
Now I want to charge them based on call duration and that too for a specific month say Jan 2015.
Here is the criteria to calculate the cost for calls -
A) For incoming calls, the charge is 1 unit per second. Example if the duration is 250 seconds then cost is 250
B) For outgoing calls, for the first 2mins, the cost is fixed at 500 units. for subsequent seconds the cost is 2 units per second.
Example if the outgoing duration is 5mins then cost is 500 units + 2*3*60 units = 860 units
Below are the tables:
customer table with columns id, name, phone
history table with columns id, incoming_phone, outgoing_phone, duration, dialed_on (YYYY-MM-DD)
I have come up with below queries for my conditions:
For incoming call cost:
select c.name, c.phone, h.duration as cost
from customer c join history h on c.phone = h.incoming_phone
When I run the above query I did not get any syntax errors.
For outgoing call cost:
select c.name, c.phone, CASE
WHEN h.duration > 120 THEN 500 + 2*(h.duration-120)
ELSE 2*(h.duration-120)
END; as cost
from customer c join history h on c.phone = h.outgoing_phone
When I run the above query I got syntax error like "ERROR 1109 (42S02) at line 1: Unknown table 'c' in field list"
I want to join these two queries and get the total cost and display the fields as name, phone, cost
I still need to add a condition for a specific month to restrict data for Jan 2015, but got stuck with the approach.
The error is due to the extra semicolon ; after END.
Sounds like your final query will be this:
SELECT c.name,
c.phone,
SUM(CASE WHEN h.direction = 'in' THEN h.duration END) as IncomingCost,
SUM(CASE WHEN h.direction = 'out' AND h.duration > 120 THEN 500 + 2*(h.duration-120)
WHEN h.direction = 'out' AND h.duration <= 120 THEN 500
END) as OutgoingCost,
SUM(CASE WHEN h.direction = 'in' THEN h.duration END +
CASE WHEN h.direction = 'out' AND h.duration > 120 THEN 500 + 2*(h.duration-120)
WHEN h.direction = 'out' AND h.duration <= 120 THEN 500
END) as TotalCost
FROM customer c
JOIN (SELECT 'out' as directon, duration, dialed_on, outgoing_phone as phone
FROM history
WHERE YEAR(dialed_on) = 1995
AND MONTH(dialed_on) = 1
UNION ALL
SELECT 'in' as direction, duration, dialed_on, incoming_phone as phone
FROM history
WHERE YEAR(dialed_on) = 1995
AND MONTH(dialed_on) = 1
) h ON c.phone = h.phone
GROUP BY c.name,
c.phone

Subtract all the rows from the value of first row in a MySQL column

This is my Select query
Select
snpc_stats.gamedetail.Player,
Sum(snpc_stats.gamedetail.Points + snpc_stats.gamedetail.Hits) As TotalPoints,
COUNT(*) As 'Games Played',
Sum(If(snpc_stats.gamedetail.Finish = 1, 1, 0)) As Wins,
Sum(If(snpc_stats.gamedetail.Finish = 2, 1, 0)) As Second,
Sum(If(snpc_stats.gamedetail.Finish = 3, 1, 0)) As Third,
Round(Avg(snpc_stats.gamedetail.Finish),2) As 'Average Finish',
Sum(snpc_stats.gamedetail.Hits) As `Total Hits`,
Sum(snpc_stats.gamedetail.ChampFund) As ChampFund,
Sum(snpc_stats.games.BuyIn) + (snpc_stats.gamedetail.ChampFund) As Cost,
Sum(snpc_stats.gamedetail.Winnings) As Winnings,
Sum(snpc_stats.gamedetail.Winnings) - (snpc_stats.games.BuyIn) - (snpc_stats.gamedetail.ChampFund) As 'Total Winnings',
COUNT(snpc_stats.games.Round) As round
From
snpc_stats.gamedetail Inner Join
snpc_stats.games On snpc_stats.games.GameID =
snpc_stats.gamedetail.GamesID
Where
snpc_stats.games.Season = '2015 Season'
Group By
snpc_stats.gamedetail.Player, snpc_stats.games.Season
Order By
TotalPoints Desc
After the TotalPoints Column I like to add a column that show the amount of points each player trails behind the leader, like the table below:
Rank | Player | Total Points | Points Behind
1 Bill 164 -
2 Al 152 -12
3 Ed 151 -13
4 Jill 123 -41
5 Bob 121 -43
6 Joe 102 -62
7 Dave 82 -82
8 Rob 60 -104
9 Doug 60 -104
10 Don 51 -113
11 Dan 30 -134
Any help would be so appreciated!
solution (1)If you are using a server side language that connects to the database and runs the query, you can preserve total points value of first row in some variable and subtracting all the following rows from it
solution (2)make a sub query that returns total points of the first row and use it in the selection part of the query
Select
snpc_stats.gamedetail.Player,
Sum(snpc_stats.gamedetail.Points + snpc_stats.gamedetail.Hits) As TotalPoints,
(Sum(snpc_stats.gamedetail.Points + snpc_stats.gamedetail.Hits) - (Select Sum(snpc_stats.gamedetail.Points + snpc_stats.gamedetail.Hits) From snpc_stats.gamedetail Inner Join snpc_stats.games On snpc_stats.games.GameID = snpc_stats.gamedetail.GamesID Where snpc_stats.games.Season = '2015 Season' Group By snpc_stats.gamedetail.Player, snpc_stats.games.Seaso Order By Sum(snpc_stats.gamedetail.Points + snpc_stats.gamedetail.Hits) Desc Limit 1)) As Points Behind,
COUNT(*) As 'Games Played',
Sum(If(snpc_stats.gamedetail.Finish = 1, 1, 0)) As Wins,
Sum(If(snpc_stats.gamedetail.Finish = 2, 1, 0)) As Second,
Sum(If(snpc_stats.gamedetail.Finish = 3, 1, 0)) As Third,
Round(Avg(snpc_stats.gamedetail.Finish),2) As 'Average Finish',
Sum(snpc_stats.gamedetail.Hits) As `Total Hits`,
Sum(snpc_stats.gamedetail.ChampFund) As ChampFund,
Sum(snpc_stats.games.BuyIn) + (snpc_stats.gamedetail.ChampFund) As Cost,
Sum(snpc_stats.gamedetail.Winnings) As Winnings,
Sum(snpc_stats.gamedetail.Winnings) - (snpc_stats.games.BuyIn) - (snpc_stats.gamedetail.ChampFund) As 'Total Winnings',
COUNT(snpc_stats.games.Round) As round
From
snpc_stats.gamedetail Inner Join
snpc_stats.games On snpc_stats.games.GameID =
snpc_stats.gamedetail.GamesID
Where
snpc_stats.games.Season = '2015 Season'
Group By
snpc_stats.gamedetail.Player, snpc_stats.games.Season
Order By
TotalPoints Desc

SQL - Subquery Error and Sum Error

The SQL code I have is shown below. The issue I am having is with the subquery first of all.
The result displays the error:
SQL ERROR: Subquery returned more than 1 value. This is not permitted
when the subquery follows =, !=, <, <= , >, >= or when the subquery is
used as an expression
Need to show rows with date greater than (or equal to) the date where ESource = Detail - The rows need to be summed in some columns and a single value selected in others.
Code used:
select DISTINCT
A.Policy,
A.Fund,
(SUM(A.AddUnits)) AS SUM,
((C.TotalUnits - (SUM(A.AddUnits))) * A.Price) AS Value,
Inner JOIN TableC C
ON C.PolicyNumber = A.PolicyNumber
where A.PolicyNumber = '120' AND C.NumberOfUnits > 0 AND C.InvestmentFund = A.InvestmentFund
AND A.DateOfEntry < DATEADD(year, -1, GetDate())
AND A.DateOfEntry >= (Select DateOfEntry FROM TableA AS D where D.ESource = 'Detail')
AND A.UnitPrice = (Select UnitPrice FROM TableA AS E where E.ESource = 'Detail')
ORDER BY IH.DateOfEntry DESC
Tables are:
Table A:
Policy Fund Units Price ESource Date
120 BR 6 0.74 RE 2015
120 BR -100 0.72 Detail 2014
120 BR 6 0.71 RE 2013
TABLE C:
Policy Fund TotalUnits
120 BR 400
DESIRED RESULT:
Policy Fund Sum Price Value
120 BR [6+(-100)] = -94 0.72 [(400+(-94))*0.72] = 220.32
As well as the sub query issues - the command to get price = 0.72 [where ="Detail"] is stopping the sum of both rows occurring making Sum = -100 and not -94
Any help with the errors would be greatly appreciated
The problem is in your where clause:
where A.PolicyNumber = '120' AND
C.NumberOfUnits > 0 AND
C.InvestmentFund = A.InvestmentFund AND
A.DateOfEntry < DATEADD(year, -1, GetDate()) AND
A.DateOfEntry >= (Select DateOfEntry FROM TableA AS D where D.ESource = 'Detail') AND
A.UnitPrice = (Select UnitPrice FROM TableA AS E where E.ESource = 'Detail')
The last two clauses are the problem. One way to fix this is with the keywords ANY, SOME, or ALL:
where A.PolicyNumber = '120' AND
C.NumberOfUnits > 0 AND
C.InvestmentFund = A.InvestmentFund AND
A.DateOfEntry < DATEADD(year, -1, GetDate()) AND
A.DateOfEntry >= ALL (Select DateOfEntry FROM TableA AS D where D.ESource = 'Detail') AND
A.UnitPrice = ALL (Select UnitPrice FROM TableA AS E where E.ESource = 'Detail')
However, I prefer explicit aggregation:
where A.PolicyNumber = '120' AND
C.NumberOfUnits > 0 AND
C.InvestmentFund = A.InvestmentFund AND
A.DateOfEntry < DATEADD(year, -1, GetDate()) AND
A.DateOfEntry >= (Select MAX(DateOfEntry) FROM TableA AS D where D.ESource = 'Detail') AND
A.UnitPrice = (Select MAX(UnitPrice) FROM TableA AS E where E.ESource = 'Detail')
Note that the query in your question seems to be missing a from clause. And the condition C.InvestmentFund = A.InvestmentFun should be in an on clause not in the where clause.

SQL - Using sum but optionally using default value for row

Given tables
asset
col - id
date_sequence
col - date
daily_history
col - date
col - num_error_seconds
col - asset_id
historical_event
col - start_date
col - end_date
col - asset_id
I'm trying to count up all the daily num_error_seconds for all assets in a given time range in order to display "Percentage NOT in error" by day. The catch is if there is a historical_event involving an asset that has an end_date beyond the sql query range, then daily_history should be ignored and a default value of 86400 seconds (one day of error_seconds) should be used for that asset
The query I have that does not use the historical_event is:
select ds.date,
IF(count(dh.time) = 0,
100,
100 - (100*sum(dh.num_error_seconds) / (86400 * count(*)))
) percent
from date_sequence ds
join asset a
left join daily_history dh on dh.date = ds.date and dh.asset_id=a.asset_id
where ds.date >= in_start_time and ds.date <= in_end_time
group by ds.thedate;
To build on this is beyond my SQL knowledge. Because of the aggregate function, I cannot simply inject 86400 seconds for each asset that is associated with an event that has an end_date beyond the in_end_time.
Sample Data
Asset
1
2
Date Sequence
2013-09-01
2013-09-02
2013-09-03
2013-09-04
Daily History
2013-09-01, 1400, 1
2013-09-02, 1501, 1
2013-09-03, 1420, 1
2013-09-04, 0, 1
2013-09-01, 10000, 2
2013-09-02, 20000, 2
2013-09-03, 30000, 2
2013-09-04, 40000, 2
Historical Event
start_date, end_date, asset_id
2013-09-03 12:01:03, 2014-01-01 00:00:00, 1
What I would expect to see with this sample data is a % of time these assets are in error
2013-09-01 => 100 - (100*(1400 + 10000))/(86400*2)
2013-09-02 => 100 - (100*(1501 + 20000))/(86400*2)
2013-09-03 => 100 - (100*(1420 + 30000))/(86400*2)
2013-09-04 => 100 - (100*(0 + 40000))/(86400*2)
Except: There was a historical event which should take precendence. It happened on 9/3 and is open-ended (has an end date in the future, so the calculations would change to:
2013-09-01 => 100 - (100*(1400 + 10000))/(86400*2)
2013-09-02 => 100 - (100*(1501 + 20000))/(86400*2)
2013-09-03 => 100 - (100*(86400 + 30000))/(86400*2)
2013-09-04 => 100 - (100*(86400 + 40000))/(86400*2)
Asset 1's num_error_seconds gets overwritten with a full day of error seconds if there is a historical event that has a start_date before 'in_end_time' and an end_time after the in_end_time
Can this be accomplished in one query? Or do I need to stage data with an initial query?
I think you're after something like this:
Select
ds.date,
100 - 100 * Sum(
case
when he.asset_id is not null then 86400 -- have a historical_event
when dh.num_error_seconds is null then 0 -- no daily_history record
else dh.num_error_seconds
end
) / 86400 / count(a.id) as percent -- need to divide by number of assets
From
date_sequence ds
cross join
asset a
left outer join
daily_history dh
on a.id = dh.asset_id and
ds.date = dh.date
left outer join (
select distinct -- avoid counting multiple he records
asset_id
from
historical_event he
Where
he.end_date > in_end_time
) he
on a.id = he.asset_id
Where
ds.date >= in_start_time and
ds.date <= in_end_time -- I'd prefer < here
Group By
ds.date
Example Fiddle