Weird issue with create table - sql-server

I'm working on a SSIS package. I have an array with a list of names. Those names will be table names.
To create those tables, I loop a SQL task editor EXECUTING a stored procedure accepting a parameter (name of the table)
Code for the stored procedure:
ALTER PROCEDURE [dbo].[TLP.CreaTable]
(
#TableName VARCHAR(255)
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL VARCHAR(4000)
SET #SQL = 'IF NOT EXISTS ( SELECT *
FROM sysobjects
WHERE id = OBJECT_ID(N''[dbo].' + #TableName + ''')
AND xtype in (N''U''))
CREATE TABLE [dbo].' + #TableName + '(
[Serial] [int] NOT NULL,
[Marc] [bit] NOT NULL,
[Sell] [bit] NOT NULL
) ON [PRIMARY]'
EXEC (#SQL)
END
The issue comes when one of the names in the array is a number. I must say all the names enter the stored procedure with []. I've created a breakpoint before each execution and when the table name is a number it brings an error inside the SSIS (Also for you to have a better idea Ive ran the stored procedure from the SQL Server Management Studio, and the error persists, Ive used the table name [3001] which is actually a real name that's causing the problem).
EXEC dbo.[TLP.CreaTable] [3001]
Msg 170, Level 15, State 1, Line 5 Line 5: Incorrect syntax near '.3001'.
If I do something like
EXEC dbo.[TLP.CreaTable] RANDOM_NAME
It works just perfectly
Apparently there's some '.' in the middle. But I just cant find it in the code.
Thanks
EDIT:
It was a matter of quotation. It worked in the SQL Server Management Studio. But when I tried it in the SSIS the error persisted. Tried modifying the C# code to add '[ ]' instead of just [] and it didn't work eiter.
The error inside the SSIS is as follows.
Error: 0xC002F210 at Create Table 030698, Execute SQL Task: Executing the query "EXEC dbo.[TLP.CreaTable] ?" failed with the following error: "An error occurred while extracting the result into a variable of type (DBTYPE_I4)".
Possible failure reasons: Problems with the query, "ResultSet"
property not set correctly, parameters not set correctly, or
connection not established correctly.
With breakpoints I watched the variable for the table name and it was ok '[3001]'
The calling inside the SQL Task Editor is
EXEC dbo.[TLP.CreaTable] ?
But somehow the EXEC dbo.[TLP.CreaTable] '[3001]' works in SQL Server Management Studio, but doesn't inside the SSIS.
Any idea? Thanks.
EDIT 2
Since Im pretty new to the forum, should I ask this second question in a new 'question'? Since the matter issued in the subject was solve.
Thanks a lot.
EDIT 3
Fixed. Parameter in CREATE TABLE SQL task editor was long, changed it to varchar and it went smooth.
Thanks

