Syntax error: Stored Procedure for Generic Insert - sql-server

I have problem compilin this code..can anyone tell whats wrong with the syntax
CREATE PROCEDURE spGenericInsert
(
#insValueStr nvarchar(200)
#tblName nvarchar(10)
)
AS
BEGIN
DECLARE #insQueryStr nvarchar(400)
DECLARE #insPrimaryKey nvarchar(10)
DECLARE #rowCountVal integer
DECLARE #prefix nvarchar(5)
IF #tblName='HW_Master_DB'
SET #rowCountVal=(SELECT COUNT(*) FROM HW_Master_DB)
ELSE IF #TableName='SW_Master_DB'
SET #rowCountVal=(SELECT COUNT(*) FROM SW_Master_DB)
ELSE IF #TableName='INV_Allocation_DB'
SET #rowCountVal=(SELECT COUNT(*) FROM INV_Allocation_DB)
ELSE IF #TableName='REQ_Master_DB'
SET #rowCountVal=(SELECT COUNT(*) FROM REQ_Master_DB)
IF #tblName = 'DEFECT_LOG'
SET #prefix='DEF_'
ELSE IF #tblName='INV_Allocation_DB'
SET #prefix='INV_'
ELSE IF #tblName='REQ_Master_DB'
SET #prefix='REQ_'
ELSE IF #tblName='SW_Master_DB'
SET #prefix='SWI_'
ELSE IF #tblName='HW_Master_DB'
SET #prefix='HWI_'
SET #insPrimaryKey= #prefix + RIGHT(replicate('0',5)+ convert(varchar(5),#rowCountVal),5) -- returns somethin like 'DEF_00005'
SET #insQueryStr= 'INSERT INTO ' + #tblName + ' VALUES (' + #insPrimaryKey + ',' + #insValueStr + ')'
EXEC(#insQueryStr)
END
I know about Integer Identity columns.. but i have to use a AlphaNumeric ID in the tables in inserting new values in a highly multi-user intranet system.
The records will not be deleted from the table. So problem is that of maintain synchronous insertion of records with ID field automatically generated.
Any suggestions how that can be done.

Take your pick:
#TableName isn't defined
#tblName vs. #TableName

I cannot immediately see what's wrong with the syntax (the sharp eye of Jonathan Lonowski has solved that already), but there are some things wrong with the code:
You create dynamic SQL, so your code is vunerable to SQL-injection attacks. Both the input parameters are used in a dangerous way. Solve this by creating a stored procedure for every table. So you don't have to generate SQL anymore.
There is no check if the table is not in the list used.
Your primary key generation algorithm can/will create duplicate keys in a multi-user scenario, or if rows are deleted from the table. Solve by using an identity column or some other feature from the database you are using.

Honestly, you seem to be making a headache for yourself. Check out integer identities and IDENTITY syntax.
Unless you are truly required to use keys in the "DEF_00005" format, they will make your life a lot easier.
CREATE TABLE DemoTable (
Key INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
Value VARCHAR(200)
);
INSERT INTO DemoTable (Value) VALUES ('Something');
SELECT * FROM DemoTable;
| Key | Value |
|-----|-----------|
| 1 | Something |

Aside from missing lots of semicolons, you're going to have to give us more to go on.
Actually, SQL Server might not need semicolons, so ignore that...
But here is a good place to start learning about stored prcedures in SQL server. You can search Google for some more as well.

Related

Adding a new column to a table getting the column name from another table

I'm using Microsoft SQL server management studio.
I would like to add a new column to a table (altertable1), and name that column using the data from a cell (Date) of another table (stattable1).
DECLARE #Data nvarchar(20)
SELECT #Data = Date
FROM stattable1
WHERE Adat=1
DECLARE #sql nvarchar(1000)
SET #sql = 'ALTER TABLE altertable1 ADD ' + #Data + ' nvarchar(20)'
EXEC (#sql)
Executing this, I get the following error and can't find out why:
"Incorrect syntax near '2021'."
The stattable1 looks like this:
Date |Adat
2021-09-08 |1
2021-09-08 is a daily generated data:
**CONVERT(date,GETDATE())**
Just like Larnu said in comment, maybe this is not a main problem for you, but if you want to do this add [ ] when you want to name column starting with number.
Like this:
SET #sql = 'ALTER TABLE altertable1 ADD [' + #Data + '] nvarchar(20)'
And of course, naming columns by date or year is not best practice.
The problem with your overall design is that you seem to be adding a column to the table every day. A table is not a spreadsheet and you should be storing data for each day in a row, not in a separate column. If your reports need to look that way, there are many ways to pivot the data so that you can handle that at presentation time without creating impossible-to-maintain technical debt in your database.
The problem with your current code is that 2021-06-08 is not a valid column name, both because it starts with a number, and because it contains dashes. Even if you use a more language-friendly form like YYYYMMDD (see this article to see what I mean), it still starts with a number.
The best solution to the local problem is to not name columns that way. If you must, the proper way to escape it is to use QUOTENAME() (and not just manually slap [ and ] on either side):
DECLARE #Data nvarchar(20), #sql nvarchar(max);
SELECT #Data = Date
FROM dbo.stattable1
WHERE Adat = 1;
SET #sql = N'ALTER TABLE altertable1
ADD ' + QUOTENAME(#Data) + N' nvarchar(20);';
PRINT #sql;
--EXEC sys.sp_executesql #sql;
This also demonstrates your ability to debug a statement instead of trying to decipher the error message that came from a string you can't inspect.
Some other points to consider:
if you're declaring a string as nvarchar, and especially when dealing with SQL Server metadata, always use the N prefix on any literals you define.
always reference user tables with two-part names.
always end statements with statement terminators.
generally prefer sys.sp_executesql over EXEC().
some advice on dynamic SQL:
Protecting Yourself from SQL Injection - Part 1
Protecting Yourself from SQL Injection - Part 2

Create table variable with exact same structure of another table

In T-SQL, I can create a table variable using syntax like
DECLARE #table AS TABLE (id INT, col VARCHAR(20))
For now, if I want to create an exact copy of a real table in the database, I do something like this
SELECT *
FROM INFOMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'MY_TABLE_NAME'
to check the column datatype and also max length, and start to create the #table variable, naming the variable, datatype and max_length one by one which is not very effective. May I know if there is any simpler way to do it like
DECLARE #table AS TABLE = SOME_REAL_TABLE_IN_DATABASE
Furthermore, is there any way to retrieve the column name, data type and max length of the column and use it directly in the declaration like
DECLARE #table AS TABLE (#col1_specs)
Thank you in advance.
EDIT:
Thanks for the answers and comments, we can do that for #table_variable but only in dynamic SQL and it is not good for maintainability. However, we can do that using #temp_table.
Based on the answer by Ezlo, we can do something like this :
SELECT TABLE.* INTO #TEMP_TABLE FROM TABLE
For more information, please refer to this answer.
Difference between temp table and table variable (stackoverflow)
Difference between temp table and table variable (dba.stackexchange)
Object names and data types (tables, columns, etc.) can't be parameterized (can't come from variables). This means you can't do the following (which would be required to copy a table structure, for example):
DECLARE #TableName VARCHAR(50) = 'Employees'
SELECT
T.*
FROM
#TableName AS T
The only workaround is to use dynamic SQL:
DECLARE #TableName VARCHAR(50) = 'Employees'
DECLARE #DynamicSQL VARCHAR(MAX) = '
SELECT
T.*
FROM
' + QUOTENAME(#TableName) + ' AS T '
EXEC (#DynamicSQL)
However, variables (scalar and table variables) declared outside the dynamic SQL won't be accessible inside as they lose scope:
DECLARE #VariableOutside INT = 10
DECLARE #DynamicSQL VARCHAR(MAX) = 'SELECT #VariableOutside AS ValueOfVariable'
EXEC (#DynamicSQL)
Msg 137, Level 15, State 2, Line 1
Must declare the scalar variable "#VariableOutside".
This means that you will have to declare your variable inside the dynamic SQL:
DECLARE #DynamicSQL VARCHAR(MAX) = 'DECLARE #VariableOutside INT = 10
SELECT #VariableOutside AS ValueOfVariable'
EXEC (#DynamicSQL)
Result:
ValueOfVariable
10
Which brings me to my conclusion: if you want to dynamically create a copy of an existing table as a table variable, all the access of your table variable will have to be inside a dynamic SQL script, which is a huge pain and has some cons (harder to maintain and read, more prone to error, etc.).
A common approach is to work with temporary tables instead. Doing a SELECT * INTO to create them will inherit the table's data types. You can add an always false WHERE condition (like WHERE 1 = 0) if you don't want the actual rows to be inserted.
IF OBJECT_ID('tempdb..#Copy') IS NOT NULL
DROP TABLE #Copy
SELECT
T.*
INTO
#Copy
FROM
YourTable AS T
WHERE
1 = 0
The answer for both questions is simple NO.
Although, I agree with you that T-SQL should change in this way.
In the first case, it means having a command to clone a table structure.
Of course, there is a possibility to make your own T-SQL extension by using SQLCLR.

Validating fields with dynamic table names

I have N amounts of tables like this:
foo_1_data
(
id int,
some_foo_data_1 varchar(100),
some_foo_data_2 char(5)
)
foo_2_data
(
id int,
some_foo_data_1 varchar(100),
some_foo_data_2 char(5)
)
bar_1_data
(
id int,
some_bar_data_1 decimal(10,2),
some_bar_data_2 datetime
)
bar_2_data
(
id int,
some_bar_data_1 decimal(10,2),
some_bar_data_2 datetime
)
The tables are based on different data - foo_data and bar_data - and are user generated and there is an ID - foo_N_data and bar_N_data - set.
Now I want to implement validation on records on these tables through a Stored Procedure to follow certain business rules on the receiving end, which we don't have control over.
Example:
some_foo_data_1 is a varchar(100) is a name and in our system it can be
between 0 and 100 characters but on the receiving end it needs to be
between 2 and 70 characters.
some_bar_data_1 is a decimal(10.2) and it needs to be between 0 and
100 (let's say it's a percentage)
We tried and implement Stored Procedures called, for example, sp_rule_name, that takes the original table and column and validated it through Dynamic SQL like this:
EXEC('SELECT ' + #column_to_validate + ' FROM ' + #table_to_validate + ' WHERE ...')
This creates re-usable code between the different validation procedures (one for each different data set - one for foo_data and one for bar_data) but the code is hard to understand and maintain because of the mixture of T-SQL and Dynamic SQL. We also tried using functions on the fields but dropped it because of slow performance.
Is there an efficient and re-usable way to validate data from dynamic tablas?
Note: We know how the table structure will look like of both foo and bar but we don't know the N value so we can't call it directly.
Once I had faced a similar situation and I had to rely on dynamic SQL to get the job done. But troubleshooting it and even reading it was a pain.
I did a small change and that helped in solving the troubleshooting/reading problem especially if it's only SELECT on the underlying table
See live demo
create proc sp_rule_name #tblName varchar(100)
as
begin
declare #sql varchar(max);
set #sql =' CREATE VIEW tempView AS SELECT * FROM '+ #tblName+ ' ;'
exec (#sql)
-- after this normal SQL syntax using tempView object for all sort of validations. No need for dynamic SQL
select * from tempView;
end
go
create table foo_1_data (
id int,
some_foo_data_1 varchar(100),
some_foo_data_2 char(5)
);
insert into foo_1_data values
(1,'345453','56666');
execute sp_rule_name N'foo_1_data';

Which column is being truncated? [duplicate]

The year is 2010.
SQL Server licenses are not cheap.
And yet, this error still does not indicate the row or the column or the value that produced the problem. Hell, it can't even tell you whether it was "string" or "binary" data.
Am I missing something?
A quick-and-dirty way of fixing these is to select the rows into a new physical table like so:
SELECT * INTO dbo.MyNewTable FROM <the rest of the offending query goes here>
...and then compare the schema of this table to the schema of the table into which the INSERT was previously going - and look for the larger column(s).
I realize that this is an old one. Here's a small piece of code that I use that helps.
What this does, is returns a table of the max lengths in the table you're trying to select from. You can then compare the field lengths to the max returned for each column and figure out which ones are causing the issue. Then it's just a simple query to clean up the data or exclude it.
DECLARE #col NVARCHAR(50)
DECLARE #sql NVARCHAR(MAX);
CREATE TABLE ##temp (colname nvarchar(50), maxVal int)
DECLARE oloop CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'SOURCETABLENAME' AND TABLE_SCHEMA='dbo'
OPEN oLoop
FETCH NEXT FROM oloop INTO #col;
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #sql = '
DECLARE #val INT;
SELECT #val = MAX(LEN(' + #col + ')) FROM dbo.SOURCETABLENAME;
INSERT INTO ##temp
( colname, maxVal )
VALUES ( N''' + #col + ''', -- colname - nvarchar(50)
#val -- maxVal - int
)';
EXEC(#sql);
FETCH NEXT FROM oloop INTO #col;
END
CLOSE oloop;
DEALLOCATE oloop
SELECT * FROM ##temp
DROP TABLE ##temp;
Another way here is to use binary search.
Comment half of the columns in your code and try again. If the error persists, comment out another half of that half and try again. You will narrow down your search to just two columns in the end.
You could check the length of each inserted value with an if condition, and if the value needs more width than the current column width, truncate the value and throw a custom error.
That should work if you just need to identify which is the field causing the problem. I don't know if there's any better way to do this though.
Recommend you vote for the enhancement request on Microsoft's site. It's been active for 6 years now so who knows if Microsoft will ever do anything about it, but at least you can be a squeaky wheel: Microsoft Connect
For string truncation, I came up with the following solution to find the max lengths of all of the columns:
1) Select all of the data to a temporary table (supply column names where needed), e.g.
SELECT col1
,col2
,col3_4 = col3 + '-' + col4
INTO #temp;
2) Run the following SQL Statement in the same connection (adjust the temporary table name if needed):
DECLARE #table VARCHAR(MAX) = '#temp'; -- change this to your temp table name
DECLARE #select VARCHAR(MAX) = '';
DECLARE #prefix VARCHAR(256) = 'MAX(LEN(';
DECLARE #suffix VARCHAR(256) = ')) AS max_';
DECLARE #nl CHAR(2) = CHAR(13) + CHAR(10);
SELECT #select = #select + #prefix + name + #suffix + name + #nl + ','
FROM tempdb.sys.columns
WHERE object_id = object_id('tempdb..' + #table);
SELECT #select = 'SELECT ' + #select + '0' + #nl + 'FROM ' + #table
EXEC(#select);
It will return a result set with the column names prefixed with 'max_' and show the max length of each column.
Once you identify the faulty column you can run other select statements to find extra long rows and adjust your code/data as needed.
I can't think of a good way really.
I once spent a lot of time debugging a very informative "Division by zero" message.
Usually you comment out various pieces of output code to find the one causing problems.
Then you take this piece you found and make it return a value that indicates there's a problem instead of the actual value (in your case, should be replacing the string output with the len(of the output)). Then manually compare to the lenght of the column you're inserting it into.
from the line number in the error message, you should be able to identify the insert query that is causing the error. modify that into a select query to include AND LEN(your_expression_or_column_here) > CONSTANT_COL_INT_LEN for the string various columns in your query. look at the output and it will give your the bad rows.
Technically, there isn't a row to point to because SQL didn't write the data to the table. I typically just capture the trace, run it Query Analyzer (unless the problem is already obvious from the trace, which it may be in this case), and quickly debug from there with the ages old "modify my UPDATE to a SELECT" method. Doesn't it really just break down to one of two things:
a) Your column definition is wrong, and the width needs to be changed
b) Your column definition is right, and the app needs to be more defensive
?
The best thing that worked for me was to put the rows first into a temporary table using select .... into #temptable
Then I took the max length of each column in that temp table. eg. select max(len(jobid)) as Jobid, ....
and then compared that to the source table field definition.

Generic Insert stored proc : Runtime error

The following code generates the primaey key for the new record to be inserted and inserts the record into a table, whose name and the values to be inserted are given as parameters to the stored procedure. I am getting a runtime error. I am using Visual Studio 2005 to work with SQL Server 2005 Express Edition
ALTER PROCEDURE spGenericInsert
(
#insValueStr nvarchar(300),
#tblName nvarchar(10)
)
AS
DECLARE #sql nvarchar(400)
DECLARE #params nvarchar(200)
DECLARE #insPrimaryKey nvarchar(10)
DECLARE #rowCountVal integer
DECLARE #prefix nvarchar(5)
--following gets the rowcount of the table--
SELECT #rowCountVal = ISNULL(SUM(spart.rows), 0)
FROM sys.partitions spart
WHERE spart.object_id = object_id(#tblName) AND spart.index_id < 2
SET #rowCountVal = #rowCountVal+1
--Following Creates the Primary Key--
IF #tblName = 'DEFECT_LOG'
SET #prefix='DEF_'
ELSE IF #tblName='INV_Allocation_DB'
SET #prefix='INV_'
ELSE IF #tblName='REQ_Master_DB'
SET #prefix='REQ_'
ELSE IF #tblName='SW_Master_DB'
SET #prefix='SWI_'
ELSE IF #tblName='HW_Master_DB'
SET #prefix='HWI_'
SET #insPrimaryKey= #prefix + RIGHT(replicate('0',5)+ convert(varchar(5),#rowCountVal),5) -- returns somethin like 'DEF_00005'
-- Following is for inserting into the table --
SELECT #sql = N' INSERT INTO #tableName VALUES ' +
N' ( #PrimaryKey , #ValueStr )'
SELECT #params = N'#tableName nvarchar(10), ' +
N'#PrimaryKey nvarchar(10), ' +
N'#ValueStr nvarchar(300)'
EXEC sp_executesql #sql, #params, #tableName=#tblName, #PrimaryKey=#insPrimaryKey, #ValueStr=#insValueStr
Output Message:
Running [dbo].[spGenericInsert] ( #insValueStr = 2,"Hi",1/1/1987, #tblName = DEFECT_LOG ).
Must declare the table variable "#tableName".
No rows affected.
(0 row(s) returned)
#RETURN_VALUE = 0
Finished running [dbo].[spGenericInsert].
You are going to have to concatenate the table name directly into the string, as this cannot be parameterized:
SELECT #sql = N' INSERT INTO [' + #tblName + '] VALUES ' +
N' ( #PrimaryKey , #ValueStr )'
SELECT #params = N'#PrimaryKey nvarchar(10), ' +
N'#ValueStr nvarchar(300)'
To prevent injection attacks, you should white-list this table name. This also isn't robust if the table has other non-nullable columns, etc.
note: Personally, though, I don't think this is a good use of TSQL; it might be more appropriate to construct the command in the client (C# or whatever), and execute it as a parameterized command. There are use-cases for dynamic-SQL, but I'm not sure this is a good example of one.
Better yet, use your preferred ORM tool (LINQ-to-SQL, NHibernate, LLBLGen, Entity Framework, etc) to do all this for you, and concentrate on your actual problem domain.
White list essentially means make sure that the table being passed in is a valid table that you want them to be able to insert into. Let's just say for arguments sake that table name is user provided, the user could then start inserting records into system tables.
You can do a white list check by bouncing the table name of the sysobjects table:
select * from sysobjects where name=#tblname and xType='U'
However as Marc suggested this is not a good use of TSQL, and your better off handling this in the app tier as a paramatized query.
Agree with Marc- overall this is an extremely poor idea. Generic inserts/updates or deletes cause problems for the database eventually.
Another point is that this process will have problems when two users run simulutaneously against the same table as they will try to insert the same Primary Key.

Resources