Why is this batch file not executing automatically? - batch-file

Basically I have a batch file which calls a SQL Server 2008 R2 stored procedure that interogates that DB for fragmentated tables over 5% fragmented and outputs the report to a text file.
The batch file is set as a Task Scheduler job to run overnight.
In Task Scheduler If I right click the task and select run immediately it executes fine, the script is in turn run on SQL Server and the output is redirected to a text file, everything is working as desired at this point, the problem is when I automate it to trigger overnight.
set local
REM Preparing Timestamp Information
set year=%date:~6,4%
set month=%date:~3,2%
set day=%date:~0,2%
set hour=%time:~0,2%
REM Replace leading space with zero
if “%hour:~0,1%” ==” ” set hour=0%hour:~1,1%
set minute=%time:~3,2%
set seconds=%time:~6,2%
set FILENAMEANDPATH= c:\DBMaintenanceLogs\SP_InspectorLog_%day%-%month%-%year%_%hour%-%minute%-%seconds%.log
sqlcmd -S .\SQLEXPRESS -E -Q "EXEC sp_DBIndexFragmentationInspector #IndexFragmentationPercentage=5" -d MyDBName -o %FILENAMEANDPATH%
The problem is that when left alone the Task Scheduler job will not run and the task says completed successfully with return code 255?
The Task is set to run using the system administrator account.
P.S. The Stored Procedure is:
USE [MyDBName]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[sp_DBIndexFragmentationInspector]
#IndexFragmentationPercentage INT
AS
BEGIN
SELECT
OBJECT_NAME(A.[object_id]) as 'TableName',
B.[name] as 'IndexName',
A.[index_type_desc],
A.[avg_fragmentation_in_percent]
FROM
sys.dm_db_index_physical_stats(db_id(),NULL,NULL,NULL,'LIMITED') A
INNER JOIN
sys.indexes B ON A.[object_id] = B.[object_id] and A.index_id = B.index_id
where [avg_fragmentation_in_percent] > #IndexFragmentationPercentage
order by TableName
END

I've been confronted with exactly the same error. For me, the solution was to add exit 0 at the end of the script. I don't understand why but it worked.

Related

Execute dynamic query and print to file

