I have a query that is giving me results using grouping sets:
Select
store,
product,
FiscalMonth,
FIscalYear,
SUM(Amount),
CASE WHEN FiscalMonth IS NULL THEN SUM(Amount) ELSE NULL END AS Total
From Sales
Group by store, product,
Grouping Sets(
(FiscalYear, FiscalMonth, product),
(FiscalYear, product)
)
Which gives me a nice set of results grouped by FiscalMonth and FiscalYear:
Store Product FiscalMonth FiscalYear Amount Total
1 123 NULL 2007 23.00 452.00
1 123 1/1/2007 2007 55.00 NULL
1 123 1/2/2007 2007 11.00 NULL
1 123 2/1/2007 2007 28.00 NULL
1 123 NULL 2008 28.00 552.00
1 123 3/1/2008 2008 99.00 NULL
1 123 4/1/2008 2008 36.00 NULL
1 123 4/2/2008 2008 55.00 NULL
1 123 4/3/2008 2008 89.00 NULL
The problem I'm having is how can I create a column that compares Fiscal 2007 and Fiscal 2008, i would like an additional column "Diff" that=-100 (452-552)
I've tried creating some different columns with CASE statements, but it seem ultimately, each row only knows about Monthly Sum and Yearly sum of the current date, I can't seem to jump around to get previous years sum.
Any Ideas?
Thanks!
Query below can do what you want, you just have check for datatypes in convert (as i don't know which datatypes you are using so i assumed varchar(20) as year).
I get the previous year total in isNUll() and subtract it from current total and then multplied the result with -100.
Select
store,
product,
FiscalMonth,
FIscalYear,
SUM(Amount),
(-100 * ((ISNULL((SELECT SUM(Amount) FROM sales WHERE FiscalYear = CONVERT(VARCHAR(20),(CONVERT(INT, s.FiscalYear) - 1)) AND FiscalMonth IS NULL Group by FIscalYear),0) -
CASE WHEN FiscalMonth IS NULL THEN SUM(Amount) ELSE NULL END
))) as Diff
From Sales
Group by store, product,
Grouping Sets(
(FiscalYear, FiscalMonth, product),
(FiscalYear, product)
)
Related
date buys/ quantity Rolling Sum
sell
08-AUG-19 BUY 100 -
13-SEP-19 SELL -100 0
26-SEP-19 BUY 200 200
28-SEP-19 SELL -50 150
29-SEP-19 SELL -150 0
I need to have the last date of buy after the recent full sold out state of the quantity.
In the above case it sold out twice once on 13-sep-19 and second one was on 29-sep-19 as an output i need to have the value as 26-sep-19 (as this was the latest buy before the recent sold out state)
Expected result:
26-sep-19
get the latest buy before the recent sold out state
Here is one option:
select max(date)
from mytable t
where
buy_sell = 'BUY'
and date < (
select max(date)
from mytable
where rolling_sum = 0 and buy_sell = 'SELL'
)
This demo on DB Fiddle with your sample data returns:
| max(date) |
| :--------- |
| 2019-09-26 |
Note: this assumes
1) that you are storing dates as a dateè-like datatype, not as strings...
2) that the rolling_sum is an actual column in your table
I have two tables namely "appointment" and "skills_data".
Structure of appointment table is:
id_ap || ap_meet_date || id_skill || ap_status.
And the value of ap_status are complete, confirm, cancel and missed.
And the skills_data table contains two columns namely:
id_skill || skill
I want to get the count of total number of appointments for each of these conditions
ap_status = ('complete' and 'confirm'),
ap_status = 'cancel' and
ap_status = 'missed'
GROUP BY id_skill and year and
order by year DESC
I tried this query which only gives me count of one condition but I want to get other two based on group by and order by clauses as mentioned.
If there is no record(for example: zero appointments missed in 2018 for a skill) matching for certain conditions, then it should display the output value 0 for zero count.
Could someone please suggest me with a query whether I should implement multiple select query or CASE clause to achieve my expected results. I have lot of records in appointment table and want a efficient way to query my records. Thank you!
SELECT a.id_skill, YEAR(a.ap_meet_date) As year, s.skill,COUNT(*) as count_comp_conf
FROM appointment a,skills_data s WHERE a.id_skill=s.id_skill and a.ap_status IN ('complete', 'confirm')
GROUP BY `id_skill`, `year`
ORDER BY `YEAR` DESC
Output from my query:
id_skill | year | skill | count_comp_conf
-----------------------------------------
1 2018 A 20
2 2018 B 15
1 2019 A 10
2 2019 B 12
3 2019 C 10
My expected output should be like this:
id_skill | year | skill | count_comp_conf | count_cancel | count_missed
------------------------------------------------------------------------
1 2018 A 20 5 1
2 2018 B 15 8 0
1 2019 A 10 4 1
2 2019 B 12 0 5
3 2019 C 10 2 2
You can use conditional aggregation using case when expression
SELECT a.id_skill, YEAR(a.ap_meet_date) As year, s.skill,
COUNT(case when a.ap_status IN ('complete', 'confirm') then 1 end) as count_comp_conf,
COUNT(case when a.ap_status = 'cancel' then 1 end) as count_cancel,
COUNT(case when a.ap_status = 'missed' then 1 end) as count_missed
FROM appointment a inner join skills_data s on a.id_skill=s.id_skill
GROUP BY `id_skill`, `year`
ORDER BY `YEAR` DESC
SELECT a.id_skill,
YEAR(a.ap_meet_date) As year,
s.skill,
SUM(IF(a.ap_status IN ('complete', 'confirm'),1,0)) AS count_comp_conf,
SUM(IF(a.ap_status='cancel',1,0)) AS count_cancel,
SUM(IF(a.ap_status='missed',1,0)) AS count_missed
FROM appointment a,skills_data s WHERE a.id_skill=s.id_skill
GROUP BY `id_skill`, `year`
ORDER BY `YEAR` DESC;
Please try to use if condition along with sum.
With below query you will get output.
select id_skill ,
year ,
skill ,
count_comp_conf ,
count_cancel ,
count_missed ( select id_skill, year, skill, if ap_status ='Completed' then count_comp_conf+1, elseif ap_status ='cancelled' then count_cancel +1 else count_missed+1
from appointment a join skills_data s on (a.id_skill = s.id_skill) group by id_skill, year) group by id_skill,year
order by year desc;
I have table as following:
hours | ... | task_assigned | task_deadline | task_completion
----------------------------------------------------------------
123 | ... | 2019-08-01 | - | -
234 | ... | - | 2018-08-01 | 2019-08-01
145 | ... | 2017-08-01 | 2017-08-01 | 2018-01-01
I want to calculate total hours for each year, i.e. grouping by year.
Currently I'm only taking into account task_completion field.
If there's no value in task_completion field, the record is not included in SUM calculation.
To elaborate further, say for year 2019, row 1 and 1 both should be considered. Hence the total hours should be 123 + 234 = 357.
And for year 2018, row 2 and 3.
Similarly, for year 2017, row 3.
SELECT YEAR(task_completion) as year, ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN '$year_from' AND '$year_to'
The resultset:
year | hours
--------------------
2017 | <somevalue>
2018 | <somevalue>
2019 | <somevalue>
How can I include other two date fields too?
You want to consider each row once for each of its years. Use UNION to get these years:
select year, round(sum(total_hours), 2) as hours
from
(
select year(task_assigned) as year, total_hours from task
union
select year(task_deadline) as year, total_hours from task
union
select year(task_completion) as year, total_hours from task
) years_and_hours
group by year
having year between $year_from and $year_to
order by year;
If you want to consider a row with one year twice or thrice also as often in the sum, then change UNION to UNION ALL.
Basically, you want to unpivot the data. I will assume that the - represents a NULL value and your dates are real dates.
select year(dte) as year, sum(total_hours) as hours
from ((select task_assigned as dte, total_hours
from task
) union all
(select task_deadline, total_hours
from task
) union all
(select task_completion, total_hours
from task
)
) d
where dte is not null
group by year(dte)
order by year(dte);
Based on your sample data, the round() is not necessary so I removed it.
If you want to filter for particular years, the filtering should be in a where clause -- so it filters the data before aggregation.
Change the where to:
where year(dte) >= ? and year(dte) <= ?
or:
where dte >= ? and dte <= ?
to pass in the dates.
The ? are for parameter placeholders. Learn how to use parameters rather than munging query strings.
This answer is no langer valid with the updated request.
If I understand correctly, you want to use task_assigned if the task_completion is still null. Use COALEASCE for this.
SELECT
YEAR(COALESCE(task_completion, task_assigned)) as year,
ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN $year_from AND $year_to
ORDER BY year;
(I don't think you actually want to use task_deadline, too, for how could a task get completed before getting assigned first? If such can occur, then include it in the COALESCE expression. Probably: COALESCE(task_completion, task_assigned, task_deadline)` then.)
I am trying to run a report that will group by month the totals of expense accounts from an expense table which has the followings columns on each row:
expense_acc
debit
credit
post_date
The desired output of the report is in the following column format:
EXP ACC - JAN - FEB - MAR
This is my SQL select query:
SELECT expense_acc,
if(MONTH(post_date)=1,SUM(expenses.debit-expenses.credit),0) AS 'JAN',
if(MONTH(post_date)=2,SUM(debit-credit),0) AS 'FEB',
if(MONTH(post_date)=3,SUM(debit-credit),0) AS 'MAR'
FROM expenses
WHERE YEAR(expenses.entered)='2016'
GROUP BY expenses.expense_acc
The results are not grouping the expense values by month as expected. I am seeing grouping in the first row, regardless of the transaction date.
You have two parts to your requirement.
A month-by-month aggregate of your table's contents
A pivot table rendering, in which you pivot by-month rows to lie in columns.
Also, SUM(expenses.debit-expenses.credit) isn't resilient if the debit or credit columns ever contain NULL values.
Also, YEAR(date) defeats any index on date.
If you're wise you'll handle these requirements in two steps. For one thing, it will be easier to troubleshoot your results. For another, the next person who comes along will better understand your project.
The month-by-month aggregate:
SELECT expense_acc, LAST_DAY(post_date) month_ending,
SUM(expenses.debit) - SUM(expenses.credit) as net_expenses
FROM expenses
WHERE post_date >= '2016-01-01'
AND post_date < '20016-12-31' + INTERVAL 1 DAY
GROUP BY expense_acc, LAST_DAY(post_date)
ORDER BY expense_acc, LAST_DAY(post_date)
This will give you a row for each account and date. The row will show the account, the month-ending date, and the net expenses. (I don't understand expenses.entered in your example. It's best to filter on the same date you use to make your aggregate.) Your auditors will appreciate this separation of logic.
Next, you can use this as a subquery, to make your pivot display.
That's pretty straightforward:
SELECT expense_acc,
SUM(IF(MONTH(month_ending)=1,net_expenses,0)) as jan,
SUM(IF(MONTH(month_ending)=2,net_expenses,0)) as feb,
SUM(IF(MONTH(month_ending)=3,net_expenses,0)) as mar,
...
FROM (
SELECT expense_acc, LAST_DAY(post_date) month_ending,
SUM(expenses.debit) - SUM(expenses.credit) as net_expenses
FROM expenses
WHERE post_date >= '2016-01-01'
AND post_date < '20016-12-31' + INTERVAL 1 DAY
) z
GROUP BY expense_acc
ORDER BY expense_acc
But, you may want to do the pivoting in a client program. It's notoriously hard to write and maintain MySQL pivot code.
SELECT
SUM(JAN) AS JAN,
SUM(FEB) AS FEB
SUM(MAR) AS MAR
FROM
(
SELECT expense_acc,
if(MONTH(post_date)=1,SUM(expenses.debit-expenses.credit),0) AS 'JAN',
if(MONTH(post_date)=2,SUM(debit-credit),0) AS 'FEB',
if(MONTH(post_date)=3,SUM(debit-credit),0) AS 'MAR'
FROM expenses
WHERE YEAR(expenses.entered)='2016'
GROUP BY expenses.expense_acc
)TMP
GROUP BY expense_acc
In my opinion, the optimal way is that:
SELECT expense_acc, MONTH(post_date) month_num, SUM(debit-credit) as total
FROM expenses
WHERE YEAR(expenses.entered) = '2016'
GROUP BY expenses.expense_account, MONTH(post_date)
That will give you something like
|expense account | mont_num | total |
-----------------------------------------
| 2345 | 1 | 50 |
-----------------------------------------
| 2345 | 2 | 30 |
-----------------------------------------
| 2346 | 1 | 45 |
...
Because if you are doing one if statement per month, you will get 12 ifs.
It's better to manage the format of rows in you controller.
Hope it will help you.
I want to retrieve/compute the price on a given date for different assets, depending on the side of the transaction. Prior to 2000, I have mid quotes, afterwards I have bid and ask or offer quotes, so I would like the price to be the average of these two quotes. More precisely:
SELECT date, price,
CASE WHEN side='' THEN 'price_mid'
WHEN side='A' THEN 'price_ask'
WHEN side='B' THEN 'price_bid'
WHEN side='O' THEN 'price_offer'
END as prices
FROM table
WHERE asset = 'a';
How can I then compute the price in a new column, having the price_mid (prior to 2000) and (price_bid+price_ask)/2 or (price_bid+price_offer)/2 afterwards?
E.g.: Let's say I have the following situation:
date price prices
1 1 price_mid
2 1.1 price_mid
3 0.9 price_bid
3 1.2 price_ask
4 1.3 price_offer
4 1.1 price_bid
And I would like to have:
date final_price
1 1
2 1.1
3 1.05
4 1.2
I understand you need the average for only some dates. Maybe the following does what you want:
SELECT date, AVG(price) as AvgValue
FROM prices
WHERE date >= 2
AND prices in ('price_ask','price_offer','price_bid')
GROUP BY date
UNION
SELECT date, price as AvgValue
FROM prices
WHERE date < 2
AND prices = 'price_mid'
GROUP BY date
UNION
SELECT date, price as AvgValue
FROM prices p
WHERE date >= 2
AND prices = 'price_mid'
AND NOT EXISTS (SELECT 1 FROM prices p2 where p2.date = p.date AND p2.prices in ('price_ask','price_offer','price_bid'))
GROUP BY date
ORDER BY DATE ASC
Something like this
SELECT `date`, AVG(price) AS final_price FROM tbl GROUP BY 1
Tried something, it did not work, nevertheless maybe that helps..
This is a changed version of the query you already have, but it does not save a kind of state, it only has all the values on the right places.
SELECT
date,
CASE WHEN side = '' THEN price
ELSE NULL
END as price_mid,
CASE WHEN side = 'A' THEN price
ELSE NULL
END as price_ask,
CASE WHEN side = 'B' THEN price
ELSE NULL
END as price_bid,
CASE WHEN side = '' THEN price
ELSE NULL
END as price_offer
FROM
table
WHERE
asset = 'a';
What should give you:
date price_mid price_ask price_bid price_offer
1 1 NULL NULL NULL
2 1.1 NULL NULL NULL
3 NULL NULL 0.9 NULL
3 NULL 1.2 NULL NULL
4 NULL NULL NULL 1,4
4 NULL NULL 1.1 NULL
Now on that table you can check with IS NULL or take the SUM()or the MAX() per date, so you can switch through your cases on one line.