Need to write subquery to calculate income for each month - sql

My table is like
summa_som date_operation
----- --------------
100 11/03/2005
500 13/07/2008
900 12/11/2015
Expected result
I want to calculate income for each month there should 3 columns:
income, month and year:
Income Month Year
------ -------- ----
10000 February 2015
15000 December 2015
I have tried this but, I do not understand well how sub-queries work. This code should give an idea of what I want:
select max(summa_som * 0.01) income
from t_operation
where to_char(date_operation,'MM') = 11
and to_char(date_operation,'YYYY') = 2015
( select max(summa_som * 0.01) income_for_dec2015 from t_operation
where to_char(date_operation,'MM') = 12
and to_char(date_operation,'YYYY') = 2015 )

You can use filter and aggregation to achieve this:
select to_char(date_operation, 'Month') as mn,
to_char(date_operation, 'YYYY') as yr,
max(summa_som) * 0.01 as income
from t_operation
where date_operation >= date '2015-11-01'
and date_operation < date '2016-01-01'
group by to_char(date_operation, 'Month'),
to_char(date_operation, 'YYYY')
If you want result of all the months and years for which the data is present, you can remove the filter condition.

You have some specific formatting concerns with date and something going on with the income by multiplying it by 0.01. When you have formatting concerns like this, I prefer to do the aggregation in an inner query based on the data types involved, keeping numbers as numbers and dates, even it modified for grouping as dates. Then, wrap the statement with an outer query that handles type conversions or formatting.
select (income*0.01) as income_value,
to_char(income_mn,'Month') as income_month,
to_char(income_mn,'YYYY') as income_year
from (
select trunc(date_operation,'MON') as income_mn,
max(summa_som) as income
from t_operation
where date_operation between to_date('11/2015','MM/YYYY')
and to_date('01/2016','MM/YYYY') - numtodsinterval(1, 'SECOND')
group by trunc(date_operation,'MON')
);
note: using between makes the both the upper and lower bound values inclusive. I've subtracted one second to the upper bound to only include the December values, but this is not really necessary unless you only want 2015 values.

Related

How to optimize script and handle dynamic data in Oracle SQL?

I need to create 12 months report, which counts values per months. I have made a separate temp table using WITH for each months which counts parts for each aircraft. It takes data from the PARTS table. My table for January looks like this:
type
qty
month
Airbus
248
1
Boeing
120
1
Emb
14
1
Then I count amount of aicrafts each type per months using AC table, here's table for January:
type
qty
month
Airbus
23
1
Boeing
10
1
Emb
5
1
Since I need to find a division of Qty to Count, I implement division Qty / count. So I joined table 1 and table 2 using month column. And combined table for January looks like this:
type
qty
count
div
month
Airbus
248
23
10.7
1
Boeing
120
10
12
1
Emb
14
5
2.8
1
I create temp table for each month and the combine them with UNION ALL. But I am afraid it could lead to DB slowdown. I think I need to rewrite and optimize my script. Any ideas how I could implement that?
Also the data in tables is dynamic and can change. So I need to look only for the last 12 months.
In my script I will have to manually add more months, which is not applicaple.
Is there a way that could possibly solve the problem of optimization and take into account only last 12 months?
Rather than having a sub-query factoring (WITH) clause for each month and trying to use UNION ALL to join them, you can include the month in the GROUP BY clause when you are counting the quantities in the ac and part table.
Since you only provided the intermediate output from the WITH clauses, I've had to try to reverse engineer your original tables to give a query something like this:
SELECT COALESCE(ac.type, p.type) AS type,
COALESCE(ac.month, p.month) AS month,
COALESCE(ac.qty, 0) AS qty,
COALESCE(p.qty, 0) AS count,
CASE p.qty WHEN 0 THEN NULL ELSE ac.qty / p.qty END AS div
FROM (
SELECT type,
TRUNC(date_column, 'MM') AS month,
COUNT(*) AS qty
FROM ac
WHERE date_column >= ADD_MONTHS(SYSDATE, -12)
GROUP BY
type,
TRUNC(date_column, 'MM')
) ac
FULL OUTER JOIN
(
SELECT type,
TRUNC(date_column, 'MM') AS month,
COUNT(*) AS qty
FROM parts
WHERE date_column >= ADD_MONTHS(SYSDATE, -12)
GROUP BY
type,
TRUNC(date_column, 'MM')
) p
ON (
ac.type = p.type
AND ac.month = p.month
)

Get consecutive months and days difference from date range?

