I am struggling with this. I have looked at Table Level Variables but I am thinking this is way beyond my simple understanding at this stage of SQL.
The issue I have created is I have an array of ID values I am generating inside MS Access as a result of some other tasks in there. I am wanting to send these over to SQL Server to grab the jobs with the ID number that matches.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[get_Job]
#jobID VARCHAR,
#JobIDs id_List READONLY
AS
BEGIN
SELECT #JobID AS JobID;
SELECT *
FROM Job
END;
Is my current stored procedure, however whilst I have been able to get it to return the JobID variable any list I added generates an error. If I insert only 1 ID into JobIDs, this doesn't generate a result either.
As I said I think I am punching well above my weight and am getting a bit lost in all this. Perhaps I can be directed to a better training resource or a site that explains this in baby steps or a book I can purchase to help me understand this? I would appreciate help with fixing the errors above but a fish teaching is probably better.
Thanks in advance
The issue comes down to much is how long is the list of ID's you going to pass to t-sql is the issue?
You could take the passed list (assume it is a string), say like this from Access at a PT query
exec GetHotels '1,2,3,4,5,6,7,10,20,30'
So, the above is the PT query you can/could send to sql server from Access.
So, in above, we want to return records based on above?
The T-SQL would thus become:
CREATE PROCEDURE GetHotels
#IdList nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MySQL nvarchar(max)
set #MySQL = 'select * from tblHotels where ID in (' + #IdList + ')'
EXECUTE sp_executesql #mysql
END
GO
Now, in Access, say you have that array of "ID" ? You code will look like this:
Sub MyListQuery(MyList() As String)
' above assumes a array of id
' take array - convert to a string list
Dim strMyList As String
strMyList = "'" & Join(MyList, ",") & "'"
Dim rst As DAO.Recordset
With CurrentDb.QueryDefs("qryPassR")
.SQL = "GetHotels " & strMyList
Set rst = .OpenRecordset
End With
rst.MoveLast
Debug.Print rst.RecordCount
End Sub
Unfortunately, creating t-sql on the fly is a "less" then ideal approach. In most cases, because the table is not known at runtime, you have to specific add EXEC permissions to the user.
eg:
GRANT EXECUTE ON dbo.GetHotels TO USERTEST3
You find that such users can execute + run + use "most" store procedures, but in this case, you have to add specific rights with above grant due to the "table" not being known or resolved until runtime.
So, the above is a way to send a "given" array that you have, but from a general permissions point of view, and that of creating t-sql on the fly - I can't recommend this approach unless you are stuck, and have no other choice.
Edit
Here is a solution that works the same as above, but we don't have to create a SQL statement as a string.
CREATE PROCEDURE [dbo].[GetHotels2]
#IdList nvarchar(max)
AS
BEGIN
SET NOCOUNT ON;
-- create a table from the passed list
declare #List table (ID int)
while charindex(',',#IdList) > 0
begin
insert into #List (ID) values(left(#IDList,charindex(',',#IdList)-1))
set #Idlist = right(#IdList,len(#IdList)-charindex(',',#IdList))
end
insert into #List (ID) values(#IdList)
select * from tblHotels where ID in (select ID from #list)
END
You didn't show us what that table-valued parameter looks like - but assuming id_List contains a column called Id, then you need to join this TVP to your base table something like this:
ALTER PROCEDURE [dbo].[get_Job]
#jobID VARCHAR,
#JobIDs id_List READONLY
AS
BEGIN
SELECT (list of columns)
FROM Job j
INNER JOIN id_List l ON j.JobId = l.Id;
END;
Seems pretty easy to me - and not really all that difficult to handle! Agree?
Also, check out Bad habits to kick : declaring VARCHAR without (length) - you should always provide a length for any varchar variables and parameters that you use. Otherwise, as in your case - that #jobID VARCHAR parameter will be exactly ONE character long - and this is typically not what you expect / want ....
This is my stored procedure, it's taking some time to execute even though running with local database.
Please suggest changes in order to improve the performance
BEGIN TRY
DECLARE #COUNTRY_CD INT
SET #COUNTRY_CD =(SELECT COUNTRY_CD FROM COUNTRY WHERE COUNTRY_DESC = LTRIM(RTRIM(#COUNTRY_DESC)))
DECLARE #COMPANNY_CD INT
SET #COMPANNY_CD =(SELECT COMPANY_CD FROM COMPANY WHERE COMPANY_DESC = LTRIM(RTRIM(#COMPANY_DESC)))
BEGIN TRANSACTION
DELETE FROM PACK
WHERE COUNTRY_CD = #COUNTRY_CD
AND COMPANY_CD = #COMPANNY_CD
AND PACK_DESC = LTRIM(RTRIM(#PACK_DESC))
COMMIT TRANSACTION
END TRY
BEGIN CATCH
IF(##TRANCOUNT > 0)
ROLLBACK TRANSACTION
DECLARE #ErrMsg nvarchar(4000),
#ErrSeverity int
SELECT #ErrMsg = ERROR_MESSAGE(),#ErrSeverity = ERROR_SEVERITY()
RAISERROR(#ErrMsg, #ErrSeverity, 1)
END CATCH
Try to evaluate the values of the variables #COUNTRY_CD and
#COMPANNY_CD in a separate proc and pass them as i/p parameter to this
proc and see if it helps. I have seen this issue in the past and the
solution that I just mentioned solved the issue.
Hard to say exactly without knowing more about your database schema. A few initial ideas might be to cleanup the *_DESC variables right away rather than doing the LTRIM and RTRIM in the WHERE clause. Maybe consider or add to an index on the PACK table that includes COUNTRY_CD/COMPANY_CD (NOT description though, assuming it's long string text. I would think COMPANY and COUNTRY are pretty small tables, but hopefully you have the proper indexes on those fields. Might also be worth trying to join to those tables in DELETE rather than doing lookups ahead of time.
-- clenaup variables
-- these should be new vars, not input parms
SELECT #COUNTRY_DESC = LTRIM(RTRIM(#COUNTRY_DESC))
,#COMPANY_DESC = LTRIM(RTRIM(#COMPANY_DESC))
,PACK_DESC = LTRIM(RTRIM(#PACK_DESC ))
-- delete
DELETE PACK
FROM PACK
JOIN COUNTRY ON PACK.COUNTRY_CD = COUNTRY.COUNTRY_CD
JOIN COMPANY ON PACK.COMPANY_CD = COMPANY.COMPANY_CD
WHERE COUNTRY.COUNTRY_DESC = #COUNTRY_DESC
AND COMPANY.COMPANY_DESC = #COMPANY_DESC
AND PACK.PACK_DESC = #PACK_DESC
Try right clicking within your stored procedure and check the Estimated Executon Plan. You can see how "expensive" your SP will be.
If need be you could try
https://stackoverflow.com/a/797968/1504882
Make sure COMPANY is indexed on company_cd, COUNTRY on country_cd and PACK on company_cd,country_cd,pack_desc.
Delete from a large table would take some time without the right index.
I am developing my very first stored procedure in SQL Server 2008 and need advice concerning the errors message.
Procedure or function xxx too many arguments specified
which I get after executing the stored procedure [dbo].[M_UPDATES] that calls another stored procedure called etl_M_Update_Promo.
When calling [dbo].[M_UPDATES] (code see below) via right-mouse-click and ‘Execute stored procedure’ the query that appears in the query-window is:
USE [Database_Test]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[M_UPDATES]
SELECT 'Return Value' = #return_value
GO
The output is
Msg 8144, Level 16, State 2, Procedure etl_M_Update_Promo, Line 0
Procedure or function etl_M_Update_Promo has too many arguments specified.
QUESTION: What does this error message exactly mean, i.e. where are too many arguments? How to identify them?
I found several threads asking about this error message, but the codes provided were all different to mine (if not in another language like C# anyway). So none of the answers solved the problem of my SQL query (i.e. SPs).
Note: below I provide the code used for the two SPs, but I changed the database names, table names and column names. So, please, don’t be concerned about naming conventions, these are only example names!
(1) Code for SP1 [dbo].[M_UPDATES]
USE [Database_Test]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ M_UPDATES] AS
declare #GenID bigint
declare #Description nvarchar(50)
Set #GenID = SCOPE_IDENTITY()
Set #Description = 'M Update'
BEGIN
EXEC etl.etl_M_Update_Promo #GenID, #Description
END
GO
(2) Code for SP2 [etl_M_Update_Promo]
USE [Database_Test]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [etl].[etl_M_Update_Promo]
#GenId bigint = 0
as
declare #start datetime = getdate ()
declare #Process varchar (100) = 'Update_Promo'
declare #SummeryOfTable TABLE (Change varchar (20))
declare #Description nvarchar(50)
declare #ErrorNo int
, #ErrorMsg varchar (max)
declare #Inserts int = 0
, #Updates int = 0
, #Deleted int = 0
, #OwnGenId bit = 0
begin try
if #GenId = 0 begin
INSERT INTO Logging.dbo.ETL_Gen (Starttime)
VALUES (#start)
SET #GenId = SCOPE_IDENTITY()
SET #OwnGenId = 1
end
MERGE [Database_Test].[dbo].[Promo] AS TARGET
USING OPENQUERY( M ,'select * from m.PROMO' ) AS SOURCE
ON (TARGET.[E] = SOURCE.[E])
WHEN MATCHED AND TARGET.[A] <> SOURCE.[A]
OR TARGET.[B] <> SOURCE.[B]
OR TARGET.[C] <> SOURCE.[C]
THEN
UPDATE SET TARGET.[A] = SOURCE.[A]
,TARGET.[B] = SOURCE.[B]
, TARGET.[C] = SOURCE.[c]
WHEN NOT MATCHED BY TARGET THEN
INSERT ([E]
,[A]
,[B]
,[C]
,[D]
,[F]
,[G]
,[H]
,[I]
,[J]
,[K]
,[L]
)
VALUES (SOURCE.[E]
,SOURCE.[A]
,SOURCE.[B]
,SOURCE.[C]
,SOURCE.[D]
,SOURCE.[F]
,SOURCE.[G]
,SOURCE.[H]
,SOURCE.[I]
,SOURCE.[J]
,SOURCE.[K]
,SOURCE.[L]
)
OUTPUT $ACTION INTO #SummeryOfTable;
with cte as (
SELECT
Change,
COUNT(*) AS CountPerChange
FROM #SummeryOfTable
GROUP BY Change
)
SELECT
#Inserts =
CASE Change
WHEN 'INSERT' THEN CountPerChange ELSE #Inserts
END,
#Updates =
CASE Change
WHEN 'UPDATE' THEN CountPerChange ELSE #Updates
END,
#Deleted =
CASE Change
WHEN 'DELETE' THEN CountPerChange ELSE #Deleted
END
FROM cte
INSERT INTO Logging.dbo.ETL_log (GenID, Startdate, Enddate, Process, Message, Inserts, Updates, Deleted,Description)
VALUES (#GenId, #start, GETDATE(), #Process, 'ETL succeded', #Inserts, #Updates, #Deleted,#Description)
if #OwnGenId = 1
UPDATE Logging.dbo.ETL_Gen
SET Endtime = GETDATE()
WHERE ID = #GenId
end try
begin catch
SET #ErrorNo = ERROR_NUMBER()
SET #ErrorMsg = ERROR_MESSAGE()
INSERT INTO Logging.dbo.ETL_Log (GenId, Startdate, Enddate, Process, Message, ErrorNo, Description)
VALUES (#GenId, #start, GETDATE(), #Process, #ErrorMsg, #ErrorNo,#Description)
end catch
GO
You invoke the function with 2 parameters (#GenId and #Description):
EXEC etl.etl_M_Update_Promo #GenID, #Description
However you have declared the function to take 1 argument:
ALTER PROCEDURE [etl].[etl_M_Update_Promo]
#GenId bigint = 0
SQL Server is telling you that [etl_M_Update_Promo] only takes 1 parameter (#GenId)
You can alter the procedure to take two parameters by specifying #Description.
ALTER PROCEDURE [etl].[etl_M_Update_Promo]
#GenId bigint = 0,
#Description NVARCHAR(50)
AS
.... Rest of your code.
Use the following command before defining them:
cmd.Parameters.Clear()
This answer is based on the title and not the specific case in the original post.
I had an insert procedure that kept throwing this annoying error, and even though the error says, "procedure....has too many arguments specified," the fact is that the procedure did NOT have enough arguments.
The table had an incremental id column, and since it is incremental, I did not bother to add it as a variable/argument to the proc, but it turned out that it is needed, so I added it as #Id and viola like they say...it works.
For those who might have the same problem as me, I got this error when the DB I was using was actually master, and not the DB I should have been using.
Just put use [DBName] on the top of your script, or manually change the DB in use in the SQL Server Management Studio GUI.
Yet another cause of this error is when you are calling the stored procedure from code, and the parameter type in code does not match the type on the stored procedure.
I feel ashamed for even having to post this, but it might help someone in the future. Make sure you don't have a typo in your function call!
I kept getting this error trying to call a function and couldn't figure out why. My function and call had the same number of arguments (or so I thought).
Here's my function call:
SELECT FORMAT_NAME(A.LASTNAME, A.FIRSTNAME, A,MIDDLENAME)
It's easier to see in Stack Overflow, but it wasn't so obvious in SSMS that I had a comma in place of a period for A.MIDDLENAME.
SELECT FORMAT_NAME(A.LASTNAME, A.FIRSTNAME, A.MIDDLENAME)
Simple user error.
In addition to all the answers provided so far, another reason for causing this exception can happen when you are saving data from list to database using ADO.Net.
Many developers will mistakenly use for loop or foreach and leave the SqlCommand to execute outside the loop, to avoid that make sure that you have like this code sample for example:
public static void Save(List<myClass> listMyClass)
{
using (var Scope = new System.Transactions.TransactionScope())
{
if (listMyClass.Count > 0)
{
for (int i = 0; i < listMyClass.Count; i++)
{
SqlCommand cmd = new SqlCommand("dbo.SP_SaveChanges", myConnection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Clear();
cmd.Parameters.AddWithValue("#ID", listMyClass[i].ID);
cmd.Parameters.AddWithValue("#FirstName", listMyClass[i].FirstName);
cmd.Parameters.AddWithValue("#LastName", listMyClass[i].LastName);
try
{
myConnection.Open();
cmd.ExecuteNonQuery();
}
catch (SqlException sqe)
{
throw new Exception(sqe.Message);
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
finally
{
myConnection.Close();
}
}
}
else
{
throw new Exception("List is empty");
}
Scope.Complete();
}
}
You either have to double check the Parameters on both side (StoredProcedure And Code):
Make Sure they are the same on both ends regarding to the number of them.
Make Sure you have NOT changed your StoredProcedure code and forgot to Execute it, nothing bad happens if you hit F5 to make sure have all the changes committed and saved.
Make Sure you you have the same naming convention on both sides (Not Likely to be the cause but it worth a shot).
Will creating Variable whenever needed in Stored Procedure or Function or Trigger helps in performance optimization?
Which one is better of below or both have same performance?
Option 1:
CREATE TRIGGER [dbo].[UpdateAmount] ON [RequestDB].[dbo].[Invoice]
AFTER UPDATE
AS
BEGIN
IF UPDATE(Service_Amount)
BEGIN
DECLARE #NewService_Amount float,#OldService_Amount float //Var Created When needed
SELECT #NewService_Amount = I.Service_Amount FROM INSERTED I
SELECT #OldService_Amount = D.Service_Amount FROM DELETED D
IF (#NewService_Amount <> #OldService_Amount)
BEGIN
SELECT #InvId = I.Id FROM INSERTED I
DECLARE #DiffService_Amount float //Var Created When needed
SET #DiffService_Amount = #NewService_Amount - #OldService_Amount
UPDATE [RequestDB].[dbo].[Request] SET Actual_Amount = #DiffService_Amount WHERE Invoice_Id = #InvId
END
END
END
Option 2:
CREATE TRIGGER [dbo].[UpdateAmount] ON [RequestDB].[dbo].[Invoice]
AFTER UPDATE
AS
BEGIN
DECLARE #NewService_Amount float,#OldService_Amount float.#DiffService_Amount float //All Var Created at once on top of code
IF UPDATE(Service_Amount)
BEGIN
SELECT #NewService_Amount = I.Service_Amount FROM INSERTED I /*For New UPDATE Value: INSERTED. For Old BEFORE UPDATE Valie: DELETED*/
SELECT #OldService_Amount = D.Service_Amount FROM DELETED D
IF (#NewService_Amount <> #OldService_Amount)
BEGIN
SELECT #InvId = I.Id FROM INSERTED I
SET #DiffService_Amount = #NewService_Amount - #OldService_Amount
UPDATE [RequestDB].[dbo].[Request] SET Actual_Amount = #DiffService_Amount WHERE Invoice_Id = #InvId
END
END
END
The docs don't get too specific about variables other than to say that once it's declared it's available through that batch process:
The scope of a variable lasts from the point it is declared until the end of the batch or stored procedure in which it is declared.
My assumption would be declaring it later is better (given how they word the docs)--if you don't use it, avoid declaring it. However, the real answer would be to test it and profile it. Whichever works better in practice would be the real solution, IMHO.
I also hope this isn't a premature optimization. If you're down to declaration order to make your scripts run faster, you're probably looking in the wrong spot.
(SQL 2005)
Is it possible for a raiserror to terminate a stored proc.
For example, in a large system we've got a value that wasn't expected being entered into a specific column. In an update trigger if you write:
if exists (select * from inserted where testcol = 7)
begin
raiseerror('My Custom Error', 16, 1)
end
the update information is still applied.
however if you run
if exists (select * from inserted where testcol = 7)
begin
select 1/0
end
a divide by 0 error is thrown that actually terminates the update.
is there any way i can do this with a raiseerror so i can get custom error messages back?
In a trigger, issue a ROLLBACK, RAISERROR and then RETURN.
see Error Handling in SQL Server - Trigger Context by Erland Sommarskog
Can you not just add a CHECK constraint to the column to prevent it from being inserted in the first place?
ALTER TABLE YourTable ADD CONSTRAINT CK_No_Nasties
CHECK (testcol <> 7)
Alternatively you could start a transaction in your insert sproc (if you have one) and roll it back if an error occurs. This can be implemented with TRY, CATCH in SQL Server 2005 and avoids having to use a trigger.
Begin try
#temp number
#temp=1/0
End try
Begin catch
#errormsg varchar(100)
#errormsg=error_massage()
Raiseerror(#errormsg,16,1)
End catch
You should check for valid data prior to performing the update.
IF (#testvalue = 7)
RAISERROR("Invalid value.", 16, 1);
ELSE
UPDATE...