I have a script with dynamic query. I want to execute the query and output its result to a file. I can't seem to figure out how to output result of an "execute" statement.
Sample code below.
declare #sql_text varchar(300)
select #sql_text = select 1
exec (#sql_text) > output.txt
To give more context. My actual script would be looping through the dynamic query and output to different files (dynamic filename as well).
You set the output file via the -o parameter to the isql client to execute the SQL. This will send the output to a file from any SQL be that normal or dynamic SQL.
So put the SQL in an input file and then run
isql -U user - P password -S -i input_filename -o output.txt
You can't call directly to a operating system file from within ASE itself without enabling xp_cmdshell which is a potential security issue (as it allows O/S commands to be run as the user running the Sybase dataserver) and is therefore prohibited in most sites.

Run script in SQLMode changing variables

I have this code in a script file:
sqlcmd -E -I -S MyServerName -d MyDb1 -i D:\Updates\script.sql -o D:\Result\MyDb1.txt
sqlcmd -E -I -S MyServerName -d MyDb2 -i D:\Updates\script.sql -o D:\Result\MyDb2.txt
sqlcmd -E -I -S MyServerName -d MyDb3 -i D:\Updates\script.sql -o D:\Result\MyDb3.txt
I have more dbs to update but this is just an example, I have a .bat file that reads each sentence and execute the script.sql into each db, this works fine but now I'm generating scripts using SQL Data tools so visual studio generates the script for me, the problem is that it creates variables and put in a variable the db name, I need to be able to update that variable each time it goes to each db and run the script in sql mode.
This is an example of the script.sql for MyDb1:
/*
Deployment script for MyDb1
This code was generated by a tool.
Changes to this file may cause incorrect behavior and will be lost if
the code is regenerated.
*/
GO
SET ANSI_NULLS, ANSI_PADDING, ANSI_WARNINGS, ARITHABORT, CONCAT_NULL_YIELDS_NULL, QUOTED_IDENTIFIER ON;
SET NUMERIC_ROUNDABORT OFF;
GO
:setvar DictionaryData "DictionaryData"
:setvar DatabaseName "MyDb1"
:setvar DefaultFilePrefix "MyDb1"
GO
:on error exit
GO
/*
Detect SQLCMD mode and disable script execution if SQLCMD mode is not supported.
To re-enable the script after enabling SQLCMD mode, execute the following:
SET NOEXEC OFF;
*/
:setvar __IsSqlCmdEnabled "True"
GO
IF N'$(__IsSqlCmdEnabled)' NOT LIKE N'True'
BEGIN
PRINT N'SQLCMD mode must be enabled to successfully execute this script.';
SET NOEXEC ON;
END
GO
USE [$(DatabaseName)];
GO
PRINT N'Altering [cimpl].[LanguageView]...';
GO
ALTER VIEW [dbo].[LanguageView]
AS
SELECT l.LanguageLabelId, ll.[Name]
FROM [$(DictionaryData)].[dict].[Language] l
INNER JOIN [$(DictionaryData)].[dict].[LanguageType] lt
ON l.[LanguageTypeId] = lt.[LanguageTypeId]
INNER JOIN [$(DictionaryData)].[dict].[LanguageLabel] ll
ON ll.[LanguageLabelId] = l.[LanguageLabelId]
LEFT JOIN [cimpl].[LanguageConfiguration] lc
ON (lc.[LanguageLabelId] = l.[LanguageLabelId])
AND lc.[LanguageTypeId] = lt.[LanguageTypeId]
AND lc.[Active] = 1
WHERE l.Active = 1
GO
The script is much longer, but as an example, you can see the code is generated automatically but for MyDb1, now I need to run it on MyDb2 and MyDb3
How can I replace dynamically the sentence:
:setvar DatabaseName "MyDb1"
by:
:setvar DatabaseName "MyDb2" and :setvar DatabaseName "MyDb3"
at the time is running the sqlcmd?

SQL Server sqlcmd execute os command in script

In SQL Server 2016, I am executing a SQL script through SQLCMD like this:
SQLCMD -H XXXXXX,1433 -U username -P password -d mydatabase
-v varMDF="testing" -i "Script.sql" -o "DATA.txt"
and in Script.sql, I want to echo some text to the console, just to see the progress. I have a while loop in the script and executing the command
echo I am in sql script
as shown here:
OPEN tab_cursor
FETCH NEXT FROM tab_cursor INTO #tablename
WHILE ##FETCH_STATUS = 0
BEGIN
!!echo i am in sql script
PRINT #tablename
FETCH NEXT FROM tab_cursor INTO #tablename
END
CLOSE tab_cursor
DEALLOCATE tab_cursor
The problem is, it display the line "i am in sql script" only once in console but I could see many entries for tablename in my output file. Please help to solve this issue or suggest if there is any other way to do this.
Thanks
I would try the following solutions in order:
1) Look into BCP; it might allow you to see what you are doing much more effectively, and depending on the size of your output file it may be significantly faster. (1b : look into SSIS, even though it's a huge pain)
2) putting a SQLCMD execution inside of Script.sql that did the data push to the file, and having the PRINT statement work as normal without a -o. (NOTE: If this is a Complicated Stored Procedure, why aren't you writing a Complicated Stored Procedure?)
3) Monkeying with server monitoring and profiler. This would be for debugging purposes only, if that's why you need the output.
Generally, it sounds to me like the source of your problem is that you're using the wrong tool for the job. If you want lots of output from SQLCMD on process status, you're probably using it where you should be using BCP, which is designed for doing exports programmatically. SQLCMD isn't all that great an interface for running complicated scripts, in my experience; it needs fire-and-forget.

Combine command line and T-SQL - SQL Server