So let's say I have a table like this:
subscriber_id
package_id
package_start_date
package_end_date
package_price_per_day
1081
231
2014-01-13
2014-12-31
$3.
1084
231
2014-03-21
2014-06-05
$3
1086
235
2014-06-21
2014-09-09
$4
Now I want the result for top 3 packages based on total revenue for each month for year 2014.
Note: For example for package 231 Revenue should be calculated such as 18 days of Jan * $3 +
28 days of feb * $3 + .... and so on.
For the second row the calculation would be same as first row (9 days of March* $3 + 30 days of April *$3 ....)
On the result the package should group by according to month and show rank depending on total revenue.
Sample result:
Month
Package_id
Revenue
Rank
Jan
231.
69499
1.
Jan.
235.
34345.
2.
Jan.
238.
23455.
3.
Feb.
231.
89274
1.
I wrote a query to filter the dates so that I get the active subscriber throughout the year 2014 (since initially there were values from different years),which shows the first table in the question, but I am not sure how do I break the months and days afterwards.
select subscriber_id, package_id, package_start_date, package_end_date
from (
select subscriber_id, package_id
, case when year(package_start_date) < '2014' then package_start_date = '01-Jan-2014' else package_start_date end as package_start_date
, case when year(package_start_date) > '2014' then package_end_date = '31-Dec-2014' else package_start_date end as package_end_date
, price_per_day
from subscription
) a
where year(package_start_date) = '2014' and year(package_end_date) = '2014'
Please do not emphasize on syntax - I am just trying to understand the logical approach in SQL.
Suppose you have a table that is a list of unique dates in a column called d, and the table is called d
It is then relatively trivial to do
SELECT *
FROM t
INNER JOIN d on d.d >= t.package_start_date AND d.d < t.package_end_date
Assuming you class a start date of jan 1 and an end date of jan 2 as 1 day. If you class as two, use <=
This will cause your package rows to multiply into the number of days, so start and end days of jan 1 and jan 11 would mean that row repeats 10 times. The d.d date is different on every row and you can extract the month from d.d and then group on it to give you totals for each month per package
Suppose you've CTEd that query above as x, it's like
SELECT DATEPART(month, x.dd), --the d.d date
package_id,
SUM(revenue)
FROM x
GROUP BY DATEPART(month, x.dd), package_id
Because the rows from T are repeated by Cartesian explosion when joined to d, you can safely group them or aggregate them to get them back to single values per month per package. If you have packages that stay with you more than a year you should also group on datepart year, to avoid mixing up the months from packages that stay from eg jan 2020 to feb 2021(they stay for two jans and two febs)
Then all you need to do is add the ranking of the revenue in, which looks like it would go in at the first step with something like
RANK(DATEDIFF(DAY, start, end)*revenue) OVER(PARTITION BY package_id)
I think I understand it correctly that you rank packages on total revenue over the entire period rather than per month.. look up the difference between rank and dense rank too as you may want dense instead

Calculate average and standard deviation for pre defined number of values substituting missing rows with zeros

I have a simple table that contains a record of products and their total sales per day over a year (just 3 columns - Product, Date, Sales). So, for example, if product A is sold every single day, it'll have 365 records. Similarly, if product B is sold for only 50 days, the table will have just 50 rows for that product - one for each day of sale.
I need to calculate the daily average sales and standard deviation for the entire year, which means that, for product B, I need to have additional 365-50=315 entries with zero sales to be able to calculate the daily average and standard deviation for the year correctly.
Is there a way to do this efficiently and dynamically in SQL?
Thanks
We can generate 366 rows and join the sales data to it:
WITH rg(rn) AS (
SELECT 1 AS rn
UNION ALL
SELECT a.rn + 1 AS rn
FROM rg a
WHERE a.rn <= 366
)
SELECT
*
FROM
rg
LEFT JOIN (
SELECT YEAR(saledate) as yr, DATEPART(dayofyear, saledate) as doy, count(*) as numsales
FROM sales
GROUP BY YEAR(saledate), DATEPART(dayofyear, saledate)
) s ON rg.rn = s.doy
OPTION (MAXRECURSION 370);
You can replace the nulls (where there is no sale data for that day) with e.g. AVG(COALESCE(numsales, 0)). You'll probably also need a WHERE clause to eliminate the 366th day on non leap years (such as MODULO the year by 4 and only do 366 rows if it's 0).
If you're only doing a single year, you can use a where clause in the sales subquery to give only the relevant records; most efficient is to use a range like WHERE salesdate >= DATEFROMPARTS(YEAR(GetDate()), 1, 1) AND salesdate < DATEFROMPARTS(YEAR(GetDate()) + 1, 1, 1) rather than calling a function on every sales date to extract the year from it to compare to a constant. You can also drop the YEAR(salesdate) from the select/group by if there is only a single year
If you're doing multiple years, you could make the rg generate more rows, or (perhaps simpler) cross join it to a list of years so you get 366 rows multiplied by e.g. VALUES (2015),(2016),(2017),(2018),(2019),(2020) (and make the year from the sales part of the join too)
find the first and last day of the year and then use datediff() to find number of days in that year.
After that don't use AVG on sales, but SUM(Sales) / days_in_year
select *,
days_in_year = datediff(day, first_of_year, last_of_year) + 1
from (values (2019), (2020)) v(year)
cross apply
(
select first_of_year = dateadd(year, year - 1900, 0),
last_of_year = dateadd(year, year - 1900 + 1, -1)
) d
There's a different way to look at it - don't try to add additional empty rows, just divide by the number of days in a year. While the number of days a year isn't constant (a leap year will have 366 days), it can be calculated easily since the first day of the year is always January 1st and the last is always December 31st:
SELECT YEAR(date),
product,
SUM(sales) / DATEPART(dy, DATEFROMPARTS(YEAR(date)), 12, 31))
FROM sales_table
GROUP BY YEAR(date), product

