This question already exists:
Update using SQL cursor SQL server [closed]
Closed 2 years ago.
I will explain my question, I use the AdventureWorksDW2012 database to demonstrate my purpose. I want to update the table based on the max date in this table where CurrencyID = 'CNY'
but I get the error Conversion failed when converting date and/or time from character string. I know the error because I use dynamic SQL statement but I should use it with cursor ..any help regarding this issue
here is my code
SET NOCOUNT ON
DECLARE
#AverageRate float
,#CurrencyID NVARCHAR(50)
,#CurrencyDate DATE
,#EndOfDayRate float
,#CurrencyKey float
,#DateKey float
,#MaxDate nvarchar(max)
Declare update_cursor
CURSOR
FOR
(select DISTINCT AverageRate
,CurrencyID
,CurrencyDate
,EndOfDayRate
,CurrencyKey
,DateKey from dbo.NewFactCurrencyRate1 );
OPEN update_cursor
FETCH NEXT FROM update_cursor INTO
#AverageRate
,#CurrencyID
,#CurrencyDate
,#EndOfDayRate
,#CurrencyKey
,#DateKey
WHILE ( ##FETCH_STATUS = 0)
BEGIN
if #CurrencyID = 'CNY'
begin
set #MaxDate = 'select max(CurrencyDate) from dbo.NewFactCurrencyRate1 '
EXEC (#MaxDate)
end
update dbo.NewFactCurrencyRate1
set CurrencyDate = #MaxDate
where CurrencyID = 'CNY'
FETCH NEXT FROM update_cursor INTO
#AverageRate
,#CurrencyID
,#CurrencyDate
,#EndOfDayRate
,#CurrencyKey
,#DateKey
END
CLOSE update_cursor
DEALLOCATE update_cursor
So far what I could understand from mobile view is you declared #maxdate as nvarchar but trying to store and update date field with it. Please use date data type.
#MaxDate date
You are attempting to "double use" the #MaxDate string. In one place you use it to hold the SQL you are trying to run, and in another place you are trying (but failing to) make it hold what should be the output of executing that SQL.
I would declare these as two variables:
#DynamicSQL nvarchar(max)
#MaxDate date
Then replace:
set #MaxDate = 'select max(CurrencyDate) from dbo.NewFactCurrencyRate1 '
EXEC (#MaxDate)
With
set #DynamicSQL = 'SET #MaxDate = (select max(CurrencyDate)
from dbo.NewFactCurrencyRate1)'
EXEC (#DynamicSQL)
Related
I have a table dbo.t_products and I want to know the last record updated. For that I have an attribute last_changed which stores for each record the timestamp of the last update.
Finally I want to save that result in a variable called #y.
DECLARE #y DATETIME
DECLARE #p_table VARCHAR(100)
SET #p_table = 'dbo.t_products'
EXECUTE sp_executesql N'SET #y = SELECT MAX(last_changed) FROM #p_table'
,N'#p_table VARCHAR, #y DATETIME OUTPUT'
,#p_table
,#y OUTPUT
SELECT #y
The system returns the following message:
Msg 156, Level 15, State 1, Line 25
Incorrect syntax near the keyword 'SELECT'.
Any ideas?
The whole point of using dynamic SQL in your case (I assume) is to allow the use of a dynamic table name. In which case you have to insert the table name into the dynamic SQL string - you can't pass it in as a parameter, thats the problem you are trying in the first place.
Also you don't need a SET followed by a SELECT just use SELECT directly to set the variable.
Finally you definitely want to use the QUOTENAME function to escape your table name and avoid an SQL injection attack - this requires you split the table name and schema name.
DECLARE #y DATETIME;
DECLARE #p_schema VARCHAR(100);
DECLARE #p_table VARCHAR(100);
DECLARE #SQL NVARCHAR(max);
SET #p_schema = 'dbo';
SET #p_table = 't_products';
-- Add the table name to the dynamic SQL
SET #SQL = 'SELECT #y = MAX(last_changed) FROM ' + QUOTENAME(#p_schema) + '.' + QUOTENAME(#p_table);
EXECUTE sp_executesql #SQL, N'#y DATETIME OUTPUT', #y OUTPUT;
-- PRINT(#SQL); --- Debugging
SELECT #y;
I am very new to SQL in that I just finished reading Sams Teach Yourself SQL in 10 Minutes and that is my only SQL knowledge. So now that I'm done with the book, I'm trying to create some tables so I can play around with them. I can easily create a table with a known amount of columns and specified header. Where I am having trouble is creating a table with an unknown amount of columns and a date as the header. What I have tried so far is this:
DECLARE #start_date AS DATE
DECLARE #end_date AS DATE
DECLARE #curr_date AS DATE
DECLARE #column_name AS CHAR(10)
SET #start_date = 2016-01-02
SET #end_date = 2016-12-31
SET #curr_date = #start_date
WHILE #curr_date < #end_date
SET #curr_date = DATEADD(DD, 7, #curr_date)
ALTER TABLE Project_1
ADD #curr_date DOUBLE
What I tried to do here is make a start and end point for the loop and use the loop condition which is stored in a local variable as my column header since that is what I need the column to be titled. I also tried using CAST to cast it as a char but the DBMS is delighted to let me know that there is Incorrect syntax near '#curr_date' on that last line (the ADD line) because it doesn't like that I'm trying to name a column with a local variable (I think). I don't have a sample output but the output should be a table with the first column defined as CHAR and titled emp_name because it will be holding names. All other columns defined as type DOUBLE and should be NULL because they will be holding a number of hours and must have the current date as the header #curr_date. I think all columns added to a table through the ALTER method are defaulted to NULL anyways. I've seen examples of dynamic SQL where you declare a variable to hold the select statement but I don't really understand how they add columns to a table. I'm not sure if this can be done in the CREATE statement but if it can that would be great to see. Also, this needs to be variable in the fact that I could change the #end_date to lets say... they year 2046.
Security is not an issue here
I agree with all of the comments about using ROWs not dynamically adding columns you can always dynamically pivot those latter and it will have more flexibily. So maybe some schema considerations but just to answer your specific question you where close.....
DECLARE #start_date AS DATE
DECLARE #end_date AS DATE
DECLARE #curr_date AS DATE
DECLARE #column_name AS CHAR(10)
SET #start_date = '2016-01-02'
SET #end_date = '2016-12-31'
SET #curr_date = #start_date
WHILE #curr_date < #end_date
BEGIN
DECLARE #SQL NVARCHAR(MAX)
SET #curr_date = DATEADD(DD, 7, #curr_date)
SET #SQL = 'ALTER TABLE TableB
ADD [' + CAST(#curr_date AS VARCHAR(10)) + '] FLOAT'
--PRINT #SQL
EXECUTE (#SQL)
END
I support a SQL database for a third party software package. They have a lot of what they call "Shadow Tables", really just audit tables. This is all fine and good but their system does not clean up these tables so it is up to me to do so. They also add new "Shadow Tables" without notice with every upgrade. The old way we were purging the tables was with a long list of DELETE FROM statements but this list has become very long and hard to maintain.
To try to make the purge process easier to maintain and automatically catch new "Shadow Tables" I wrote the following stored procedure. The stored procedure works but I would prefer to figure out a way without using a cursor and dynamic queries since this will be running daily on a lot of different tables. Is there an alternative way of doing this without using a cursor and dynamic queries?
DECLARE #workingTable varchar(128);
DECLARE #sqlText varchar(250);
DECLARE #CheckDate DATETIME = DATEADD(yy, -2, GETDATE());
DECLARE curKey SCROLL CURSOR FOR
SELECT name AS TableName
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name
OPEN curKey
WHILE ##fetch_status = 0
BEGIN
FETCH NEXT FROM curKey INTO #workingTable
SET #sqlText = 'DELETE FROM DataTEST.dbo.' + #workingTable + ' WHERE LAST_MOD < ''' + CONVERT(CHAR(10), #CheckDate, 101) + ''';'
--PRINT #sqlText
EXEC (#sqlText)
END
CLOSE curKey
DEALLOCATE curKey
I do not know of anyway to get away from dynamic SQL when you do not know the table names ahead of time. SQL Server has a feature where you can do variable assignment in a select statement, once for each row returned. This can be used to eliminate the cursor and pass one string with all the delete statements to SQL server to execute
DECLARE #sqlText nvarchar(MAX) = ''; -- initialize because NULL + 'x' is NULL
DECLARE #CheckDate DATETIME = DATEADD(YEAR, -2, GETDATE());
SELECT #sqlText = #SqlText + 'DELETE FROM dataTEST.dbo.' + QUOTENAME(name)
+ ' WHERE LAST_MOD < #CheckDate ; '
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name
IF ##ROWCOUNT > 0
EXEC sp_executesql #sqlText
, N'#CheckDate DATETIME'
, #CheckDate
I don't think using using cursor and dynamic query here is a bad idea
One way is to append the delete queries and execute it at the end after generating all the delete queries.
Btw, cursor is just used for framing dynamic query so it is not a big deal
DECLARE #workingTable varchar(128);
DECLARE #sqlText nvarchar(max)='';
DECLARE #CheckDate DATETIME = DATEADD(yy, -2, GETDATE());
DECLARE curKey SCROLL CURSOR FOR
SELECT name AS TableName
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name
OPEN curKey
WHILE ##fetch_status = 0
BEGIN
FETCH NEXT FROM curKey INTO #workingTable
SET #sqlText += 'DELETE FROM DataTEST.dbo.' + #workingTable + ' WHERE LAST_MOD < ''' + CONVERT(CHAR(10), #CheckDate, 101) + ''';'
END
CLOSE curKey
DEALLOCATE curKey
--PRINT #sqlText
EXEC (#sqlText)
You may get a bit better performance by doing the following:
DECLARE #workingTable SYSNAME;
DECLARE #sqlText nvarchar(MAX);
DECLARE #CheckDate DATETIME = DATEADD(YEAR, -2, GETDATE());
DECLARE curKey CURSOR LOCAL FAST_FORWARD FOR
SELECT name AS TableName
FROM dataTEST.sys.tables
WHERE (name like '%[_]h' OR name like '%[_]dh')
ORDER BY name
OPEN curKey
WHILE ##fetch_status = 0
BEGIN
FETCH NEXT FROM curKey INTO #workingTable
SET #sqlText = 'DELETE FROM DataTEST.dbo.' + QUOTENAME(#workingTable)
+ ' WHERE LAST_MOD < #CheckDate'
Exec sp_executesql #sqlText
,N'#CheckDate DATETIME'
,#CheckDate
END
CLOSE curKey
DEALLOCATE curKey
Improvements:
Use appropriate data type for sql server object names tables (SYSNAME).
Use sp_executesql instead of EXEC(#Sql)
Pass the parameter as date, do not convert it to a string so that sql server can make use of indexes defined on that column.
Use QUOTENAME() function for put square brackets around the table names just in case any of the table name is a reserved key word in sql server, so the query wont error out.
Make your cursor local and fast_forward default settings for cursor are global , you don't need that right?
I created the procedure listed below:
CREATE procedure getdata
(
#ID int,
#frm varchar(250),
#to varchar(250)
)
AS
BEGIN
DECLARE #SQL nvarchar(500)
set #SQL = 'select'
set #SQL = #SQL + ' EmpName, Address, Salary from Emp_Tb where 1=1 '
IF (#ID <> '' and #ID is not null)
Begin
SET #sql=#sql+' AND Emp_Id_Pk=' +#ID
End
END
print #sql
--execute (#sql)
I try to execute it using:
**execute getdata 3,'','';**
But I'm getting the following error:
Conversion failed when converting the nvarchar value 'select EmpName,
Address, Salary from Emp_Tb where 1=1 AND Emp_Id_Pk=' to data type int
Please help.
You are trying to concatenate a string and an integer.
You need to cast #ID as a string.
try:
SET #sql=#sql+' AND Emp_Id_Pk=' + CAST(#ID AS NVARCHAR(10))
Try Using
CONVERT(nvarchar(10),#ID)
This is similar to cast but is less expensive(in terms of time consumed)
I was using a KEY word for one of my columns and I solved it with brackets []
I use the latest version of SSMS or sql server management studio. I have a SQL script (in query editor) which has about 100 lines of code. This is error I got in the query:
Msg 245, Level 16, State 1, Line 2
Conversion failed when converting the nvarchar value 'abcd' to data type int.
Solution - I had seen this kind of error before when I forgot to enclose a number (in varchar column) in single quotes.
As an aside, the error message is misleading. The actual error on line number 70 in the query editor and not line 2 as the error says!
don't use string concatenation to produce sql, you can use sp_executesql system stored prcedure to execute sql statement with parameters
create procedure getdata #ID int, #frm varchar(250), #to varchar(250) as
begin
declare #sql nvarchar(max), #paramDefs nvarchar(max);
set nocount on;
set #sql = N'select EmpName, Address, Salary from Emp_Tb where #id is null or Emp_Id_Pk = #id';
set #paramDefs = N'#id int';
execute sp_executesql #sql, #paramDefs, #id = #ID;
end
see sp_executesql
I got this error when I used a where clause which looked at a nvarchar field but didn't use single quotes.
My invalid SQL query looked like this:
SELECT * FROM RandomTable WHERE Id IN (SELECT Id FROM RandomTable WHERE [Number] = 13028533)
This didn't work since the Number column had the data type nvarchar. It wasn't an int as I first thought.
I changed it to:
SELECT * FROM RandomTable WHERE Id IN (SELECT Id FROM RandomTable WHERE [Number] = '13028533')
And it worked.
You got this Error because you tried to convert column DataType from String to int which is
leagal if and only if
you dont have row in that table with string content inside that column
so just make sure your previously inserted Rows is compatible with the new changes
I have faced to the same problem, i deleted the constraint for the column in question and it worked for me. You can check the folder Constraints.
Capture :
You must use CONCAT and not the +
SET #sql = CONCAT(#sql,' AND Emp_Id_Pk=' ,#ID )
I'm currently having trouble writing a stored procedure and setting the value of a variable of type int to the results of a select statement with a variable as the tablename. I've looked at old threads and tried multiple methods, but no luck. If I'm not getting an error regarding the tablename, I end up getting an error with a variable conversion issue. I've been working on this for too long and any help would be appreciated. Below is a portion of my code. Thanks
DECLARE #BATCHNUMBER VARCHAR --value set in earlier code
DECLARE #ETABLE VARCHAR(50); --the table name
DECLARE #FIRSTDOCID INT;
SET #ETABLE = 'tablename_' + #BATCHNUMBER; --CREATE FIRST TABLE NAME
SELECT #FIRSTDOCID = MIN(D0CID) FROM #ETABLE
The error I get is: Must declare the table variable "#ETABLE"
You are trying to select from a VARCHAR, not a table. The only way to make this work is by using Dynamic SQL.
DECLARE #SQL NVARCHAR(250);
SET #SQL = 'SELECT #OUTPUT = MIN(D0CID) FROM ' + QuoteName(#ETABLE);
EXEC sp_executeSql #SQL, N'#output INT OUTPUT', #FIRSTDOCID OUTPUT;
SELECT #FIRSTDOCID;
However, I would not suggest using Dynamic SQL as this often leads to SQL injection.
You'll probably have to do something like use exec if you're dynamically building the query:
SET #QUERY = "SELECT" + ...etc.
exec(#QUERY)
Since ETABLE is a varchar, and not, as expected, a 'table variable'.