Get data for fiscal year from table without date columns - sql

I'm trying to create a query (purpose: manual DB testing) that would get the rows of the previous/current/next Fiscal Year and then the SUM(turnover)
Given
(1) the below table,
and
(2) Fiscal Year (FY) = March to February
When Previous FY -- Then 2 rows: 2016/1 to 2016/2
When Current FY -- Then 12 rows: from 2016/3 to 2017/2 (year/month)
When Future FY -- Then 1 row: 2017/3
+--------------+---------------+----------+
| Year (num) | Month (num) | Turnover |
+--------------+---------------+----------+
| 2016 | 1 | 1000 |
+--------------+---------------+----------+
| 2016 | 2 | 2000 |
+--------------+---------------+----------+
| 2016 | 3 | 3000 |
+--------------+---------------+----------+
| 2016 | 4 | 4000 |
+--------------+---------------+----------+
| 2016 | 5 | 2000 |
+--------------+---------------+----------+
| 2016 | 6 | 1000 |
+--------------+---------------+----------+
| 2016 | 7 | 2000 |
+--------------+---------------+----------+
| 2016 | 8 | 1000 |
+--------------+---------------+----------+
| 2016 | 9 | 2000 |
+--------------+---------------+----------+
| 2016 | 10 | 3000 |
+--------------+---------------+----------+
| 2016 | 11 | 4000 |
+--------------+---------------+----------+
| 2016 | 12 | 5000 |
+--------------+---------------+----------+
| 2017 | 1 | 6000 |
+--------------+---------------+----------+
| 2017 | 2 | 2000 |
+--------------+---------------+----------+
| 2017 | 3 | 1000 |
+--------------+---------------+----------+
The best solution I came up with is the below query and change the Year values to switch between years. It feels hacky to me because of creating an extra solumn with sysdate and checking for NOT NULL. Is there a more elegant way?
WITH CTE AS (
SELECT
CASE
WHEN Month BETWEEN 3 AND 12 AND Year = 2016
THEN sysdate
WHEN Month BETWEEN 1 AND 2 AND Year = 2017
THEN sysdate
END case_statement_date,
year, month, turnover, FROM Table
)
SELECT sum(turnover) FROM CTE
WHERE case_statement_date IS NOT NULL
;

Is this what you want?
select year + (case when month >= 3 then 0 else -1 end) as fiscal_year,
sum(turnover)
from t
group by year + (case when month >= 3 then 0 else -1 end) ;

Related

How to get balance from two SQL table debit and credit dynamically

Customer table
id | name | customerid
1 | Philip James | ac1001
2 | Frank Mathew | ac1002
Credit table
id| year | customer | amount
1 | 2020 | ac1001 | 1000
2 | 2020 | ac1001 | 1000
3 | 2020 | ac1001 | 1000
4 | 2020 | ac1001 | 1000
5 | 2019 | ac1001 | 1000
6 | 2019 | ac1001 | 2000
7 | 2020 | ac1002 | 2000
8 | 2020 | ac1002 | 2000
Debit table
id| year | customer| amount
1 | 2020 | ac1001 | 1000
2 | 2020 | ac1001 | 1000
3 | 2020 | ac1001 | 1000
4 | 2020 | ac1001 | 1000
5 | 2019 | ac1001 | 2000
6 | 2019 | ac1001 | 2000
7 | 2020 | ac1002 | 2000
8 | 2020 | ac1002 | 2000
I am trying to get the balance for each customer dynamically in respect to the year, i tried using this;
SELECT debit.year,customers.name,customers.customerid,SUM(debit.amount),SUM(credit.amount),
SUM(COALESCE((debit.amount),0)-COALESCE((credit.amount),0))AS balance FROM
customers
RIGHT JOIN credit ON customers.customerid=credit.customer
RIGHT JOIN debit ON customers.customerid=debit.customer GROUP BY customers.customerid,debit.year
Query Result
year| customer | sum(debit)| sum(credit)| Balance
2020 | ac1001 | 48000 | 42000 | 6000
2020 | ac1002 | 8000 | 6000 | 2000
But what i need is this table below, thank you
Expected Result
year| customer | sum(debit)| sum(credit)| Balance
2019 | ac1001 | 4000 | 3000 | 1000
2020 | ac1001 | 4000 | 4000 | 0
2020 | ac1002 | 4000 | 4000 | 0
union the two tables and then aggregate. You can use a cumulative sum to calculate the balance:
select year, customer, sum(amount) as amount_in_year,
sum(sum(amount)) over (partition by customer order by year) as end_of_year_balance
from ((select id, year, customer, amount
from credit
) union all
(select id, year, customer, - amount
from debit
)
) cd
group by year, customer;
EDIT:
For the revised question:
select year, customer, sum(credit) as sum_credit, sum(debit) as sum_debit,
sum(sum(credit - debit)) over (partition by customer order by year) as end_of_year_balance
from ((select id, year, customer, amount as credit, 0 as debit
from credit
) union all
(select id, year, customer, 0 as credit, amount as debit
from debit
)
) cd
group by year, customer;