You could try wrapping #TableName with the QuoteName function which should escape the table name for you. Change that line to:
CREATE TABLE [dbo].' + QuoteName(#TableName) + '(
Edit: Might also be an idea to wrap the table name in square brackets, in case it's a reserved word:
CREATE TABLE [dbo].[' + QuoteName(#TableName) + '] (

try using ' around the table name
EXEC dbo.[TLP.CreaTable] '[3001]'

Add brackets around the
[' + #TableName + ']
So that the store procedure looks like this:
ALTER PROCEDURE [dbo].[TLP.CreaTable]
(
#TableName VARCHAR(255)
)
AS
BEGIN
SET NOCOUNT ON
DECLARE #SQL VARCHAR(4000)
SET #SQL = 'IF NOT EXISTS ( SELECT *
FROM sysobjects
WHERE id = OBJECT_ID(N''[dbo].' + #TableName + ''')
AND xtype in (N''U''))
CREATE TABLE [dbo].[' + #TableName + '](
[Serial] [int] NOT NULL,
[Marc] [bit] NOT NULL,
[Sell] [bit] NOT NULL
) ON [PRIMARY]'
EXEC (#SQL)
END

You can't create a table name with a number directly. Try putting the table name in quotations: I.e.
EXEC [dbo].[TLP.CreaTable] ['100']

Related

Could not find stored procedure 'sp_msforeachtable' while looping through server for stats (SSMS)

I've written this to loop through each database on a server, collecting the statistics for each table and storing them in a temp table. Eventually, I'll integrate this into a more permanent structure, but for now I'm just trying to get this working. My problem is, after 57 databases, I get the error stating it can't find the stored procedure sp_msforeachtable.
I've verified that this stored procedure exists on every database on the server and on the server level.
I've excluded this database in the findings by adding it to the "where name not in" condition, and it just moves to the next one in the list and gives the same error.(I've confirmed it exists on the next database also). I've actually done this for the next 6 databases.
This is causing me to not collect accurate information. Am I running out of resources somewhere?
DECLARE #Database TABLE (DbName SYSNAME);
IF OBJECT_ID('tempdb.dbo.#TableLvlSizes', 'U') IS NOT NULL
BEGIN
PRINT 'dropping table'
DROP TABLE tempdb.dbo.#TableLvlSizes;
END
CREATE TABLE #TableLvlSizes (
TableName nvarchar(128)
,NumberOfRows varchar(50)
,ReservedSpace varchar(50)
,TableDataSpace varchar(50)
,IndexSize varchar(50)
,unused varchar(50))
DECLARE #DbName AS SYSNAME;
DECLARE #Sql1 AS VARCHAR(MAX);
SET #DbName = '';
INSERT INTO #Database (DbName)
SELECT NAME
FROM sys.databases
where name not in ('tempdb')
ORDER BY NAME ASC;
WHILE #DbName IS NOT NULL
BEGIN
SET #DbName = (
SELECT MIN(DbName)
FROM #Database
WHERE DbName > #DbName
);
print #DbName;
SET #Sql1 =
'USE ' + #DbName + '; ' + '
Exec sp_msforeachtable
''insert into #TableLvlSizes exec sp_spaceused [?]''
'
Exec (#SQL1);
END
If someone is using Azure SQL, they will not find sp_MSforeachtable since it is not available in Azure SQL.
You may need to create one for yourself.
Since you already verified that the stored procedure does in fact exist, I believe your database is case sensitive. Therefore, the error is still accurate. Basically, the stored procedure with the case you used does not exist. The actual procedure name is sp_MSforeachtable
In your code, you are using the following:
Exec sp_msforeachtable
If you change your code to use the proper case for the stored procedure to be sp_MSforeachtable, it should work:
SET #Sql1 =
'USE ' + #DbName + '; ' + '
Exec sp_MSforeachtable
''insert into #TableLvlSizes exec sp_spaceused [?]'''

Table already exists in a new SQL Server database

I'm writing a script to create a bunch of tables in SQL Server. As I write the script, I want to create and delete the database. The problem is that the I get an error saying that the object already exists.
Here is my example script
DECLARE #db_name varchar(20);
DECLARE #command varchar(100);
SET #db_name='testdb';
SET #command='DROP DATABASE ' + #db_name
IF EXISTS(SELECT * FROM sys.databases WHERE name=#db_name)
exec(#command)
SET #command='CREATE DATABASE ' + #db_name
EXEC(#command)
--listing databaes
SELECT name from master.dbo.sysdatabases
-- using our database
SET #command='USE ' + #db_name
EXEC(#command)
PRINT 'listing tables'
SET #command = 'SELECT table_name FROM ' + #db_name + '.INFORMATION_SCHEMA.TABLES WHERE table_type = "base TABLE"'
EXEC(#command)
CREATE TABLE stuff(
name VARCHAR(30) PRIMARY KEY NOT NULL,
weight INT,
quantity INT)
and the output I get is
name
------------
master
tempdb
model
msdb
testdb
SW
(6 rows affected)
listing tables
table_name
Error:
Msg 2714, Level 16, State 6, Server Orange, Line 22
There is already an object named 'stuff' in the database.
I run this on a Linux mint machine, a freshly installed SQL Server, and I use sqlcmd. I guess I can put a drop/delete command before the creating the table, but this shouldn't happen to begin with. What is going on here?
When you execute a USE statement from dynamic SQL, the database context reverts back to the original database context (master?) when the executed batch completes. You'll need to add a USE to the CREATE TABLE script and execute it using dynamic SQL too:
SET #command = N'USE' + QUOTENAME(#db_name) + N';
CREATE TABLE stuff(
name VARCHAR(30) PRIMARY KEY NOT NULL,
weight INT,
quantity INT);
';
bind the create table statement inside a object existence check. like this
IF OBJECT_ID('stuff') IS NULL
BEGIN
CREATE TABLE stuff(
name VARCHAR(30) PRIMARY KEY--NOT NULL is not needed as the primary key does not allow NULL,
weight INT,
quantity INT)
END

MS SQL loop through all DBs. Script works to alter table but not stored procedure

I use a piece of code to loop through all the databases on an MS SQL server. It works fine for altering a column on a table and also for updating the data. But I continue to get errors when trying to alter a stored procedure. Here is the code:
use master
declare #dbname varchar(100)
,#sql varchar(max)
declare db_cur cursor for
SELECT name
FROM sys.databases where ([name] like 'ce%')
and [state] = 0
open db_cur
fetch next from db_cur into #dbname
while ##FETCH_STATUS = 0
begin
set #sql=
'ALTER TABLE ['+#dbname+'].[dbo].MyStuff
ADD myNewColumn bit NULL DEFAULT(0)
'
exec(#sql)
fetch next from db_cur into #dbname
end
close db_cur
deallocate db_cur
So the code above works perfectly fine. But when I alter that code to instead do an alter stored procedure I receive the message below:
'CREATE/ALTER PROCEDURE' does not allow specifying the database name as a prefix to the object name.
I realized that the message stated I can't use the database name in the front of the procedure like I was doing here: ALTER procedure ['+#dbname+'].[dbo].[spSelectSomething]. But I haven't been able to figure out a way around the issue. Thanks for your help.
You need to nest dynamic SQL for this task because a proc CREATE or ALTER must be the first statement in the batch:
SET #sql= N'EXEC(N''USE ' + QUOTENAME(#dbname) + N';EXEC(N''''CREATE PROC...;'''')'')';

Incorrect Syntax Near GO, T-SQL EXEC()

I am using the following script:
DECLARE #dbName NVARCHAR(20) = 'ABC';
EXEC ( N' USE ' + #dbName + '
GO
-- Create Schema
CREATE SCHEMA meta
GO
-- Create Log Table
CREATE TABLE meta.LogAudit(
[EventDate] [datetime] NOT NULL DEFAULT (getdate()),
[EventType] [nvarchar](64) NULL
)
GO
'
);
but it throws me the following error.
Msg 111, Level 15, State 1, Line 5
'CREATE SCHEMA' must be the first statement in a query batch.
Msg 102, Level 15, State 1, Line 22
Incorrect syntax near 'GO'.
Why is that?
--
Edit:
This answer seems to be answering my question:
dynamic sql error: 'CREATE TRIGGER' must be the first statement in a query batch
So it seems that in my situation I cannot program it dynamically. My whole code works in the following way:
USE DB
GO
CREATE SCHEMA SCH
GO
CREATE TABLE SCH.TABLE
GO
CREATE TRIGGER TRG
GO
So as SCHEMA and TRIGGER needs to be first query statement, it cannot be written in this way.
Try removing comma after [EventType] [nvarchar](64) NULL, and see if the error message changes.
So you have 2 problems:
As #Tanner has pointed out, you cannot use GO in dynamic SQL.
You still have that trailing comma in the meta.LogAudit table columns definition.
Try running this code:
DECLARE #dbName NVARCHAR(20) = 'ABC';
declare #sql nvarchar(max) = N'exec '+ #DBName + '..sp_executesql N''CREATE SCHEMA meta'''
execute(#sql)
declare #sql2 nvarchar(max) = '
-- Create Log Table
CREATE TABLE '+ #DBName + '.meta.LogAudit(
[EventDate] [datetime] NOT NULL DEFAULT (getdate()),
[EventType] [nvarchar](64) NULL
)'
exec sp_executesql #sql2,N''
It will allow you to programmatically create schema in the specified Database as opposite to using current database.
You can use following script for solution to your requirement with the help of unsupported undocumented stored procedure sp_MSForEachDB
EXEC sp_MSForEachDB '
Use [?];
IF ''[?]'' = ''[ABC]''
begin
-- Create Schema
exec sp_executesql N''CREATE SCHEMA meta''
end
'
EXEC sp_MSForEachDB '
Use [?];
IF ''[?]'' = ''[ABC]''
begin
-- Create Log Table
CREATE TABLE meta.LogAudit(
[EventDate] [datetime] NOT NULL DEFAULT (getdate()),
[EventType] [nvarchar](64) NULL,
)
end'

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