Stored Procedure to create cumulative sums - sql

I'm trying to write Firebird stored procedure, which returns me cumulative sums for twelve months grouped by another two fields.
For now I created something like this:
SET TERM ^ ;
CREATE PROCEDURE A_OBRATZISKKUM(
START_YEAR INTEGER,
END_YEAR INTEGER)
RETURNS(
OBRAT DOUBLE PRECISION,
MESIC INTEGER,
ROK INTEGER,
SECURITYUSER_ID TYPE OF ID COLLATE WIN1250,
FIRM_ID TYPE OF ID COLLATE WIN1250)
AS
DECLARE VARIABLE act_month INTEGER = 1;
begin
for select
sum(AO.Obrat), :act_MONTH, :start_YEAR, AO.SecurityUser_ID,
AO.Firm_ID
from A_OBRATYKUMHIST_TEMP AO
where
AO.Rok = :start_year
and AO.Mesic <= :act_month
group by
AO.SecurityUser_ID, AO.Firm_ID
into :obrat, :mesic, :rok, :securityuser_id, :firm_id
do
act_month = :act_MONTH + 1;
suspend;
end^
SET TERM ; ^
But when I run it I get only one row with weird sum. What's wrong on my code?
EDIT1.
I come out from this sql query:
select
sum(AO.Obrat), *1*, 2012, AO.SecurityUser_ID,
AO.Firm_ID
from A_OBRATYKUMHIST_TEMP AO
where
AO.Rok = 2012
and AO.Mesic <= *1*
group by
AO.SecurityUser_ID, AO.Firm_ID
When I take for example month number 3, I get sum of all previous months grouped by securityuser_id and firm_id.
But if I want to get sums for all months, then I have to run this twelve times with replacing 1 for 2, then for 3, then ........
At the end I get cumulative sums splits up to months, years, users and firms.

A DO is followed by either a single statement, or by a block. You are currently doing a single SUSPEND instead of multiple suspends as your intention is. Instead of
FOR SELECT ...
DO
statement;
SUSPEND;
You need to do
FOR SELECT ...
DO
BEGIN
statement;
SUSPEND;
END
Also the statement act_month = :act_MONTH + 1; should be act_month = act_MONTH + 1; (no colon :)
The running sum
The running sum can be achieved by self-joining for all months <= the current month and summing over that:
SELECT SUM(AO2.Obrat), AO.Mesic, AO.Rok, AO.SecurityUser_ID, AO.Firm_ID
FROM A_OBRATYKUMHIST_TEMP AO
INNER JOIN A_OBRATYKUMHIST_TEMP AO2
ON AO2.Rok = AO.Rok AND AO2.Mesic <= AO.Mesic
WHERE AO.Rok = 2012
GROUP BY AO.Mesic, AO.Rok, AO.SecurityUser_ID, AO.Firm_ID
Note this only works within the same year, you'd need to modify the join condition if you need to sum over multiple years.

Related

Substract time-values from same column in SSRS