Time dimension table in SQLPlus

I'm doing a datawarehouse project for college with Oracle Database (SQLPlus).
I need to create the time dimension table and to populate it. The table needs to be like this:
It needs to go from 2004 to 2019.
I've tried different things and queries that I've found but they don't works and, sadly, I don't know enough about SQLPlus to create one on my own (or to successfully modify one). I'm completely lost.
Thank you very much for your help and patience.
Do not store all the columns; use virtual columns instead to calculate derived data otherwise you will find that your columns could be inconsistent:
CREATE TABLE table_name (
id NUMBER(10,0)
GENERATED ALWAYS AS IDENTITY
CONSTRAINT table_name__id__pk PRIMARY KEY,
"DATE" DATE
CONSTRAINT table_name__date__nn NOT NULL
CONSTRAINT table_name__date__u UNIQUE
CONSTRAINT table_name__date__chk CHECK ( "DATE" = TRUNC( "DATE" ) ),
id_day_of_week NUMBER(1,0)
GENERATED ALWAYS AS ( "DATE" - TRUNC( "DATE", 'IW' ) + 1 ),
day_of_week VARCHAR2(9)
GENERATED ALWAYS AS ( CAST( TRIM( TO_CHAR( "DATE", 'DAY', 'NLS_DATE_LANGUAGE = AMERICAN' ) ) AS VARCHAR2(9) ) ),
is_holiday NUMBER(1,0)
CONSTRAINT table_name__id_holiday__chk CHECK ( is_holiday IN ( 0, 1 ) ),
id_month NUMBER(2,0)
GENERATED ALWAYS AS ( EXTRACT( MONTH FROM "DATE" ) ),
month VARCHAR2(9)
GENERATED ALWAYS AS ( CAST( TRIM( TO_CHAR( "DATE", 'MONTH', 'NLS_DATE_LANGUAGE = AMERICAN' ) ) AS VARCHAR2(9) ) ),
id_year NUMBER(5,0)
GENERATED ALWAYS AS ( EXTRACT( YEAR FROM "DATE" ) ),
id_total NUMBER(1,0)
GENERATED ALWAYS AS ( 1 ),
total CHAR(5)
GENERATED ALWAYS AS ( 'Total' )
);
Note:
You should not name the column DATE as its a keyword and you will need to surround it in double-quotes and use the same case every time you use it.
The id_day_of_week is based on the day of the ISO8601 week because relying on TO_CHAR( "DATE", 'D' ) depends on the NLS_TERRITORY setting as to which day of the week is the first day; this way it is independent of any settings.
The day_of_week and month columns have a fixed language.
It is unclear what id_total and total should contain so these are generated as literal values; if you want to have non-static data in these columns then remove the GENERATED ... part of the declaration.
Then you can populate it using:
INSERT INTO table_name ( "DATE", is_holiday )
SELECT DATE '2004-01-01' + LEVEL - 1, 0
FROM DUAL
CONNECT BY DATE '2004-01-01' + LEVEL - 1 < DATE '2020-01-01';
And update the holiday dates using an UPDATE statement according to your territory.
Then if you do:
SELECT *
FROM table_name
ORDER BY "DATE" ASC
FETCH FIRST 32 ROWS ONLY;
The output is:
ID | DATE | ID_DAY_OF_WEEK | DAY_OF_WEEK | IS_HOLIDAY | ID_MONTH | MONTH | ID_YEAR | ID_TOTAL | TOTAL
-: | :-------- | -------------: | :---------- | ---------: | -------: | :------- | ------: | -------: | :----
1 | 01-JAN-04 | 4 | THURSDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
2 | 02-JAN-04 | 5 | FRIDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
3 | 03-JAN-04 | 6 | SATURDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
4 | 04-JAN-04 | 7 | SUNDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
5 | 05-JAN-04 | 1 | MONDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
6 | 06-JAN-04 | 2 | TUESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
7 | 07-JAN-04 | 3 | WEDNESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
8 | 08-JAN-04 | 4 | THURSDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
9 | 09-JAN-04 | 5 | FRIDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
10 | 10-JAN-04 | 6 | SATURDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
11 | 11-JAN-04 | 7 | SUNDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
12 | 12-JAN-04 | 1 | MONDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
13 | 13-JAN-04 | 2 | TUESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
14 | 14-JAN-04 | 3 | WEDNESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
15 | 15-JAN-04 | 4 | THURSDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
16 | 16-JAN-04 | 5 | FRIDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
17 | 17-JAN-04 | 6 | SATURDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
18 | 18-JAN-04 | 7 | SUNDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
19 | 19-JAN-04 | 1 | MONDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
20 | 20-JAN-04 | 2 | TUESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
21 | 21-JAN-04 | 3 | WEDNESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
22 | 22-JAN-04 | 4 | THURSDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
23 | 23-JAN-04 | 5 | FRIDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
24 | 24-JAN-04 | 6 | SATURDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
25 | 25-JAN-04 | 7 | SUNDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
26 | 26-JAN-04 | 1 | MONDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
27 | 27-JAN-04 | 2 | TUESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
28 | 28-JAN-04 | 3 | WEDNESDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
29 | 29-JAN-04 | 4 | THURSDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
30 | 30-JAN-04 | 5 | FRIDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
31 | 31-JAN-04 | 6 | SATURDAY | 0 | 1 | JANUARY | 2004 | 1 | Total
32 | 01-FEB-04 | 7 | SUNDAY | 0 | 2 | FEBRUARY | 2004 | 1 | Total
db<>fiddle here
create table date_dim
(id number(38),
date date,
id_dayofweek number(38),
dayofweek varchar(100),
id_holiday number(38),
id_month number(38),
month varchar(100),
id_year number(38),
id_total number(38),
Total varchar(100));
Use above query to create the table.
Regarding the data, you can generate it through connect by clause.
insert into date_dim
(select level as id, to_date('31-DEC-2003', 'DD-MON-YYYY') + level as date1,
case when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Monday' then 2
when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Tuesday' then 3
when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Wednesday' then 4
when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Thursday' then 5
when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Friday' then 6
when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Saturday' then 7
when ltrim(rtrim(to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day'))) = 'Sunday' then 1 end as id_dayofweek,
to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Day') as dayofweek,
0 as id_holiday,
to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'MM') as id_month,
to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'Month') as month,
to_char(to_date('31-DEC-2003', 'DD-MON-YYYY') + level, 'YYYY') as year,
1 as id_total,
'Total' as Total
from dual
connect by level < = 5844);

