Dynamic bulk insert silently failing - sql-server

I have a job that runs nightly to load a bunch of flat files to a database (around 250 files/tables, runs for around one hour). It uses a cursor to loop through the tables, and runs the following dynamic query to build and execute a bulk insert of the data.
select #sql = 'BULK INSERT tmp.VW_' + #tableName
+ ' FROM ''' + #filePath + #fileName
+ ''' WITH(FIELDTERMINATOR = '''
+ #fieldTerminator + ''',ROWTERMINATOR = '''
+ #rowTerminator + ''', KEEPNULLS,TABLOCK);'
exec (#sql)
Three times in the past year this statement has silently failed for the entire job. What I mean by this is the cursor loops through each table, it logs that the data is loaded for all tables, but when I check the data all 250 tables are empty.
When I re-run the job it works fine and all the data is loaded. When there are issues with the file format, SQL deadlock, TCP connection issues the job stops loading and logs the error.
I have checked the server logs and see nothing, and nothing in my jobs logs either. I've tried recreating the issue, but without success. As I mentioned it has happened 3x in the past year, but 0x in the previous three years this job has run.
Any idea what is happening or how to prevent it?

Can you wrap everything in a Try...Catch?
BEGIN TRY
select #sql = 'BULK INSERT tmp.VW_' + #tableName
+ ' FROM ''' + #filePath + #fileName
+ ''' WITH(FIELDTERMINATOR = '''
+ #fieldTerminator + ''',ROWTERMINATOR = '''
+ #rowTerminator + ''', KEEPNULLS,TABLOCK);'
exec (#sql)
END TRY
GO
-- The previous GO breaks the script into two batches,
-- generating syntax errors. The script runs if this GO
-- is removed.
BEGIN CATCH
-- Store exception information casting error message to varchar
SELECT #IsError = 1, #ErrorMessage = 'Exception message: ' + Cast (Error_Message() as varchar) + ';'
END CATCH
IF #IsError <> 0
BEGIN
DECLARE #Subject VARCHAR(250)
SELECT #Subject = 'Server Name: ' + ##SERVERNAME + '; Database Name: ' + DB_NAME() + ';' + ' - Email failed'
EXEC msdb.dbo.sp_send_dbmail #recipients = 'AlertSupport#abc.com'
, #from_Address = 'sqlemail.abc.com'
, #body = #ErrorMessage
, #subject = #Subject
, #profile_name = 'DBProfileName'
Raiserror('Error encountered while sending email', 16, 1);
END
GO
I experienced some weirdness a few years back doing something similar. We never figured out what the issue was, but it seemed like something was taking down SQL my Server overnight. Eventually I created a VB.NET .exe that fired off every few minutes, using Windows Task Scheduler, and the .exe checked to see if SQL Server was running or not. If the .exe found SQL Server was not running, it started it up. We never figured out what was taking it down...

Related

SQL Server on database drop trigger deleting associated agent jobs

We have databases with associated SQL agent jobs that we keep track in a table called databasename.dbo.sqlagentjobs. Some databases are demos or QA databases and get regularly deleted.
At some point it happens that the administrator forgets to delete the associated SQL Agent jobs. In this case I wanted to write a trigger on database drop like this:
create trigger deleteagentjobs on all server for drop_database as
begin
declare #databasename nvarchar(100)
declare #eventdata xml
declare #query nvarchar(1000)
set #eventdata = EVENTDATA()
set #databasename = #eventData.value('(/EVENT_INSTANCE/DatabaseName)[1]','varchar(128)')
if object_id(#databasename +'.dbo.sqlagentjobs') is not null begin -- check if the database is a database with "our" database scheme
set #query = N'declare c insensitive cursor for select jobname_komplett from ' + #databasename + N'.dbo.sqlagentjobs ' +
N'declare #jobname nvarchar(257) ' +
N'declare #jobid uniqueidentifier ' +
N'open c ' +
N'fetch next from c into #jobname ' +
N'while ##FETCH_STATUS = 0 begin ' +
N' select job_id from msdb.dbo.sysjobs where name = #jobname ' +
N' exec msdb.dbo.sp_delete_job #jobid ' +
N' fetch next from c into #jobname ' +
N'end ' +
N'close c ' +
N'deallocate c '
exec sp_sqlexec #query
end
end
The issue with the trigger is that I get the following logical error message when I try to drop the database as follows:
exec msdb.dbo.sp_delete_database_backuphistory #database_name = n'demo'
go
alter database [demo] set single_user with rollback immediate
go
drop database [demo]
Error message:
Msg 5064, Level 16, State 1, Line 3
Changes to the state or options of database demo cannot be made at this time. The database is in single-user mode, and a user is currently connected to it.
Msg 5069, Level 16, State 1, Line 3
ALTER DATABASE statement failed.
Msg 3702, Level 16, State 4, Line 5
Cannot drop database demo because it is currently in use.
The error makes sense to me since I set the database to single user mode and try to access the database inside of the trigger.
Any ideas how to fix this? Thanks in advance!

Error when trying to delete the database in azure portal after removing from SQL pool

I am trying to delete a datapase in azure portal. After removing from SQL pool, I am getting the following error:
Msg 37106, Level 16, State 1, Line 7 The database '{databasename}' on server {'servername}' is in use by job account 'jobagent'. The database cannot be deleted or renamed while associated with a job account.
How should I address fixing this?
If you don't have other databases on it, you could simply drop the server and recreate it. That will get you around the issue.
Else Follow below steps delete database
You can kill a process by a right click on the process in the grid and selecting the Kill Process menu item. You will be asked for a confirmation to kill the related process and then will kill the open connection to the database over this process. This action is just like running to kill sql process t-sql command for a single process.
Way to drop all active connections of a database can be implemented by generating dynamic sql commands that runs a list of "Kill #spId" commands.
DECLARE #DatabaseName nvarchar(50)
SET #DatabaseName = N'Works'
--SET #DatabaseName = DB_NAME()
DECLARE #SQL varchar(max)
SET #SQL = ''
SELECT #SQL = #SQL + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DatabaseName) AND SPId <> ##SPId
-- SELECT #SQL
EXEC(#SQL)
A very similar to the sql code above, an other code block can be used by using the COALESCE as shown below
DECLARE #DatabaseName nvarchar(50)
SET #DatabaseName = N'Works'
DECLARE #SQL varchar(max)
SELECT #SQL = COALESCE(#SQL,'') + 'Kill ' + Convert(varchar, SPId) + ';'
FROM MASTER..SysProcesses
WHERE DBId = DB_ID(#DatabaseName) AND SPId <> ##SPId
--SELECT #SQL
EXEC(#SQL)
You can also refer this article

sp_rename failing when called from inside another stored procedure

First post from a self-taught data warehouse guy. I've done lots of searching and reading to get where I am now, but can't get past this sticking point.
Background: as part of our nightly ETL job, we have to copy many tables from many remote DBs (linked servers) into staging-area DBs. After table copies have finished, I continue with the transformation from the staging area DBs into production tables.
Since the remote DBs all have identical schema, I made a stored procedure in the production DB to do the work. The stored procedure accepts parameters of the remote database name and the table name. In the nightly job, SQL Server Agent runs an SSIS package; the package contains one (retry-looping) SSIS task for each remote database; all the tasks run concurrently; each task uses a variable to pass the DB name to SQL file; then the SQL file calls the stored procedure once for each table.
Example remote table and local staging-area table:
Remote: [FLTA].[cstone].[csdbo].[CLIENT]
Local: [FLTAL].dbo.[FLTA CLIENT]
The stored procedure is pretty simple, dropping the old table and using SELECT to make a fresh copy from the remote DB. It looks approximately like this:
CREATE PROCEDURE dbo.spTableCopyNew
(#p VARCHAR(50), #Tablename VARCHAR(50))
AS
-- Drop the existing table
EXEC('IF OBJECT_ID(''[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'', ''U'') IS NOT NULL
DROP TABLE [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'
)
-- Copy the new table
EXEC('SELECT * into [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']
FROM [' + #p + '].[cstone].[csdbo].[' + #Tablename +']'
)
GO
The SQL looks roughly like this:
-- Set local variables for the remote server connection, the local database name, and the table prefix
DECLARE #Prefix varchar(50)
-- Accept the variables passed in from the SSIS task
SET #Prefix = ?
-- Copy the two tables
EXEC Datawarehouse.dbo.spTableCopy #Prefix, 'CLIENT'
EXEC Datawarehouse.dbo.spTableCopy #Prefix, 'PATIENT'
Maintenance is a breeze: when we need to grab a new table from all the remote databases, I just add it to the "productionLoad.sql" file.
It works really well...except when it doesn't.
Due to un-figured-out-yet reasons, sometimes a table fails to copy. And since I'm dropping the existing table before copying the new one, this will sometimes break things further down the line. My SSIS tasks will retry up to three times per remote DB, so occasional failures are no big deal. But if the same remote DB has three failures in one night, I'm gonna have a bad time.
My current attempt at a solution is to copy the remote table to a temp table, then ONLY AFTER that copy is successful, drop the local table and rename the temp table to the "real" table. Which brings me to the problem:
I can't get sp_rename to work when called from a stored procedure, to rename tables that exist in a different database than the stored procedure. I've created new variables to resolve expressions, then send those variables to sp_rename, since I can't pass expressions into that stored procedure.
Here's my attempt at a new stored procedure:
CREATE PROCEDURE dbo.spTableCopy
(#p VARCHAR(50), #Tablename VARCHAR(50))
AS
BEGIN
EXEC('USE [' + #p + 'L]')
-- Create variables for schema and table names
-- Since sp_rename can accept variables, but not expresssions containing variables.
DECLARE #RemoteTable VARCHAR(50) = '[' + #p + '].[cstone].[csdbo].[' + #Tablename +']'
DECLARE #LocalTableTemp VARCHAR(50) = '[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +'_temp]'
DECLARE #LocalTable VARCHAR(50) = '' + #p + ' ' + #Tablename + ''
-- Check for previous temp table and drop it
EXEC('IF OBJECT_ID(''[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +'_temp]'', ''U'') IS NOT NULL
DROP TABLE [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +'_temp]'
)
-- Copy the new table
EXEC('SELECT * into ' + #LocalTableTemp + '
FROM ' + #RemoteTable + ''
)
-- Drop the existing table
EXEC('IF OBJECT_ID(''[' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'', ''U'') IS NOT NULL
DROP TABLE [' + #p + 'L].dbo.[' + #p + ' ' + #Tablename +']'
)
-- Rename temp table to real table
EXEC sp_rename #LocalTableTemp, #LocalTable
END
GO
This all works when executing it as normal SQL code, but when I make it into a stored procedure, sp_rename fails (everything else works). The final table [FLTAL CLIENT_temp] is there and contains the right data.
sp_rename returns the following error:
Msg 290, Level 16, State 2, Procedure sp_rename, Line 318
Invalid EXECUTE statement using object "Object", method "LockMatchID".
I've fought with this way too long.
Am I just screwing up the syntax?
Can I get sp_rename to work on other DBs with "USE?"
If not, will it work if I make a copy of my sp_tableCopy in every staging-area DB?
If not, will catch-try work inside this stored procedure, even if I call this stored procedure many times concurrently?
What else can I do to recover from failed table copies?
My alternate solution that I haven't pursued yet: after the temp table is successfully created, to TRUNC the existing table and insert everything from the temp table into the real table. That seems messy though.
P.S. Our IT guys are "looking into" the nature of the copy failures.
Try This...
USE
EXEC ..sp_rename '..', '<target_table>'
USE sourcedb
EXEC targetdatabase..sp_rename 'schema.oldtable', 'target_table'

Searching SQL Server database for control characters

I'm looking to search for unwanted control characters in a MSSQL database.
I currently use a stored procedure that gets created against a database I need to search, but this will only work when searching for a simple character or string of characters. See below for the procedure as it stands (This was first gathered from this site)
CREATE PROC SearchAllTables
(
#SearchStr nvarchar(100)
)
AS
BEGIN
-- Creates a Stored Procedure for the database
-- When running the procedure, set the #SearchStr parameter to the character you are searching for
CREATE TABLE #Results (ColumnName nvarchar(370), ColumnValue nvarchar(3630))
SET NOCOUNT ON
DECLARE #TableName nvarchar(256), #ColumnName nvarchar(128), #SearchStr2 nvarchar(110)
SET #TableName = ''
SET #SearchStr2 = QUOTENAME('%' + #SearchStr + '%','''')
WHILE #TableName IS NOT NULL
BEGIN
SET #ColumnName = ''
SET #TableName =
(
SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME))
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > #TableName
AND OBJECTPROPERTY(
OBJECT_ID(
QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)
), 'IsMSShipped'
) = 0
)
WHILE (#TableName IS NOT NULL) AND (#ColumnName IS NOT NULL)
BEGIN
SET #ColumnName =
(
SELECT MIN(QUOTENAME(COLUMN_NAME))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(#TableName, 2)
AND TABLE_NAME = PARSENAME(#TableName, 1)
AND DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar')
AND QUOTENAME(COLUMN_NAME) > #ColumnName
)
IF #ColumnName IS NOT NULL
BEGIN
INSERT INTO #Results
EXEC
(
'SELECT ''' + #TableName + '.' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630)
FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + #ColumnName + ' LIKE ' + #SearchStr2
)
END
END
END
SELECT ColumnName, ColumnValue FROM #Results
END
Now, I need to alter this to allow me to search for a list of control characters:
'%['
+ CHAR(0)+CHAR(1)+CHAR(2)+CHAR(3)+CHAR(4)
+ CHAR(5)+CHAR(6)+CHAR(7)+CHAR(8)+CHAR(9)
+ CHAR(10)+CHAR(11)+CHAR(12)+CHAR(13)+CHAR(14)
+ CHAR(15)+CHAR(16)+CHAR(17)+CHAR(18)+CHAR(19)
+ CHAR(20)+CHAR(21)+CHAR(22)+CHAR(23)+CHAR(24)
+ CHAR(25)+CHAR(26)+CHAR(27)+CHAR(28)+CHAR(29)
+ CHAR(30)+CHAR(31)+CHAR(127)
+ ']%',
Now the procedure as it stands won't allow me to use this as a search string, and it won't search correctly even using a single control character e.g. CHAR (28)
USE [DBNAME]
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[SearchAllTables]
#SearchStr = N'CHAR (28)'
SELECT 'Return Value' = #return_value
GO
Removing the N'' from the #SearchStr in the example above results in the error message:
Incorrect syntax near '28'
Can anyone help with a way of adapting this procedure to allow the search of control characters?
I would opt for a dynamic CharIndex(). Consider the following
Declare #ColumnName varchar(25)='[SomeField]'
Declare #SearchFor nvarchar(max) ='CHAR(0),CHAR(1),CHAR(2),CHAR(3),CHAR(4),CHAR(5),CHAR(6),CHAR(7),CHAR(8),CHAR(9),CHAR(10),CHAR(11),CHAR(12),CHAR(13),CHAR(14),CHAR(15),CHAR(16),CHAR(17),CHAR(18),CHAR(19),CHAR(20),CHAR(21),CHAR(22),CHAR(23),CHAR(24),CHAR(25),CHAR(26),CHAR(27),CHAR(28),CHAR(29),CHAR(30),CHAR(31),CHAR(127)'
Set #SearchFor = 'CharIndex('+Replace(#SearchFor,',',','+#ColumnName+')+CharIndex(')+','+#ColumnName+')'
So Your Dynamic where would look something like this
' WHERE ' + #SearchFor + '>0'
Just for illustration, the #SearchFor string would look like this
CharIndex(CHAR(0),[SomeField])+CharIndex(CHAR(1),[SomeField])+...+CharIndex(CHAR(31),[SomeField])+CharIndex(CHAR(127),[SomeField])
It looks like QUOTENAME is what is breaking things for you. When you try to use certain characters - such as char(0) - it returns NULL. Because of this, you are probably better off manually putting the single quotes yourself.
This means you would want to change this part:
INSERT INTO #Results
EXEC
(
'SELECT ''' + #TableName + '.' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630)
FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + #ColumnName + ' LIKE ' + #SearchStr2
)
to this:
INSERT INTO #Results
EXEC
(
'SELECT ''' + #TableName + '.' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630)
FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + #ColumnName + ' LIKE ''' + #SearchStr + ''' -- Note the use of #SearchStr (Not #SearchStr2) and the additional quotes to wrap your search string in.
)
Which should allow you to use your %[...]% pattern matching syntax.
Concerns:
Performance
As you probably know, wildcards (%) at the beginning and end of the argument prevent your SARG from using any indexes at all (even if it claims to use an INDEX SCAN) as SQL Server has no idea where the values will be. In a worst case scenario, it might even look in the wrong areas!
More grievous, the last EXEC statement you fire off will make SQL Server run through hoops. Despite what you might think, SQL Server initializes variables at the time of execution. Meaning, the optimizer will be running with its bed-clothes still on while it is in the middle of executing the query plan and may end up changing several times!
An example of what might be unleashed occurred on one of my DBs a
month ago, where a terrible new plugin ran a simple query looking for
one row with just two badly parameterized predicates on a large table of 1
million rows. Yet, the Optimizer swallowed up trillions of IOs in a
matter of seconds (the query came and went too fast for a governor)
and sent 2 billion rows PER QUERY through the network.
Tragically, the issue was zombied that day, and with just 500 one-row
result sets in my database running repeatedly, it brought down our
server.
Isolation and Transactions
Guessing haphazardly, expect to have locking issues and swallowed up resources. Major operations like UPDATES, and REINDEXING, and ALTER statements will either be forced to wait or kick your query to the curbside. Even using READ UNCOMMITTED will not save you from some blocking issues.
A New Approach
All of those characters you listed are neither letters nor numbers, but meaningless garbage (to SQL Server) that flows in from a front end application. I noticed you excluded Microsoft System Tables, so where does your data flow come from and how is it disseminated throughout the database? Who is at fault? How does the system, user, and designer play a role in the mess?
Is this Server an OLTP or READ heavy? Does your org not have a capable SSIS, ETL system to prevent garbage from wreaking havoc on your server?
Database Constraints
Assuredly, what reason does your application fail to pre-cleanse the data before sending it? And when it does get to the database level, why can we not use both the DATA TYPE and TABLE CONSTRAINTS to our advantage? Simple solutions like using DATE instead of VARCHAR for storing dates, adding normalization instead of storing blobs to isolate the read-heavy tables from write-heavy can spell wonders to improvement.
Admittingly, using CHECK CONSTRAINTS can lead to an exponential degradation of performance on your INSERT statements, so you may need to think about the larger impact.
Preventative vs Prescriptive
Ostensibly, I could write a query that would solve your current question (encapsulating EXEC statements in another Stored Proc enables proper parameter sniffing), we need to ask more and write less code. Your Procedure is terrible now and will always be, even if we window-dress. It masks the real issue of how those control characters got there in the first place and forces expensive queries on your poor system.
How relationally your tables work, normalization, cardinality should mean something to you so you can discriminate between not only types of tables but those specific columns they possess. Your current trouble would be disastrous on many of my databases, which can reach 1.5+ Terabytes in size
The more you gather your requirements, the better your answer will be. Heck, even setting up a database entirely for ETL would be superior than your current solution. And even if you still end up running a similar query, at least you will have shortened your list of columns and tables to a minute, understandable list instead of blindly inflicting pain on everyone in your company.
Best of wishes!

Creating A Login and Mapping to ALL Databases at Once [duplicate]

This question already has answers here:
SQL Server - Give a Login Permission for Read Access to All Existing and Future Databases
(2 answers)
Closed 9 years ago.
I'm trying to execute the following tsql
USE [master]
GO
CREATE LOGIN UserTest WITH Password = 'hi'
GO
sp_msforeachdb #command1= "CREATE USER [UserTest] FOR LOGIN [UserTest]"
Getting the following error repeatedly (for each DB):
Msg 15023, Level 16, State 1, Line 1
User, group, or role 'UserTest' already exists in the current database.
Is my understanding of sp_msforeachdb incorrect? Basically I'm trying to Create 'UserTest' as a user on each database from the existing login 'UserTest' (User Mapping) without having to go write code for each database individually. Am I approaching this task the wrong way?
Somewhat of a beginner in TSQL. Just trying to understand the language more thoroughly
Please don't use sp_msforeachdb. It is undocumented, unsupported, and has a bug that I've pointed out many times where it can actually skip databases. I've written two alternatives:
Making a more reliable and flexible sp_MSforeachdb
Execute a Command in the Context of Each Database in SQL Server
What you should do is use one of these superior replacements, or just create the command yourself. You should also check if the user already exists instead of just throwing a potential error over the wall at SQL Server.
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'
IF NOT EXISTS (SELECT 1 FROM '
+ QUOTENAME(name) + '.sys.database_principals
WHERE name = N''UserTest'')
EXEC sys.sp_executesql N''USE ' + QUOTENAME(name)
+ ';CREATE USER UserTest FOR LOGIN UserTest;'';'
FROM sys.databases
WHERE database_id > 4;
PRINT #sql; -- just to debug the first 8K
-- EXEC sys.sp_executesql #sql;
-- uncomment the above line when you're happy with the output
You could also add error handling there, e.g.
DECLARE #sql NVARCHAR(MAX);
SET #sql = N'';
SELECT #sql = #sql + N'
IF NOT EXISTS (SELECT 1 FROM '
+ QUOTENAME(name) + '.sys.database_principals
WHERE name = N''UserTest'')
BEGIN
BEGIN TRY
EXEC sys.sp_executesql N''USE ' + QUOTENAME(name)
+ ';CREATE USER UserTest FOR LOGIN UserTest;'';
END TRY
BEGIN CATCH
PRINT ''Did not work for ' + name + ';''
END CATCH
END'
FROM sys.databases
WHERE database_id > 4;
PRINT #sql;
-- EXEC sys.sp_executesql #sql;
The ? value is used as a placeholder for the database name, so your example would be:
sp_msforeachdb #command1= 'USE [?]; CREATE USER [UserTest] FOR LOGIN [UserTest];'
Please be aware of pitfalls documented here when working with with the undocumented stored procedure.

Resources