I need to do substract time-values from the same column. I already orderd the values that need to be substracted from each other. I will add a picture of my table, so you have a general idea. Now I need to substract number 1 from number 2, number 2 from number 3,.... (from same vehicle) And put it in a column named Consumption for example.
As an If function gave me an error i tried to go to a case function. But now I get the error : "A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations. Problem code is the CASE-function till #lastvalue = AGVLoggingrunstate.starttime. I apparently can't put this code inside data-retrieval code. So how do I have to do it ? Do I need to make a stored procedure ? Tips are welcome.
Declare #lastSN AS nvarchar, #lastValue int;
SET #lastSN = 'AGV';
SET #lastValue = 0;
SELECT ROW_NUMBER() OVER (ORDER BY AgvLoggingRunstate.vehicle,AgvLoggingRunstate.starttime) AS Row,
AgvLoggingRunstate.id,
AgvLoggingRunstate.vehicle,
AgvLoggingRunstate.node,
AgvLoggingRunstate.runstate,
AgvLoggingRunstate.orderclass,
AgvLoggingRunstate.loaded,
AgvLoggingRunstate.starttime,
AgvLoggingRunstate.endtime,
DATEDIFF(second,AgvLoggingRunstate.starttime,AgvLoggingRunstate.endtime) AS DiffDate,
Case
WHEN #lastSN = AgvLoggingRunstate.vehicle Then AgvLoggingRunstate.starttime - #lastValue
ELSE 0
END AS Consumption,
#lastSN = AgvLoggingRunstate.vehicle,
#lastValue = AgvLoggingRunstate.starttime
FROM AgvLoggingRunstate
Where AgvLoggingRunstate.starttime > #Startdate and AgvLoggingRunstate.starttime < #Enddate and AgvLoggingRunstate.runstate = 'Battery'
Order by
AgvLoggingRunstate.vehicle,
AgvLoggingRunstate.starttime
Result after Answer and adding an order by function :
Now the only incorrect answer is between two days. I need to filter this out. (Still reading all the info over COALESCE,LEFT JOIN,.. Because answer isn't completely clear yet to me)
WITH Times (RowNumber, vehicle, starttime,endtime)
AS
(SELECT ROW_NUMBER() OVER (ORDER BY vehicle, starttime),vehicle, starttime,endtime FROM AgvLoggingRunState WHERE starttime > #Startdate and starttime < #Enddate and AgvLoggingRunstate.runstate = 'Battery')
SELECT CurrentTime.Vehicle,
CurrentTime.StartTime,
CurrentTime.EndTime,
NextTime.StartTime AS NextTime,
COALESCE(DATEDIFF(SECOND,CurrentTime.StartTime,NextTime.StartTime),0)
AS Seconds,
COALESCE(DATEDIFF(SECOND,CurrentTime.StartTime,CurrentTime.EndTime),0)
AS SecondsDiffEnd
FROM Times CurrentTime
LEFT OUTER JOIN Times NextTime
ON CurrentTime.RowNumber +1 = NextTime.RowNumber
AND CurrentTime.Vehicle = NextTime.Vehicle
Order by StartTime
You started correctly with the row number function. However, you're trying to do with a variable what you need to do with a join. In the example below, you'll see how I can create the CTE with your row number and then use that in the query, joining to the next record (as long as the vehicles match).
You'll need to determine the correct answer for the last record per vehicle. Right now, it's zero (see the Coalesce Function)
This would be really easy in a future version with LEAD. Oh well.
WITH Times (RowNumber, Vehicle, StartTime)
AS
(SELECT ROW_NUMBER() OVER(ORDER BY Vehicle, StartTime),
Vehicle, StartTime, NextTime FROM dbo.AvgLoggingRunState)
SELECT CurrentTime.Vehicle,
CurrentTime.StartTime,
NextTime.StartTime AS NextTime,
COALESCE(DATEDIFF(SECOND,CurrentTime.StartTime,NextTime.StartTime),0)
AS SecondsDiffNext,
COALESCE(DATEDIFF(SECOND,CurrentTime.StartTime,CurrentTime.EndTime),0)
AS SecondsDiffEnd
FROM Times CurrentTime
-- join to the next time - note that I join both on row number and
-- on vehicles as I don't want to mix vehicles
LEFT OUTER JOIN Times NextTime
ON CurrentTime.RowNumber +1 = NextTime.RowNumber
AND CurrentTime.Vehicle = NextTime.Vehicle

can we use two sum fuction in same query in Postgres

I written a query as mentioned below :
select
sum(co.BOMParentQty) as hiredqty,
cy.dehireqty as dehire,
cy.M_product_id,
co.Project_Prod
from c_order co
inner join M_CycleDays cy ON (co.Project_Prod :: numeric = cy.M_product_id)
where co.c_doctype_id = 1000600
AND co.Project_Prod = '1000470'
group by cy.M_product_id, co.Project_Prod, dehire
it gives me output for two lines but i want only single line with the sum of dehireqty if i put sum function for dehire qty column then it sums two times
Actual Output without Sum of dehireqty column is (two records):
6.00;2.00;1000470;"1000470"
6.00;3.00;1000470;"1000470"
After put sum function for dehireqty column:
12.00;15.00;1000470;"1000470"
But actually I want:
6.00;5.00;1000470;"1000470"
How can I get this result?
Just make your query a sub-query and sum-up dehireqty in outer-query:
SELECT
hiredqty,
SUM(dehire) AS dehireqty,
M_product_id,
Project_Prod
FROM (
select
sum(co.BOMParentQty) as hiredqty,
cy.dehireqty as dehire,
cy.M_product_id,
co.Project_Prod
from c_order co
inner join M_CycleDays cy ON (co.Project_Prod :: numeric = cy.M_product_id)
where co.c_doctype_id = 1000600
AND co.Project_Prod = '1000470'
group by cy.M_product_id, co.Project_Prod, dehire
) AS subquery Group by hiredqty,M_product_id,Project_Prod;

SQL not properly ended?

I know i don't have to use SQL but i have other code between declare begin and end . I just extracted this part and everything works except this . I get the following error:
Error report:
ORA-06550: line 5, column 5:
PL/SQL: ORA-00933: SQL command not properly ended
ORA-06550: line 3, column 5:
PL/SQL: SQL Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
This is the code:
DECLARE
BEGIN
UPDATE octombrie
SET nr_imprumut = I.nr
from (select book_date bd,count(book_date) nr
from rental
where to_char(book_date,'mm') like '10'
group by book_date) I
where data = I.bd;
END;
/
I don't get it what did i do wrong ?
EDIT: book_date will give me a day from the month of october. In that day multiple books are rented, so i find out how many books i rented by counting the number of times the same date apears (the rented books are in the rental table). I then take this data and Update October table(i put the number of books aka 'nr' where the date in the october table matches the date in which the books where rented);
DECLARE
BEGIN
UPDATE octombrie o
SET o.nr_imprumut =
(select count(r.book_date)
from rental r
where to_char(r.book_date,'mm') like '10' and o.data = r.book_date)
WHERE exists (select 1 from rental r
where to_char(r.book_date,'mm') like '10' and o.data = r.book_date);
END;
Use
where exists (select 1 from rental r
where to_char(r.book_date,'mm') like '10' and o.data = r.book_date)
if you want to update only those rows for which you found something in rental (otherwise ALL rows will be updated in octombrie)
Another variant (updates all October 2014 data in octombrie; for each data calculates the number of rental ON this data)
UPDATE octombrie o
SET o.nr_imprumut =
(select count(r.book_date)
from rental r
where r.book_date between trunc(o.data) and trunc(o.data) + 1 - 1/24/60/60)
WHERE o.data between to_date('2014-10-01','yyyy-mm-dd') and to_date('2014-10-31 23:59:59','yyyy-mm-dd hh24:mi:ss');
I guess you really wanted to something like that:
DECLARE
BEGIN
UPDATE octombrie o
SET o.nr_imprumut =
(SELECT count(1)
FROM rental r
WHERE r.book_date between o.data and o.data+1-1/86400)
WHERE data between to_date('2014-10-01','yyyy-mm-dd') and to_date('2014-10-31','yyyy-mm-dd');
END;
Note that the WHERE-clause of the UPDATE-statement specifies a year, I don't think you want to updated your records for all days in any october. Limiting the date like that (with a BETWEEN operator, but without a TO_CHAR) makes it possible to use an index on octombrie.data, something I hope you have.

Oracle: Updating table with inner join and union

We are migrating our PRODUCTION database from MSSQL to Oracle 11G. Part of this process is to recreate some existing stored procedures from TSQL to Oracle SQL. Being new to Oracle, I have a question on best practice in a specific situation.
Essentially, I have a table that summarizes data from a sales transaction table. The basic layout of the table (F5542HIS) is as follows (* indicates index):
*NSAN8 (sold_to)
*NSSHAN (ship_to)
*NSMCU (sold_from)
*NSITM (item)
*NSCTRY (Century)
*NSYR (year)
*NSMNTH (month)
*NSDCTO (Order Type)
*NSLNTY (Order Line Sales Type)
NSAEXP (Total Extended Price)
NSSOQS (Total Units Shipped)
NSECST (Total Extended Cost)
On my first pass of the script, I populate the table with the primary index values (indicated by the * above).
On the second pass, I need to do an update to this table to populate the summation values from my transaction tables. The transaction tables are the F4211 (open orders) and F42119 (order history), so a union is required.
So, I need to do an update based on the following select statement which returns the proper data:
select sum(cast(sdaexp as numeric)) sumaexp,
sum(cast(sduorg as numeric)) sumuorg,
sum(cast(sdecst as numeric)) sumecst
from PRODDTA.F4211 a inner join proddta.f5542his b
on(a.sdan8 = b.nsan8 and
a.sdshan = b.nsshan and
a.sditm = b.nsitm and
a.sdlnty = b.nslnty and
a.sddcto = b.nsdcto and
a.sdlnty = b.nslnty and
ltrim(rtrim(a.sdmcu)) = ltrim(rtrim(b.nsmcu)) )
where sdtrdj >= 108001 and
sdtrdj <= 108031 and
group by sdaexp, sduorg, sdecst
UNION
select sum(cast(sdaexp as numeric)) sumaexp,
sum(cast(sduorg as numeric)) sumuorg,
sum(cast(sdecst as numeric)) sumecst
from PRODDTA.F4211 a inner join proddta.f5542his b
on(a.sdan8 = b.nsan8 and
a.sdshan = b.nsshan and
a.sditm = b.nsitm and
a.sddcto = b.nsdcto and
a.sdlnty = b.nslnty and
ltrim(rtrim(a.sdmcu)) = ltrim(rtrim(b.nsmcu)) )
where sdivd >= 108001 and
sdivd <= 108031 and
group by sdaexp, sduorg, sdecst
Notice that the SDTRDJ (transaction date) and also the SDIVD (Invoice date) are using the values >= 108001 and <= 108031. These are JULIAN DAYS (not julian dates). It is a strange method of retaining dates, but it works. 108001 = Jan 1, 2008, and 108031 = Jan 31, 2008.
Also, the F5542HIS table will have a WHERE clause on the update for this example where the NSCTRY (Century) = 20, NSYR (Year) = 8, and NSMNTH (Month) = 1. So essentially, the structure could be inthe form:
UPDATE f5542HIS
SET nsaexp=sumaexp, nsuorg-sumuorg, nsecst=sumecst from<union>
where nsctry=20 and nsyr=8 and nsmnth=1
I have been looking at the SQL Update and also the SQL MERGE functions. Can someone help me define the best practice for formatting this statement into either an UPDATE or MERGE that will work here?

SQL function returning a error regarding a subquery

Good morning all,
I am trying to make a revenue view, but I am stuck due to a error I cannot fix.
I've made a function which calcutates total revenue from both Accessories and Bikes. The function combines these to give a result per month, which is the variable. I tried the seperate queries and they seem to work properly. The following occurs, I seem to be missing a step I think:
CREATE FUNCTION fnOmzetPMaand
(
#Maand AS int
)
RETURNS int
AS
BEGIN
DECLARE #AccOmzet AS int
SET #AccOmzet = (
SELECT dbo.fnOmzet(SUM(a.Accessoire_Dagprijs), h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum)
FROM Huurovereenkomst h
INNER JOIN HuurovereenkomstAccessoire ha
ON ha.HuurovereenkomstAccessoire_Huurovereenkomst_id = h.Huurovereenkomst_id
INNER JOIN Accessoire a
ON a.Accessoire_id = ha.HuurovereenkomstAccessoire_Accessoire_id
WHERE MONTH(h.Huurovereenkomst_Betaal_datum) = #Maand
GROUP BY h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum
)
DECLARE #FietsOmzet AS int
SET #FietsOmzet = (
SELECT dbo.fnOmzet(SUM(f.Fiets_Dagprijs), h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum)
FROM Huurovereenkomst h
INNER JOIN HuurovereenkomstFiets hf
ON hf.HuurovereenkomstFiets_Huurovereenkomst_id = h.Huurovereenkomst_id
INNER JOIN Fiets f
ON f.Fiets_id = hf.HuurovereenkomstFiets_Fiets_id
WHERE MONTH(h.Huurovereenkomst_Betaal_datum) = #Maand
GROUP BY h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum
)
RETURN #FietsOmzet + #AccOmzet
END
The view:
CREATE VIEW vOmzetPMaand
AS
SELECT DATENAME(MONTH, Huurovereenkomst_Betaal_datum) Maand,
dbo.fnOmzetPMaand(MONTH(Huurovereenkomst_Betaal_datum)) Omzet
FROM Huurovereenkomst
The error:
Msg 512, Level 16, State 1, Line 43
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as an
expression.
The expected results:
May 100
June 200
July 150
etc etc.
USE [Biker]
GO
/****** Object: UserDefinedFunction [dbo].[fnOmzet] Script Date: 05-Jan-18 12:05:50 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[fnOmzet]
(
#Bedrag AS int,
#BeginDatum AS DATETIME,
#EindDatum AS DATETIME
)
RETURNS int
AS
BEGIN
RETURN #Bedrag * DATEDIFF(Y, #BeginDatum, #EindDatum)
END
Yo have two subqueries but both are using the same table and grouped by the same criteria.
Without looking at your dataset, I would said that since the expected result is aggregated by month (based on your question): The impacting data is very likely that you have more than one combination of "h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum" per month. If that is the case, and since those columns values are grouped but not aggregated they would be returning multiple rows.
The following are some of the options that I can think of so far:
Option 1. Use aggregated functions on both of those fields like min and max (if the business logic allows for it in order to obtain a single record.
Option 2. Handling the multiple rows of each result and guarantee a single resultant value. (this can be accomplish with multiple solutions that could be from extra sub query withing your select statement, to even return the values on a table alike structure like table variable and then do an extra processing to return only the final value over the multiple rows)
Option 3. Extract somehow the relevant month from the combination "h.Huurovereenkomst_Begin_datum, h.Huurovereenkomst_Eind_datum" and group by the month instead of the you current grouping.
Important: I would prefer the option #3 or some variation of it if the business rules allows it.