Summing Sales Based On Different Year Cases

| Product ID | YearBought | Sales | Min_Year | Max_Year |
| 1 | 2016 | $20 | 2011 | 2016 |
| 2 | 2016 | $10 | 2016 | 2018 |
| 2 | 2017 | $30 | 2016 | 2018 |
| 3 | 2017 | $5 | 2015 | 2019 |
| 3 | 2018 | $10 | 2015 | 2019 |
| 3 | 2018 | $20 | 2015 | 2019 |
| 3 | 2019 | $5 | 2015 | 2019 |
| 3 | 2019 | $30 | 2015 | 2019 |
| 4 | 2018 | $5 | 2018 | 2020 |
| 4 | 2019 | $10 | 2018 | 2020 |
| 4 | 2020 | $20 | 2018 | 2020 |
Min_Year = the year the product was first introduced
Max_Year + 1 = Product drop off year
Above is a sample of the table I'm working with. Trying to find:
the sum of sales new products brought in the year they were first introduced
the sum of "dropped sales" aka the sum of sales from products the year after they dropped off (had no sales). (Ex. Product brought in $15 in 2018 but had no sales in 2019, want to show $15 as dropped sales in 2019)
Expected Output:
| YearBought | New Product Sales | Dropped Product Sales |
| 2016 | $10 | |
| 2017 | | $20 |
| 2018 | $5 | |
| 2019 | | |
| 2020 | | $35 |
was thinking something like this but it's not working. any help would be appreciated!
select
YearBought,
sum(case when yearbought=min_year then sales else 0 end) as NewSales,
sum(case when yearbought=max_year+1 then sales else 0 end) as DropSales
from
#t
group by
yearbought
Aggregate the dropped product sales separately from the new product sales, then join the aggregations. You can do this with subqueries, or, as I have done below, with common table expressions.
with
droppedProds as (
select droppedYear = yearBought + 1,
foregoneSales = sum(sales)
from #t t
where YearBought = Max_Year
group by YearBought
),
newSales as (
select YearBought,
sales = sum(sales),
newSales = sum(case when yearBought = min_year then sales end)
from #t t
group by YearBought
)
select YearBought,
n.sales,
n.newSales,
d.foregoneSales
from newSales n
left join droppedProds d on n.yearBought = d.droppedYear
order by YearBought;
Results in:
+------------+-------+----------+---------------+
| YearBought | sales | newSales | foregoneSales |
|------------|-------|----------|---------------|
| 2016 | 30 | 10 | |
| 2017 | 35 | | 20 |
| 2018 | 35 | 5 | |
| 2019 | 45 | | |
| 2020 | 20 | | 35 |
+------------+-------+----------+---------------+
You could list the dates first, then join it with the original table and do conditional aggregation:
select
y.yearbought,
sum(case when t.yearbought = t.min_year then sales end) new_product_sales,
sum(case when t.yearbought = t.max_year then sales end) dropped_product_sales
from (select distinct yearbought from #t) y
inner join #t on y.yearbought in (t.min_year, t.max_year + 1)
group by y.yearbought
I think your problem is that the years are staggered.
with n as (
select year_bought as y, sum(sales) as sales,
sum(case when year_bought = min_year then sales end) as ns
from T group by year_bought
), d as (
select year_bought + 1 as y,
sum(case when year_bought = max_year then sales end) as ds
from T group by year_bought
)
select y, sales,
ns as newSales, coalesce(dropped, 0) as foregoneSales
from n left outer join p on p.y = n.y;
or use lead()
select year_bought, sum(Sales) as Sales,
sum(case when year_bought = min_year then sales end) as newSales,
coalesce(lead(sum(case when year_bought = max_year then sales end))
over (order by year_bought), 0) as foregoneSales
from T
group by year_bought;

Join two columns as a date in sql

I am currently working with a report through Microsoft Query and I ran into this problem where I need to calculate the total amount of money for the past year.
The table looks like this:
Item Number | Month | Year | Amount |
...........PAST YEARS DATA...........
12345 | 1 | 2019 | 10 |
12345 | 2 | 2019 | 20 |
12345 | 3 | 2019 | 15 |
12345 | 4 | 2019 | 12 |
12345 | 5 | 2019 | 11 |
12345 | 6 | 2019 | 12 |
12345 | 7 | 2019 | 12 |
12345 | 8 | 2019 | 10 |
12345 | 9 | 2019 | 10 |
12345 | 10 | 2019 | 10 |
12345 | 11 | 2019 | 10 |
12345 | 12 | 2019 | 10 |
12345 | 1 | 2020 | 10 |
12345 | 2 | 2020 | 10 |
How would you calculate the total amount from 02-2019 to 02-2020 for the item number 12345?
Assuming that you are running SQL Server, you can recreate a date with datefromparts() and use it for filtering:
select sum(amount)
from mytable
where
itemnumber = 12345
and datefromparts(year, month, 1) >= '20190201'
and datefromparts(year, month, 1) < '20200301'
You can use this also
SELECT sum(amount) as Amount
FROM YEARDATA
WHERE ( Month >=2 and year = '2019')
or ( Month <=2 and year = '2020')
and ItemNumber = '12345'

Group By Different Values

I would like to group by the first day and then the rest of the month, I have data that spans years.
I have data like below:
--------------------------------------
DAY MONTH YEAR VISITOR_COUNT
--------------------------------------
1 | 12 | 2014 | 16260
2 | 12 | 2014 | 15119
3 | 12 | 2014 | 14464
4 | 12 | 2014 | 13746
5 | 12 | 2014 | 13286
6 | 12 | 2014 | 14352
7 | 12 | 2014 | 19293
8 | 12 | 2014 | 13338
9 | 12 | 2014 | 13961
10 | 12 | 2014 | 9519
11 | 12 | 2014 | 10204
12 | 12 | 2014 | 9380
13 | 12 | 2014 | 11611
14 | 12 | 2014 | 14839
15 | 12 | 2014 | 10051
16 | 12 | 2014 | 8983
17 | 12 | 2014 | 7348
18 | 12 | 2014 | 7258
19 | 12 | 2014 | 7205
20 | 12 | 2014 | 6113
21 | 12 | 2014 | 5316
22 | 12 | 2014 | 6914
23 | 12 | 2014 | 6880
24 | 12 | 2014 | 6289
25 | 12 | 2014 | 6000
26 | 12 | 2014 | 13328
27 | 12 | 2014 | 10367
28 | 12 | 2014 | 7946
29 | 12 | 2014 | 9042
30 | 12 | 2014 | 9408
31 | 12 | 2014 | 8411
1 | 1 | 2015 | 9965
2 | 1 | 2015 | 10560
3 | 1 | 2015 | 9662
4 | 1 | 2015 | 8735
5 | 1 | 2015 | 12817
6 | 1 | 2015 | 13516
7 | 1 | 2015 | 9800
8 | 1 | 2015 | 10629
9 | 1 | 2015 | 12325
10 | 1 | 2015 | 11899
11 | 1 | 2015 | 11049
12 | 1 | 2015 | 13934
13 | 1 | 2015 | 16833
14 | 1 | 2015 | 13434
15 | 1 | 2015 | 13128
16 | 1 | 2015 | 14660
17 | 1 | 2015 | 11951
18 | 1 | 2015 | 10916
19 | 1 | 2015 | 14126
20 | 1 | 2015 | 16909
21 | 1 | 2015 | 16555
22 | 1 | 2015 | 14726
23 | 1 | 2015 | 14642
24 | 1 | 2015 | 13067
25 | 1 | 2015 | 11738
26 | 1 | 2015 | 15353
27 | 1 | 2015 | 17935
28 | 1 | 2015 | 14448
29 | 1 | 2015 | 15372
30 | 1 | 2015 | 16694
31 | 1 | 2015 | 16763
I would like to be able to group it like below:
--------------------------------------
DAY MONTH YEAR VISITOR_COUNT
--------------------------------------
1 | 12 | 2014 | 16260
2-31| 12 | 2014 | 309971
1 | 1 | 2015 | 9965
2-31| 1 | 2015 | 404176
Microsoft SQL Server 2016. Compatibility level: SQL Server 2005 (90)
Just use case:
select (case when min(day) = 1 then '1'
else concat(min(day), '-', max(day))
end) as day, month, year,
sum(visitor_count)
from t
group by year, month,
(case when day = 1 then 1 else 2 end);
Okay, this is a little tricky. The case in the group by and the case in the select are different. The group by just puts the days into two categories, 1 and others. The select chooses the minimum and maximum days in the month, to construct the range string.
EDIT:
Oy, SQL Server 2005 ???
Of course, you can do the same thing with + and type conversion, or using replace():
select (case when min(day) = 1 then '1'
else replace(replace('#min-#max', '#min', min(day)), '#max', max(day))
end) as day, month, year,
sum(visitor_count)
from t
group by year, month,
(case when day = 1 then 1 else 2 end);