Bigquery calculate split from the beginning of month until end of month/current date - sql

I have some problems, I want to calculate amount split from the beginning of month until end of month/current date and split by product like this :
data :
Date_payment
product
amount
2020-02-01
aa
10
2020-02-01
aa
20
2020-02-03
bb
5
2020-02-29
bb
5
2020-03-01
aa
4
2020-03-03
aa
3
2020-03-03
bb
1
let say Current date is 2020-03-05
i want to calculate split by product and by month from the beginning of month until end of month/current date
my expectation result is :
Date_Report
product
Total_amount
2020-02-01
aa
30
2020-02-02
aa
30
2020-02-03
aa
30
2020-02-03
bb
5
2020-02-04
aa
30
2020-02-04
bb
5
....so on until..
Date_Report
product
Totalamount
2020-02-29
aa
30
2020-02-29
bb
10
2020-03-01
aa
4
2020-03-02
aa
4
2020-03-03
aa
7
2020-03-03
bb
1
2020-03-04
aa
7
2020-03-04
bb
1
2020-03-05
aa
7
2020-03-05
bb
1
I want to see the total amount of each day from the beginning of the month to the end of the month for each month.
anyone can help ?

You need to generate the days, cross join with the products and bring in the existing data:
select report_date, p.product, coalesce(month_amount, 0) as month_amount
from (unnest(generate_date_array(date('2020-01-01'), current_date, interval 1 day)) report_date cross join
(select distinct product from t) p left join
(select date_trunc(report_date, month) as mon, product,
sum(amount) as month_amount
from t
group by 1, 2
) t
on t.mon = date_trunc(report_date, month) and t.product = p.product;

The following query should do the job:
SELECT
DATE_TRUNC(Date_payment, MONTH) AS payment_month,
product,
SUM(amount) AS total_amount
FROM
transactions
GROUP BY
payment_month,
product
ORDER BY
payment_month,
product
With the table you provided the result is:

Assuming your columns are as below :
create table "Example"
( "DatePayment" date,
"Product" nvarchar(5),
"Amount" int
);
Your logic could be(please change to BigQuery syntax):
DO BEGIN
DECLARE FIRSTDATE,LASTDATE DATE;
SELECT TO_DATE(YEAR(MIN("DatePayment"))||'-'||MONTH(MIN("DatePayment"))||'-01') INTO FIRSTDATE FROM "Example";
SELECT LAST_DAY(MAX("DatePayment")) INTO LASTDATE FROM "Example";
VAR1= SELECT D."DT",E."Product",case when S."Amount" is null then 0 else S."Amount" end as "Amount_CC" FROM
(select "GENERATED_PERIOD_START" AS "DT" from "SERIES_GENERATE_DATE"('INTERVAL 1 DAY' ,:FIRSTDATE, :LASTDATE ) ) D
CROSS JOIN ( SELECT DISTINCT "Product" FROM "Example" ) E
LEFT OUTER JOIN ( select "DatePayment","Product","Amount" from "Example" ) S
ON D."DT"=S."DatePayment" AND E."Product"=S."Product";
VAR2= select * , SUM("Amount_CC") OVER (PARTITION BY MONTH("DT"), "Product" ORDER BY "DT"
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS "Amount" from :VAR1 ;
select "DT" as "DatePayment","Product","Amount" from :VAR2 where "Amount" <>0 order by "DT","Product";
END;
You will get the result for all months in your dataset.
Results below :

Related

How can I select records from the last value accumulated

I have the next data: TABLE_A
RegisteredDate
Quantity
2022-03-01 13:00
100
2022-03-01 13:10
20
2022-03-01 13:20
-80
2022-03-01 13:30
-40
2022-03-02 09:00
10
2022-03-02 22:00
-5
2022-03-03 02:00
-5
2022-03-03 03:00
25
2022-03-03 03:20
-10
If I add cumulative column
select RegisteredDate, Quantity
, sum(Quantity) over ( order by RegisteredDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as Summary
from TABLE_A
RegisteredDate
Quantity
Summary
2022-03-01 13:00
100
100
2022-03-01 13:10
20
120
2022-03-01 13:20
-80
40
2022-03-01 13:30
-40
0
2022-03-02 09:00
10
10
2022-03-02 22:00
-5
5
2022-03-03 02:00
-5
0
2022-03-03 03:00
25
25
2022-03-03 03:20
-10
15
Is there a way to get the following result with a query?
RegisteredDate
Quantity
Summary
2022-03-03 03:00
25
25
2022-03-03 03:20
-10
15
This result is the last records after the last zero.
EDIT:
Really for the solution to this problem I need the: 2022-03-03 03:00 is the first date of the last records after the last zero.
You can try to use SUM aggregate window function to calculation grp column which part represent to last value accumulated.
Query 1:
WITH cte AS
(
SELECT RegisteredDate,
Quantity,
sum(Quantity) over (order by RegisteredDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as Summary
FROM TABLE_A
), cte2 AS (
SELECT *,
SUM(CASE WHEN Summary = 0 THEN 1 ELSE 0 END) OVER(order by RegisteredDate desc) grp
FROM cte
)
SELECT RegisteredDate,
Quantity
FROM cte2
WHERE grp = 0
ORDER BY RegisteredDate
Results:
| RegisteredDate | Quantity |
|----------------------|----------|
| 2022-03-03T03:00:00Z | 25 |
| 2022-03-03T03:20:00Z | -10 |
Use a CTE that returns the summary column and NOT EXISTS to filter out the rows that you don't need:
WITH cte AS (SELECT *, SUM(Quantity) OVER (ORDER BY RegisteredDate) Summary FROM TABLE_A)
SELECT c1.*
FROM cte c1
WHERE NOT EXISTS (
SELECT 1
FROM cte c2 WHERE c2.RegisteredDate >= c1.RegisteredDate AND c2.Summary = 0
)
ORDER BY c1.RegisteredDate;
There is no need for ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW in the OVER clause of the window function, because this is the default behavior.
See the demo.
Try this:
with u as
(select RegisteredDate,
Quantity,
sum(Quantity) over (order by RegisteredDate ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as Summary
from TABLE_A)
select * from u
where RegisteredDate >= all(select RegisteredDate from u where Summary = 0)
and Summary <> 0;
Fiddle
Basically what you want is for RegisteredDate to be >= all RegisteredDatess where Summary = 0, and you want Summary <> 0.
When using window functions, it is necessary to take into account that RegisteredDate column is not unique in TABLE_A, so ordering only by RegisteredDate column is not enough to get a stable result on the same dataset.
With A As (
Select ROW_NUMBER() Over (Order by RegisteredDate, Quantity) As ID, RegisteredDate, Quantity
From TABLE_A),
B As (
Select A.*, SUM(Quantity) Over (Order by ID) As Summary
From A)
Select Top 1 *
From B
Where ID > (Select MAX(ID) From B Where Summary=0)
ID
RegisteredDate
Quantity
Summary
8
2022-03-03 03:00
25
25

SQL query to compare budget to actual expenses

I'm struggling to build a query comparing budget to actual expense items.
The budget table has a single record per month/category on the first day of the month whereas the expense table has many records throughout the days of the month.
My desired result:
YEAR
MONTH
category
budgetAmt
sumExpenseAmt
2021
1
daily
100
49
2021
1
monthly
42
87
2021
2
daily
101
36
2021
2
monthly
55
82
What I'm getting:
YEAR
MONTH
category
budgetAmt
sumExpenseAmt
2021
1
daily
100
85
2021
1
monthly
42
169
2021
2
daily
101
85
2021
2
monthly
55
169
The amounts in "sumExpenseAmt" are wrong AND they're repeating.
(85 is the sum of all expense-daily items (jan + feb): 40 + 9 + 32 + 4)
(169 is the sum of all expense-monthly items (jan + feb): 83 + 4 + 75 +7)
MY SQL:
SELECT YEAR( "b"."date" ) AS "Year"
, MONTH( "b"."date" ) AS "Month"
, "b"."category"
, "b"."budgetAmt"
, SUM( "e"."expenseAmt" ) AS "sumExpenseAmt"
FROM "budget" AS "b"
JOIN "expense" AS "e" ON "b"."category" = "e"."category"
GROUP BY YEAR( "b"."date" ), MONTH( "b"."date" ), "b"."category", "b"."budgetAmt"
table: budget
date
category
budgetAmt
2021-01-01
daily
100
2021-01-01
monthly
42
2021-02-01
daily
101
2021-02-01
monthly
55
table: expense
date
category
expenseAmt
2021-01-04
daily
40
2021-01-07
daily
9
2021-01-08
monthly
83
2021-01-25
monthly
4
2021-02-01
daily
32
2021-02-05
daily
4
2021-02-15
monthly
75
2021-02-20
monthly
7
I've tried aggregating the expense table with a query and feeding the result into my initial SQL query, but that gives me the same result.
query: qry_summary_expense
date
category
budgetAmt
2021-01-01
daily
49
2021-01-01
monthly
87
2021-02-01
daily
36
2021-02-01
monthly
82
SELECT YEAR( "b"."date" ) AS "Year"
, MONTH( "b"."date" ) AS "Month"
, "b"."category", "b"."budgetAmt"
, SUM( "e"."expenseAmt" ) AS "sumExpenseAmt"
FROM "budget" AS "b"
JOIN "qry_summary_expense" AS "e" ON "b"."category" = "e"."category"
GROUP BY YEAR( "b"."date" ), MONTH( "b"."date" ), "b"."category", "b"."budgetAmt"
I'd join on both month and category
select year(b.date) as year,
month(b.date) as month,
b.category,
avg(budgetAmt) as budgetAmt,
sum(expenseAmt) as expenseAmt
from expense e
join budget b
on (month(b.date) = month(e.date)
and b.category = e.category)
group by year(b.date), month(b.date), b.category
You need to join the two tables on category AND month (using eomonth (end of month) does the trick).
SELECT Year(Eomonth(e.date)) AS year,
Month(Eomonth(e.date)) AS month,
e.category,
Avg(budgetamt) AS budgetAmt,
Sum(expenseamt) AS sumExpenseAmt
FROM expense e
INNER JOIN budget b
ON Eomonth(e.date) = Eomonth(b.date)
AND e.category = b.category
GROUP BY e.category,
Eomonth(e.date);
Fiddle
Alternatively you can use left or right joins based on your need.
select b.Year,b.Month,b.category,Budget, Expenses
from(
Select year(date) [Year] ,month(date) [Month] ,category,sum(budgetAmt) Budget
from budget
group by year(date),month(date),category
) b
Join
(
Select year(date) [Year] ,month(date) [Month] ,category,sum(expenseAmt) Expenses
from expense
group by year(date),month(date),category
) e
on b.Month = e.Month and b.Year = e.Year and b.category = e.category
Another Approach Using EOMONTH Function:
select Year(a.dates) Year ,month(a.dates) Month,a.category,sum(Budget) Budget, sum(Expenses) Expenses
from(
Select EOMONTH(date) dates,category,sum(budgetAmt) Budget
from #budget
group by EOMONTH(date),category
) a
Join
(
Select EOMONTH(date) dates,category,sum(expenseAmt) Expenses
from #expense
group by EOMONTH(date),category
) b
on a.dates = b.dates and a.category = b.category
group by Year(a.dates),month(a.dates),a.category

SQL query group by with null values is returning duplicates

I have following query
My #dates table has following records:
month year saledate
9 2020 2020-09-01
10 2020 2020-10-01
11 2020 2020-11-01
with monthlysalesdata as(
select month(salesdate) as salemonth, year(salesdate) as saleyear,salesrepid, salespercentage
from salesrecords r
join #dates d on d.saledate = r.salesdate
group by salesrepid, salesdate),
averagefor3months as(
select 0 as salemonth, 0 as saleyear, salesrepid, salespercentage
from monthlysalesdata
group by salesrepid)
finallist as(
select * from monthlysalesdata
union
select * from averagefor3months
This query returns following records which gives duplicate for a averagefor3months result set when there is null record in the first monthlyresultdata. how to achieve average for 3 months as one record instead of having duplicates?
salesrepid salemonth saleyear percentage
232 0 0 null -------------this is the duplicate record
232 0 0 90
232 9 2020 80
232 10 2020 null
232 11 2020 100
My first cte has this result:
salerepid month year percentage
---------------------------------------------
232 9 2020 80
232 10 2020 null
232 11 2020 100
My second cte has this result:
salerepid month year percentage
---------------------------------------------
232 0 0 null
232 0 0 90
How to avoid the duplicate record in my second cte,
I suspect that you want a summary row per sales rep based on some aggregation. Your question is not clear on what is needed for the aggregation, but something like this:
with ym as (
select r.salesrepid, d.year, d.month, sum(<something>) as whatever
from salesrecords r join
#dates d
on d.saledate = r.salesdate
group by r.salesrepid, d.year, d.month
)
select ym.*
from ym
union all
select salesrepid, null, null, avg(whatever)
from hm
group by salesrepid;
I updated to selected the group by from the table directly instead of the previous cte and got my results. Thank you all for helping
with ym as (
select r.salesrepid, d.year, d.month, sum(<something>) as whatever
from salesrecords r join
#dates d
on d.saledate = r.salesdate
group by r.salesrepid, d.year, d.month
),
threemonthsaverage as(
select r.salesrepid, r.year, r.month, sum(something) as whatever
from salesrecords as r
group by salesrepid)
select ym *
union
select threemonthsaverage*

How to get daily budget based on monthly budget and workings days

Have have 2 tables.
One table with month budget, and one table with workings days.
What I want, is find out daily budget based on the monthly budget and working days.
Example:
August have a budget on 1000 and have 21 workings day.
September have a budget on 2000 and 23 workings days
I want to figure out what the total budget betweens two dates.
Ex: between 2020-08-02 and 2020-09-15
But must be sure that, days in august takes budget from august, days from september takes budget from september etc.
tbBudget:
Date | Amount
2020-08-01 | 1000
2020-09-01 | 2000
2020-10-01 | 3000
tbWorkingDays
Date | WorkingDay
2020-08-01 | 0
2020-08-02 | 0
2020-08-03 | 1
2020-08-04 | 1
2020-08-05 | 1
2020-08-06 | 1
2020-08-07 | 1
2020-08-08 | 1
...
2020-09-01 | 1
2020-09-02 | 1
2020-09-03 | 0
2020-09-04 | 1
...
2020-10-01 | 1
2020-10-02 | 0
2020-10-03 | 1
2020-10-04 | 1
I have no idea how to solve this issue. Can you help me?
My result should be like:
Date | WorkingDay | BudgetAmount
2020-08-02 | 0 | 0.0
2020-08-03 | 1 | 47.6
2020-08-04 | 1 | 47.6
2020-08-05 | 1 | 47.6
..
2020-09-13 | 1 | 86.9
2020-09-14 | 1 | 86.9
2020-09-15 | 1 | 86.9
Using CTE and group by:
with CTE1 AS(
SELECT FORMAT(A.DATE, 'MMyyyy') DATE, B.AMOUNT, SUM(CASE WHEN [WorkingDay] = 1 THEN 1 ELSE 0 END) AS TOTAL_WORKING_DAYS
FROM tbWorkingDays A INNER JOIN tbBudget B
ON (FORMAT(A.DATE, 'MMyyyy') = FORMAT(B.DATE, 'MMyyyy')) GROUP BY FORMAT(A.[DATE], 'MMyyyy'), B.AMOUNT
)
SELECT A.DATE,
A.WORKINGDAY,
CASE WHEN A.WORKINGDAY = 1 THEN B.AMOUNT/B.TOTAL_WORKING_DAYS
ELSE 0 END AS BudgetAmount
FROM CTE1 B
INNER JOIN
tbWorkingDays A
ON (FORMAT(A.DATE, 'MMyyyy') = B.DATE);
Assuming that the budgets are by month:
select wd.*,
(case when workingday = 0 then 0
else wd.budget * 1.0 / sum(wd.workingday) over (partition by wd.date)
end) as daily_amount
from tbWorkingDays wd join
tblBudget b
on wd.date >= b.date and wd.date < dateadd(month, 1, wd.date);
If the budget dates are not per month, then use apply instead:
select wd.*,
(case when workingday = 0 then 0
else wd.budget * 1.0 / sum(wd.workingday) over (partition by wd.date)
end) as daily_amount
from tbWorkingDays wd cross apply
(select top (1) b.*
from tblBudget b
where wd.date >= b.date
order by b.date desc
) b
Use sum as an analytical function to get the number of workingdays pr month, then divide out
Here is a functioning solution
with tally as
(
SELECT
row_number() over (order by (select null))-1 n
from (values (null),(null),(null),(null),(null),(null),(null),(null),(null),(null),(null)) a(a)
cross join (values (null),(null),(null),(null),(null),(null),(null),(null),(null),(null),(null)) b(b)
cross join (values (null),(null),(null),(null),(null),(null),(null),(null),(null),(null),(null)) c(c)
)
, tbWorkingDays as
(
select
cast(dateadd(day,n,'2020-01-01') as date) [Date],
iif(DATEPART(WEEKDAY,cast(dateadd(day,n,'2020-01-01') as date)) in (1,7),0,1) WorkingDay
from tally
where n<365
)
, tbBudget AS
(
select * from
(values
(cast('2020-08-01' as date), cast(1000 as decimal(19,2)))
,(cast('2020-09-01' as date), cast(2000as decimal(19,2)))
,(cast('2020-10-01' as date), cast(3000as decimal(19,2)))
) a([Date],[Amount])
)
select
a.[Date]
,a.WorkingDay*
(b.Amount/
sum(a.WorkingDay) over (partition by year(a.Date)*100+month(a.Date)))
from tbWorkingDays a
inner join tbBudget b
on a.Date between b.Date and dateadd(day,-1,dateadd(month,1,b.date))
The work is done here:
select
a.[Date]
,a.WorkingDay*
(b.Amount/
sum(a.WorkingDay) over (partition by year(a.Date)*100+month(a.Date)))
from tbWorkingDays a
inner join tbBudget b
on a.Date between b.Date and dateadd(day,-1,dateadd(month,1,b.date))
The expression
sum(a.WorkingDay) over (partition by year(a.Date)*100+month(a.Date))
Sums the number of workingdays for the current month. I then join against the budget and take the sum for the month and divide by the expression above.
To make sure there only is budget on workingdays, I simply multiply by "workingday", since 0 is a non workingday, the sum will be 0 for all non workingdays.

Count data per day between two dates

Hi i'm trying to count the total late remark per day between two dates inputs by the user.
for example:
ID NAME DATE_TIME REMARKS
1 Aa 2020-01-18 09:57:56 LATE
2 Aa 2020-01-18 10:57:56 LATE
3 Aa 2020-01-19 06:52:56
4 Aa 2020-01-19 09:57:56 LATE
5 Aa 2020-01-19 09:57:56 LATE
6 Aa 2020-01-21 09:57:56 Late
Expected result.
NAME DATE count
Aa 2020-01-18 2
Aa 2020-01-19 2
Aa 2020-01-20 0
Aa 2020-01-21 1
The Data type of DATE_TIME is varhcar2
this is my attemp but i dont know how to achive it.
Select Count(REMARKS) countBT from TBLACCESSLOGS WHERE To_date(DATE_TIME,'YYYY-MM-DD') between To_date('2020-02-18','YYYY-MM-DD') and To_date('2020-02-20','YYYY-MM-DD')
and i get error date format picture ends before converting entire input string pointing on DATE_TIME as i execute.
Hope someone help me with this.
Thank you in advance
Since you face the prospect that there may be missing dates within the range your looking for you need to generate an entry for each date in that range. You the join those dates with your table, counting the number of remarks column.
with date_parms as
(select to_date('&Start_Date','yyyy-mm-dd') start_date
, to_date('&End_Date','yyyy-mm-dd') end_date
from dual
)
, date_list as
(select start_date+lev-1 t_date
from date_parms
, ( select level lev
from dual
connect by level <= (select end_date - start_date + 1
from date_parms
)
)
)
select t_date "Date"
, name
, count(*) "Num Late"
from date_list dl
left join lates l on trunc(l.date_time) = dl.t_date and lower(l.remark) = 'late'
where 1=1 --lower(l.remark) = 'late'
group by trunc(t_time), name;
Note. Once the initial parameters (start and end dates) are converted to from strings to dates no further date-string manipulation is required.
Completely edited; this is a multi-step process, the real important SQL logic is in "THE_GOODS". THe generation of days is in "DAYS" and I took that from here: https://www.zetetic.net/blog/2009/2/12/generating-a-sequential-date-series-in-oracle.html -- I don't understand it much more than ctl-c/ctl-v.
"PERMS" makes the permutation of dates/names, then that is left-joined to THE_GOODS to get the counts. So for each combo of user and dates in range you get one row, and the count from THE_GOODS, or zero if there's no matching row.
to fiddle with it: http://sqlfiddle.com/#!4/42618/8
WITH DAYS AS
(SELECT TO_CHAR(TRUNC(TO_DATE('01-JAN-2020') + ROWNUM - 1, 'DD'), 'YYYY-MM-DD') AS ADAY
FROM (
SELECT ROWNUM FROM (
SELECT 1 FROM DUAL
CONNECT BY LEVEL <= (TO_DATE('08-JAN-2020') - TO_DATE('01-JAN-2020'))
)
)
),
THE_GOODS AS (
select name, to_char(DATE_TIME, 'YYYY-MM-DD') AS ADAY, count(*) AS HOW_MANY
from TBLACCESSLOGS
where trunc(DATE_TIME, 'DD') between to_date('2020-01-01', 'YYYY-MM-DD')
and to_date('2020-01-05', 'YYYY-MM-DD')
and remarks = 'LATE'
group by name, to_char(DATE_TIME, 'YYYY-MM-DD')
)
,
PERMS AS (
SELECT DISTINCT DAYS.ADAY, THE_GOODS.NAME
FROM DAYS
CROSS JOIN
THE_GOODS
)
SELECT p.NAME, p.ADAY, COALESCE(g.HOW_MANY, 0) AS HOWMANY
FROM PERMS p
LEFT JOIN THE_GOODS g
on p.ADAY = g.ADAY
and p.NAME = g.NAME
ORDER BY p.ADAY, g.NAME
Try this ..
SQL> select * from late_remarks;
ID NAME DATE_TIME REMARKS
-- -- ------------------- ----
1 Aa 2020-01-18 09:57:56 LATE
2 Aa 2020-01-18 10:57:56 LATE
3 Aa 2020-01-19 06:52:56
4 Aa 2020-01-19 09:57:56 LATE
5 Aa 2020-01-19 09:57:56 LATE
6 Aa 2020-01-21 09:57:56 LATE
6 rows selected.
SQL> with dates as (
2 select to_date('17-01-2020', 'DD-MM-YYYY') + level "DATE"
3 from dual
4 connect by level <= (to_date('21-01-2020', 'DD-MM-YYYY') - to_date('17-01-2020', 'DD-MM-YYYY'))
5 )
6 select 'Aa' name, d."DATE", count(lr.remarks) count from dates d
7 left outer join late_remarks lr
8 on d."DATE" = trunc(to_timestamp (lr.date_time, 'YYYY-MM-DD HH24:MI:SS'))
9 group by d."DATE"
10 order by d."DATE";
NAME DATE COUNT
-- --------- ----------
Aa 18-JAN-20 2
Aa 19-JAN-20 2
Aa 20-JAN-20 0
Aa 21-JAN-20 1
.. assuming name to be constant