I am trying to execute some sqlcmd through T-SQL on SQL Server 2008. There is a part of my code where I am checking a data file size and if that data file size does not equal to 0, then start deleting the specific table so I can BCP in new data.
Below is my code that is not being executed:
SET #myVariable = '
SETLOCAL
FOR %%R IN (X:\Main Folder\Data\'+#databaseName+'_'+#tableName+'.dat) DO SET size=%%~zR
IF %size% NEQ 0 (
SQLCMD -E -S my-server-name -Q "DELETE FROM '+#databaseName+'.'+#schemaName+'.'+#tableName+';" >> X:\Main Folder\Log\Log.txt
)'
EXEC master..xp_cmdshell #myVariable
For some reason when I execute my stored procedure, the code above seems to be skipped because it does not shoot back any error messages.
EDIT: After re-adjusting the spacing and my code, #myVariable, gets executed now. However, it still does not work in regards that it still deletes the table even though the data file size = 0. However, when I hard code it within a batch file, it works perfectly fine. Any ideas?
You need to use single % in your for loop as you are not executing the code in a batch file (that requires %%), see this post for some further clarification. So your for loop should be:
FOR %R IN (X:\Main Folder\Data\'+#databaseName+'_'+#tableName+'.dat) DO SET size=%~zR
I think the problem is that you're not using quotes around your filenames. That top level directory has a space in it.
Jaco's answer looks correct and I'm sure it was part of the problem. You should probably initialize size just to be safe too:
SET #myVariable = '
SETLOCAL
SET size=0
FOR %R IN ("X:\Main Folder\Data\'+#databaseName+'_'+#tableName+'.dat") DO SET size=%~zR
IF %size% NEQ 0 (
SQLCMD -E -S my-server-name -Q "DELETE FROM '+#databaseName+'.'+#schemaName+'.'+#tableName+';" >> "X:\Main Folder\Log\Log.txt"
)'
EXEC master..xp_cmdshell #myVariable
Without the quotes the for loop is going to treat that its "set" (the terminology used in for /?) as two items separated by the space. If your current directory is X:\Main Folder\Data\ it would still work though since it sees the last one as a relative path to the .dat file and then still sets the right value on the last pass.
Why would you go 'down' to the command line at all? (there are reasons why xp_cmdshell is disabled by default)
Could you not simply loop over your tables (sys.tables WHERE name LIKE ...) and then
create a shadow-copy of the table (SELECT INTO)
BULK INSERT from the (expected) file into shadow-table (in a TRY..CATCH to handle situations where the file does not exist, or is empty, or is corrupt, ..
if there is data in the shadow-table, then DELETE the actual table records and move the data over
if there is no data in the shadow table then DELETE the actual table records (or leave them in if you assume a missing or empty bcp file means it will arrive later on and you're stuck with the current version for now)
DROP the shadow table again
Not sure if this is an option for you but since you are on SQL 2008 you should be able to use powershell command instead:
DECLARE #File varchar(100), #outputFile varchar(100)
DECLARE #cmd varchar(1000)
SELECT #File = 'path_to_file'
SELECT #outputFile = 'path_to_output_file'
SELECT #cmd = 'powershell.exe -command "if((Get-Item '''+#File+''').length -gt 0) {&sqlcmd.exe -E -S SERVERNAME -Q ''SELECT name FROM master.sys.databases ;'' -o '+#outputFile+'}"'
SELECT #cmd
exec master..xp_cmdshell #cmd
I've checked and it seems to be working depending on the file size.
I can't speak to your DOS command. I can however suggest using Ole Automation Procedures to get the file size. That way you would not have to rely on running batch commands.
First you need to enable Ole Automation Procedures on your SQL Server instance, as follows:
sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
GO
sp_configure 'Ole Automation Procedures', 1;
GO
RECONFIGURE;
GO
You only need to do this once.
Next is a script that gets the file size. The example assumes that there's a file called C:\Temp\testfile.txt. The script selects the size if the file exists, or selects 0 if it doesn't. You can take this script as an example to do what you want based on the size.
Here goes:
DECLARE #hr INT;
DECLARE #size INT;
DECLARE #obj_file INT;
DECLARE #obj_file_system INT;
DECLARE #file_name VARCHAR(100)='C:\Temp\testfile.txt';
-- Create a FileSystemObject. Create this once for all subsequent file manipulation. Don't forget to destroy this object once you're done with file manipulation (cf cleanup)
EXEC #hr = sp_OACreate 'Scripting.FileSystemObject', #obj_file_system OUT;
IF #hr<>0 GOTO __cleanup;
-- Get a handle for the file. Don't forget to release the handle for each file you get a handle for (see cleanup). The return will be different from 0 if the file doesn't exist
EXEC #hr = sp_OAMethod #obj_file_system, 'GetFile', #obj_file out, #file_name;
IF #hr<>0 GOTO __print_file_size;
-- Retrieve the file size.
EXEC sp_OAGetProperty #obj_file, 'size', #size OUT;
__print_file_size:
SELECT ISNULL(#size,0) AS file_size;
__cleanup:
EXEC sp_OADestroy #obj_file_system;
EXEC sp_OADestroy #obj_file;
You are using X:\ in your code. But the code is running under the service account for SQL Server. That account may not have x: available.
I would suggest using a UNC instead of a mapped drive. Also, make sure that your service is running under a domain account, and that the domain account has all required permissions to the UNC.
I realized that I can check the table count instead of a data file size by using this method:
SET #sqlCheck = 'SQLCMD -E -S ServerA -Q "IF (SELECT COUNT(*) FROM '+#databaseName+'.'+#schemaName+'.'+#tableName+') > 0 BEGIN DELETE FROM ServerB.'+#databaseName+'.'+#schemaName+'.'+#tableName+' END;"'
EXEC MASTER..xp_cmdshell #sqlcheck
You seems to know the names of the databases and tables already, so you can use the following, which basically does a DIR for the file you're looking for and checks if it's '0 bytes', if so it then does whatever you want. Things to note:
STRING TEMPLATES -- When building strings, I like to build a 'template' and then replace within the string. This is a good way to make sure you have the right number of quotes, parenthesis, etc. I did it twice here, once to build the DIR command and then again to build the TRUNCATE command.
TRUNCATE -- although not part if your question, you may want to use a TRUNCATE instead of DELETE FROM. If you had a million rows in your table, DELETE FROM may take 2 min to run, where as TRUNCATE will always take 0-seconds to run.
Your answer:
SET NOCOUNT ON;
DECLARE #DatabaseName VARCHAR(50) = 'database1'
DECLARE #TableName VARCHAR(50) = 'table1'
DECLARE #PathTemplate VARCHAR(50) = 'dir c:\temp\{#DatabaseName}_{#TableName}.txt'
SET #PathTemplate = REPLACE(#PathTemplate, '{#DatabaseName}', #DatabaseName);
SET #PathTemplate = REPLACE(#PathTemplate, '{#TableName}', #TableName);
DECLARE #FileNames AS TABLE (FileNames VARCHAR(100))
INSERT #FileNames (FileNames)
exec xp_cmdshell #PathTemplate
IF EXISTS ( SELECT 1 FROM #FileNames WHERE FileNames LIKE '%0 bytes')
BEGIN
PRINT 'No Content/Missing File'
END
ELSE BEGIN
DECLARE #SqlExc VARCHAR(500) = 'TRUNCATE TABLE [{#DatabaseName}].[dbo].[{#TableName}]'
SET #SqlExc = REPLACE(#SqlExc, '{#DatabaseName}', #DatabaseName);
SET #SqlExc = REPLACE(#SqlExc, '{#TableName}', #TableName);
PRINT #SqlExc
-- sp_executesql #SqlExc <-- Do this in production
END

Populating ms sql with sql files

I already have an existing database I am trying to keep up to date on a daily basis. I get a daily dump of sql files. The batch script below created and populated the database the first time I run it, but that doesn't work when I try to update the database with it.
`#echo off
ECHO %USERNAME% started the batch process at %TIME% >output.txt
for %%f in (*.sql) do (
sqlcmd.exe -S servername -E -d DatabaseName -i %%f >>output.txt
)
pause`
Is there a different command for updating a database with sql files?
This is the output i get.
HUTRC started the batch process at 9:55:12.25 Changed database context to 'master'. Msg 15416, Level 16, State 1, Server HUTRC1-HP, Procedure sp_dbcmptlevel, Line 67 Usage: sp_dbcmptlevel [dbname [, compatibilitylevel]] Valid values of the database compatibility level are 100, 110, or 120.
What the sql file looks like. It is very long. I just got the first few lines.
USE [master]
GO
IF NOT EXISTS (SELECT [name] FROM sys.databases WHERE name = N'Migration')
BEGIN
CREATE DATABASE [Migration] COLLATE SQL_Latin1_General_CP1_CI_AS
END
GO
EXEC dbo.sp_dbcmptlevel #dbname=N'Migration', #new_cmptlevel=90
The error message tells you precisely where to look for the problem and what it is (emphasis added):
Procedure sp_dbcmptlevel, Line 67 Usage: sp_dbcmptlevel [dbname [, compatibilitylevel]] Valid values of the database compatibility level are 100, 110, or 120.
Examining your SQL script for sp_dbcmptlevel shows that it uses a different value:
EXEC dbo.sp_dbcmptlevel #dbname=N'Migration', #new_cmptlevel=90
^^
You'll need to either edit the SQL script to a valid compatibility level, or downgrade your server version to the same version as the source server.

Resources