Related
I have two tables called Employees and Salarygroup. PersonalID is the primary key of Employees and SalaryID is the primary key of Salarygroup. Inside the Employees table there is one more row called StartDat, which has date data type and tracks down the date when the employee started working at the company. Moreover, AmountInEuros is the salary that an employee gets every month and it has a numeric data type
I need to make a function, which count the total amount of money, that the employee has recieved so far from the company, but when I input the PersonalID I get an error saying *Correlation name 'Salarygroup' not found.
Could someone helo me understand why this is happening?
ALTER FUNCTION "dba"."countTotalAmountOfMoney"(#PersonalID int)
RETURNS int
AS
BEGIN
DECLARE #totalAmountOfMoney int;
SELECT #totalAmountOfMoney = g.AmountInEuros * DATEDIFF(month, g.StartDat,
'2019-01-16')
FROM dba.Employees
Inner Join dba.Salarygroup s
ON dba.Employees.SalaryId = dba.Salarygroup.SalaryId
RETURN #totalAmountOfMoney;
END
You have given the table an alias, so you need to use it. I would recommend using aliases for all the tables:
DECLARE #totalAmountOfMoney int;
SELECT #totalAmountOfMoney = s.AmountInEuros * DATEDIFF(month, e.StartDat, '2019-01-16')
FROM dba.Employees e INNER JOIN
dba.Salarygroup s
ON e.SalaryId = s.SalaryId
WHERE e.personalID = #PersonalID;
Note that the g alias is not defined in your query. StartDat comes from Employees, so I changed that to e. I am guessing that AmountInEuros comes from s.
Maybe you forgotten to refer the second table using the Alias 's' you created:
ALTER FUNCTION "dba"."countTotalAmountOfMoney"(#PersonalID int)
RETURNS int
AS
BEGIN
DECLARE #totalAmountOfMoney int;
SELECT #totalAmountOfMoney = g.AmountInEuros * DATEDIFF(month, g.StartDat,
'2019-01-16')
FROM dba.Employees
Inner Join dba.Salarygroup s
ON dba.Employees.SalaryId = s.SalaryId
RETURN #totalAmountOfMoney;
END
I have a Stored Procedure (SP), named myStoredProcedure, returning me such output based on startDate and endDate user-defined parameters:
PrimaryName SecondaryName Volume
A B 20
C D 30
A D 50
...
So, Volume represents the sum of all the cases between the dates defined.
In another SP, named mySecondStoredProcedure, I am using the first SP to get the result there. However, my problem is that I need an additional attribute in my output, which is year, I want to see year based volumes. Therefore, the output I would like to see is something like that
assume startDate: 2014, endDate: 2015:
PrimaryName SecondaryName Volume Year
A B 12 2014
C D 14 2014
A D 20 2014
A B 8 2015
C D 16 2015
A D 30 2015
...
I am not allowed to modify myStoredProcedure. Therefore I build a while loop in the second SP to receive it. My code is like:
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int
)
while #startDate < #endDate
begin
insert into #temp_table
exec myStoredProcedure #startDate #endDate
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp_table
This is giving me the result without the year column. I need a year column like I showed in my example output above. I could not find a way to add it. There is no primary key in the result set returned by myStoredProcedure. Also, SQL Server 2008 does not let me add a year column in #temp_table, saying that fields are not matching. How can I add the year column properly? Any help would be appreciated!
EDIT: When I add year column in the definition of #temp_table, the error I receive: Column name or number of supplied values does not match table definition.
You're close with the syntax you currently have, you'll just need to add the year to the temp table and supply it after calling the stored procedure. In addition, you will also need to specify the columns being inserted (a practice well worth getting in the habit of) as your procedure doesn't return the same number of columns.
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int,
Year int
)
while #startDate < #endDate
begin
insert into #temp_table (PrimaryGroup, SecondaryGroup, Volume)
exec myStoredProcedure #startDate #endDate
Update #temp_table
Set Year = #StartDate
Where Year Is Null
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp_table
Add a Year column to your temp table, and apply the structured insert
declare #temp_table table
(
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int,
Year int
)
while #startDate < #endDate
begin
insert into #temp_table (PrimaryName,SecondaryName,Volume)
exec myStoredProcedure #startDate #endDate
Update #temp_table set Year = #startDate where Year is Null
set #startDate = DATEADD(YEAR,1,#startDate)
end
select * from #temp
Create a second table variable that will hold the result:
declare #result_table table
(
Year int,
PrimaryGroup varchar(10),
SecondaryGroup varchar(10),
Volume int
)
Then in the while loop after fetching the result into #temp_table:
insert into #result_table
select <year>, PrimaryGroup, SecondaryGroup, Volume from #temp_table;
truncate #temp_table;
I have a table as below:
The first record Amount and TotalAmount are same
In the second record Amount is added from first row and current and TotalAmount is added
And so on....
Now if I update the second row from 1.25 to 2, then the TotalAmount for all subsequent records should be changed.
I need an update query for this.
I have seq_no and row no as reference and field Type is the reference
Ideally you should create a view or stored procedure that performs a running total, this is an example of one method using a subquery:
SELECT
Type,
Amount ,
Total =
(
SELECT SUM(Amount)
FROM SomeTable B
WHERE B.Type=A.Type AND B.RowNum <= A.RowNum
)
FROM SomeTable A
This is just one method (not necessarily the best). I would suggest you google 'Running totals in SQL' and familiarise yourself with the explanation of this and other methods their pros, cons and performance implications of each.
One question, what version of SQL server are you using?
If you can use row number as reference than you can try following query but storing totals in table is bad idea as mentioned in comment:
DECLARE #Temp TABLE
(
Amount float,
TotalAmount float,
Rownum int
)
INSERT INTO #Temp VALUES (1.25,1.25,1),(1.25,2.50,2),(10,12.50,3)
DECLARE #PreviousAmount AS FLOAT
SELECT #PreviousAmount = Amount FROM #Temp WHERE Rownum=1
DECLARE #NewAmount AS FLOAT = 2
UPDATE #Temp SET TotalAmount = TotalAmount - #PreviousAmount WHERE Rownum>=1
UPDATE #Temp SET Amount=#NewAmount, TotalAmount = TotalAmount + #NewAmount WHERE Rownum=1
UPDATE #Temp SET TotalAmount = TotalAmount + #NewAmount WHERE Rownum>1
SELECT * FROM #Temp
If you want to use triggers(which is not recommended).you can use this:
create trigger trigger_name
for update
as
declare #count int= (select count(*) from table)
declare #a int =1
while(#a<#count)
begin
update table
set total_amount=(select amount from table where row_number=#a) + (select amount from table where row_number=#a-1 )
where row_number!=1
set #a=#a+1
end
Go
Write an UDF accepting order date and ship date. Make the UDF return the number of sales orders (using Vista credit cards) that took place between the order date and ship date entered by a user.
(The table is Sales-SalesOrderHeader in AdventureWorks2012 in SQL Server 2012).
It looks like I have to reference Sales-CreditCard table to get the exact card name, because that is not provided in Sales.SalesOrderHeader table:
CREATE FUNCTION fx_SalesNmbr INT
(SELECT OrderDate, ShipDate
FROM Sales.SalesOrder
WHERE CreditCardID = Vista (Maybe is should be a function # Creditcardname)
Can someone, please, show me how it is done?
CREATE FUNCTION dbo.fx_SalesNmbr
(
#pOrderDate DATETIME,
#pShipDate DATETIME,
#pCardType NVARCHAR(50)
)
RETURNS INT
AS
BEGIN
DECLARE #result INT
SELECT
#result = COUNT(*)
FROM
Sales.SalesOrderHeader AS soh
JOIN Sales.CreditCard AS cc
ON soh.CreditCardID = cc.CreditCardID
WHERE
cc.CardType = #pCardType
AND soh.OrderDate = #pOrderDate
AND soh.ShipDate = #pShipDate
RETURN #result
END
GO
SELECT dbo.fx_SalesNmbr ('2001-07-01', '2001-07-08', 'Vista')
I have wrote this cursor for commission report. What happens is commission comes in one table, the records are another table. I match two based on certain critera (there is not exact match available). The problem is there are duplicates where records exist. When I match commission with the records table, it can result picking up these duplicates. Thus the rep gets paid more. On the other hand, there are duplicates in commission table also but those are valid beause they simple mean an account got paid for 2 months.
I wrote this query but it takes 5+ minutes to run. I have 50,000 records in records table and 100,000 in commission table. Is there any way I an improve this cursor?
/* just preparation of cursor, this is not time consuming */
CREATE TABLE #result
(
repid INT,
AccountNo VARCHAR(100),
supplier VARCHAR(15),
CompanyName VARCHAR(200),
StartDate DATETIME,
EndDate DATETIME,
Product VARCHAR(25),
commodity VARCHAR(25),
ContractEnd DATETIME,
EstUsage INT,
EnrollStatus VARCHAR(10),
EnrollDate DATETIME,
ActualEndDate DATETIME,
MeterStart DATETIME,
MeterEnd DATETIME,
ActualUsage INT
)
DECLARE #AccountNo VARCHAR(100)
DECLARE #supplier VARCHAR(10)
DECLARE #commodity VARCHAR(15)
DECLARE #meterstart DATETIME
DECLARE #meterEnd DATETIME
DECLARE #volume FLOAT
DECLARE #RepID INT
DECLARE #Month INT
DECLARE #Year INT
SET #repID = 80
SET #Month = 1
SET #year = 2012
/* the actual cursor */
DECLARE commission_cursor CURSOR FOR
SELECT AccountNo,
supplier,
commodity,
meterStart,
MeterEnd,
Volume
FROM commission
WHERE Datepart(m, PaymentDate) = #Month
AND Datepart(YYYY, PaymentDate) = #Year
OPEN commission_cursor
FETCH next FROM commission_cursor INTO #AccountNo, #supplier, #commodity, #MeterStart, #MeterEnd, #Volume;
WHILE ##fetch_status = 0
BEGIN
IF EXISTS (SELECT id
FROM Records
WHERE AccountNo = #AccountNo
AND supplier = #supplier
AND Commodity = #commodity
AND RepID = #repID)
INSERT INTO #result
SELECT TOP 1 RepID,
AccountNo,
Supplier,
CompanyName,
[Supplier Start Date],
[Supplier End Date],
Product,
Commodity,
[customer end date],
[Expected Usage],
EnrollStatus,
ActualStartDate,
ActualEndDate,
#meterstart,
#MeterEnd,
#volume
FROM Records
WHERE AccountNo = #AccountNo
AND supplier = #supplier
AND Commodity = #commodity
AND RepID = #repID
AND #MeterStart >= Dateadd(dd, -7, ActualStartDate)
AND #meterEnd <= Isnull(Dateadd(dd, 30, ActualEndDate), '2015-12-31')
FETCH next FROM commission_cursor INTO #AccountNo, #supplier, #commodity, #MeterStart, #MeterEnd, #Volume;
END
SELECT *
FROM #result
/* clean up */
CLOSE commission_cursor
DEALLOCATE commission_cursor
DROP TABLE #result
I have read answer to How to make a T-SQL Cursor faster?, for that what I get is rewrite this query in table form. But I do have another query which uses join and is lightening fast. The problem is, it can not differentiate between the dups in my records table.
Is there anything I can do to make is faster. This is primary question. If not, do you have any alternative way to do it.
I specifically need help with
Will using Views or store procedure help
I there a way I can use cache in Cursor to make it faster
Any other option in syntax
The very first option is to set the least resource intensive options for your cursor:
declare commission_cursor cursor
local static read_only forward_only
for
Next is to investigate whether you need a cursor at all. In this case I think you can do the same with a single pass and no loops:
;WITH x AS
(
SELECT
rn = ROW_NUMBER() OVER (PARTITION BY r.AccountNo, r.Supplier, r.Commodity, r.RepID
ORDER BY r.ActualEndDate DESC),
r.RepID,
r.AccountNo,
r.Supplier,
r.CompanyName,
StartDate = r.[Supplier Start Date],
EndDate = r.[Supplier End Date],
r.Product,
r.Commodity,
ContractEnd = r.[customer end date],
EstUsage = r.[Expected Usage],
r.EnrollStatus,
EnrollDate = r.ActualStartDate,
r.ActualEndDate,
c.MeterStart,
c.MeterEnd,
ActualUsage = c.Volume
FROM dbo.commission AS c
INNER JOIN dbo.Records AS r
ON c.AccountNo = r.AccountNo
AND c.Supplier = r.Supplier
AND c.Commodity = r.Commodity
AND c.RepID = r.RepID
WHERE
c.PaymentDate >= DATEADD(MONTH, #Month-1, CONVERT(CHAR(4), #Year) + '0101')
AND c.PaymentDate < DATEADD(MONTH, 1, CONVERT(CHAR(4), #Year) + '0101')
AND r.RepID = #RepID
)
SELECT RepID, AccountNo, Supplier, CompanyName, StartDate, EndDate,
Product, Commodity, ContractEnd, EstUsage, EnrollStatus, EnrollDate,
ActualEndDate, MeterStart, MeterEnd, ActualUsage
FROM x
WHERE rn = 1 --ORDER BY something;
If this is still slow, then the cursor probably wasn't the problem - the next step will be investigating what indexes might be implemented to make this query more efficient.
Temp tables are your friend
The way I solved my problem, merging data from two tables, removed duplicates in complex fashion and everything extremely fast was to use temporary table. This is what I did
Create a #temp table, fetch the merged data from both the tables. Make sure you include ID fields in both tables even if you do not required it. This will help remove duplicates.
Now you can do all sort of calculation on this table. Remove duplicates from table B, just remove duplicate table B IDs. Remove duplicates from table A, just remove duplicate table A Ids. There is more complexity to the problem but at least this is probably the best way to solve your problem and make it considerably faster if cursors are too expensive and takes considerable time to calculate. In my case it was taking +5 min. The #temp table query about about 5 sec, which had a lot more calculations in it.
While applying Aaron solution, the cursor did not get any faster. The second query was faster but it did not give me the correct answer, so finally I used temp tables. This is my own answer.