Using Date to find the inequality for sales than 500

I'm curious as to find the daily average sales for the month of December 1998 not greater than 100 as a where clause. So what I imagine is that since the table consists of the date of sales (sth like 1 december 1998, consisting of different date, months and year), amount due....First I'm going to define a particular month.
DEFINE a = TO_DATE('1-Dec-1998', 'DD-Month-YYYY')
SELECT SUBSTR(Sales_Date, 4,6), (SUM(Amount_Due)/EXTRACT(DAY FROM LAST_DAY(Sales_Date))
FROM ......
WHERE SUM(AMOUNT_DUE)/EXTRACT(DAY FROM LAST_DAY(&a)) < 100
I'm stuck as to extract the sum of amount due in the month of december 1998 for the where clause....
How can I achieve the objective?
To me, it looks like this:
select to_char(sales_date, 'mm.yyyy') month,
avg(amount_due) avg_value
from your_table
where sales_date >= trunc(date '1998-12-01', 'mm')
and sales_date < add_months(trunc(date '1998-12-01', 'mm'), 1)
group by to_char(sales_date, 'mm.yyyy')
having avg(amount_due) < 100;
WHERE clause can be simplified; it shows how to fetch certain period:
trunc to mm returns first day in that month
add_months to the above value (first day in that month) will return first day of the next month
the bottom line: give me all rows whose sales_date is >= first day of this month and < first day of the next month; basically, the whole this month
Finally, the where clause you used should actually be the having clause.
As long as the amount_due column only contains numbers, you can use the sum function.
Below SQL query should be able to satisfy your requirement.
Select SUM(Amount_Due) from table Sales where Sales_Date between '1-12-1998' and '31-12-1998'
OR
Select SUM(Amount_Due) from table Sales where Sales_Date like '%-12-1998'

Need help to make a query

The query below returns two columns - dateOfBusiness, a datetime and salesOnDate, an int. This is the sales generated by one store with an area code storeId for each day. The result set consists of 500-600 rows.
I want to get the average sales for a month, ie do average of salesOnDate. I wanted to use sub query as shown in this SO question. But it did not work for me. How do i do this ?
select count(salesOnDate)
from dbo.localSales
where productName like '%juice%'
and storeId = 'MUN123'
group by dateOfBusiness
I tried to do it like this, but it does not work -
select sum(x.count)
from
(
select count(id) as 'count'
from table
) x
Additional info -
Table structure -
dateOfBusiness | salesOnDate
-------------------------------
2013-10-5 | 200
2013-10-6 | 100
2013-10-7 | 700
I want to sum salesOnDate for any period of time, say one month.
This will give you the average sales by month along with the total number of sales for that month:
-- can use DATEPART instead of DATENAME if you prefer numbered months (i.e., 1 = January, 2 = February, etc.)
SELECT DATENAME(month, dateOfBusiness) [Month], SUM(salesOnDate) [TotalSales], AVG(salesOnDate) [AverageSales]
FROM dbo.localSales
WHERE productName like '%juice%'
AND storeId = 'MUN123'
GROUP BY DATENAME(month, dateOfBusiness)
The important part of the query is the DATENAME (or DATEPART if you use that) function, which will only take specific information from your date column, rather than the entire value.
For example, lat's say you have three records for April 11, 2013
dateOfBusiness salesOnDate
--------------------- -----------
2013-04-11 08:03:24 5
2013-04-11 11:45:17 1
2013-04-11 20:23:52 3
Using the query above will show them grouped into one month:
Month TotalSales AverageSales
----------------- -------------- ----------------
April 9 3
Are you sure you don't want sum(salesOnDate) instead of count(salesOnDate)? Count returns the number of rows, sum will return the total of all the row values.
The Average function is AVG, so you could say:
select
dateOfBusiness,storeId ,
count(salesOnDate), sum(salesOnDate), avg(salesOnDate)
from
dbo.localSales
where
productName like '%juice%'
and storeId = 'MUN123'
group by
dateOfBusiness,storeId
OK, given the edits to your question, try:
select
storeId , dob_yr, dob_mo, count(salesOnDate), sum(salesOnDate), avg(salesOnDate)
from (
select
dateOfBusiness,storeId , year(dateOfBusiness) as dob_yr,
month(dateOfBusiness) as dob_mo,
salesOnDate
from
dbo.localSales
where
productName like '%juice%'
and storeId = 'MUN123'
) t
group by
storeId , dob_yr, dob_mo