Static Variable in TSQL? - sql-server

I have bunch of servers that I need their information such as servername and instance name and add all these information into a table in Server X. I am using sqlcmd to connect to server X, here is a simple code:
declare #servername varchar(30)
set #servername=##servername
:connect to X
insert into X.table values(#servername)
so i thought #servername gets the current servername and then it goes to line 3 and add it to new server(server X), but I was wrong and once it connects to server X it retrieves the data (#servername) from this server. meaning that it can't hold the data. So my question is how to hold the data from old server and not overwrite it, something like Static variable in java.

SQLCMD happens before processing the script, so you can't use it for your intended purpose.
Have you tried using a linked server and the EXEC AT command?
EXEC ('INSERT INTO MyTable VALUES (?)', #servername) AT [LinkedServer]
http://www.mssqltips.com/sqlservertip/1757/dynamic-sql-execution-on-remote-sql-server-using-exec-at/
https://msdn.microsoft.com/en-us/library/ms188332.aspx

If you have the server names before hand, you can use :setvar myvariable variablevalue to set a variable that you can access in your sqlcmd script via $(myvariable).
In this situation I would probably do a delimited string of server names to parse.
Take a look at the MSDN https://msdn.microsoft.com/en-us/library/ms188714.aspx , for more examples.

Related

Using variables in TSQL and keep formatting in SQL Server Management Studio

I'm creating some views with a lot of references to tables in another database.
At some point the other database needs to change.
I want to make it easy for the next developer to change the scripts to use another database.
This obviously work like it should:
CREATE VIEW ViewName
AS
SELECT *
FROM AnotherDatabase.SchemaName.TableName;
But when I do:
DECLARE #DB CHAR(100)
SET #DB = 'AnotherDatabase'
GO
CREATE VIEW ViewName
AS
SELECT *
FROM #DB.SchemaName.TableName;
I get the error:
Msg 137, Level 15, State 2, Procedure ViewName, Line 3
Must declare the scalar variable "#DB".
I could do something like:
DECLARE #SQL ...
SET #SQL = ' ... FROM ' + #DB + ' ... '
EXEC (#SQL)
But that goes against the purpose of making it easier for the next developer - because this dynamic SQL approach removed the formatting in SSMS.
So my question is: how do I make it easy for the next developer to maintain T-SQL code where he needs to swap out the database reference?
Notes:
I'm using SQL Server 2008 R2
The other database is on the same server.
Consider using SQLCMD variables. This will allow you to specify the actual database name at deployment time. SQL Server tools (SSMS, SQLCMD, SSDT) will replace the SQLCMD variable names with the assigned string values when the script is run. SQLCMD mode can be turned on for the current query windows from the menu option Query-->SQLCMD mode option.
:SETVAR OtherDatabaseName "AnotherDatabaseName"
CREATE VIEW ViewName AS
SELECT *
FROM $(OtherDatabaseName).SchemaName.TableName;
GO
This approach works best when SQL objects are kept under source control.
When you declare variables, they only live during the execution of the statement. You can not have a variable as part of your DDL. You could create a bunch of synonyms, but I consider that over doing it a bit.
The idea that your database names are going to change over time seems a bit out of the ordinary and conceivably one-time events. However, if you do still require to have the ability to quickly change over to point to a new database, you could consider creating a light utility directly in SQL to automatically generate the views to point to the new database.
An implementation may look something like this.
Assumptions
Assuming we have the below databases.
Assuming that you prefer to have the utility in SQL instead of building an application to manage it.
Code:
create database This;
create database That;
go
Configuration
Here I'm setting up some configuration tables. They will do two simple things:
Allow you to indicate the target database name for a particular configuration.
Allow you to define the DDL of the view. The idea is similar to Dan Guzman's idea, where the DDL is dynamically resolved using variables. However, this approach does not use the native SQLCMD mode and instead relies on dynamic SQL.
Here are the configuration tables.
use This;
create table dbo.SomeToolConfig (
ConfigId int identity(1, 1) primary key clustered,
TargetDatabaseName varchar(128) not null);
create table dbo.SomeToolConfigView (
ConfigId int not null
references SomeToolConfig(ConfigId),
ViewName varchar(128) not null,
Sql varchar(max) not null,
unique(ConfigId, ViewName));
Setting the Configuration
Next you set the configuration. In this case I'm setting the TargetDatabaseName to be That. The SQL that is being inserted into SomeToolConfigView is the DDL for the view. I'm using two variables, one {{ViewName}} and {{TargetDatabaseName}}. These variables are replaced with the configuration values.
insert SomeToolConfig (TargetDatabaseName)
values ('That');
insert SomeToolConfigView (ConfigId, ViewName, Sql)
values
(scope_identity(), 'dbo.my_objects', '
create view {{ViewName}}
as
select *
from {{TargetDatabaseName}}.sys.objects;'),
(scope_identity(), 'dbo.my_columns', '
create view {{ViewName}}
as
select *
from {{TargetDatabaseName}}.sys.columns;');
go
The tool
The tool is a stored procedure that takes a configuration identifier. Then based on that identifier if drops and recreates the views in the configuration.
The signature for the stored procedure may look something like this:
exec SomeTool #ConfigId;
Sorry -- I left out the implementation, because I have to scoot, but figured I would respond sooner than later.
Hope this helps.

Query multiple SQL Servers in One query

I am trying to set up a query that will grab the Windows version of each SQL Server I have and throw it into a table. I have the query that grabs the version but I think there is a better way to get the information needed than connecting to each indiviual server one by one to run the query. I am not opposed to using XP_cmdshell I am just wondering if there is a way to run one query that will grab the version of each Windows OS I have on the sql servers. Also I do have a list of servers to use.
EDIT: I know I wil have to in some way touch each server. I would just like a way to get around having the RDP to each server and open SQL server and query it or haveing to connect to each server within sql server and running the query one by one.
All I have right now code wise is a simple INSERT STATEMENT I get here and I draw a blank on where to go next of even hoe to tackle the problem. The table below has two columns ServerName and Win_Ver ServerName is already populated with all the servers I have.
INSERT INTO mtTable
(Win_Ver)
SELECT ##Version
Given that:
there are "roughly 112 servers"
the servers being a "mixture between 2008 - 2012"
"There is table we are keeping with all of our DB server Statistics."
and "We periodically get asked to produce these statistics"
one option is to cycle through that table of servers using a cursor, and for each one, execute xp_cmdshell to call SQLCMD to run the query. You would use a table variable to capture the result set from SQLCMD as returned by xp_cmdshell. Something like:
DECLARE #ServerName sysname,
#Command NVARCHAR(4000),
#CommandTemplate NVARCHAR(4000);
DECLARE #Results TABLE ([ResultID] INT IDENTITY(1, 1) NOT NULL, [Result] NVARCHAR(4000));
SET #CommandTemplate = N'SQLCMD -S {{SERVER_NAME}} -E -h-1 -Q "PRINT ##VERSION;"';
DECLARE srvrs CURSOR LOCAL READ_ONLY FAST_FORWARD
FOR SELECT [ServerName]
FROM ServerStats;
OPEN srvrs;
FETCH NEXT
FROM srvrs
INTO #ServerName;
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #Command = REPLACE(#CommandTemplate, N'{{SERVER_NAME}}', #ServerName);
INSERT INTO #Results ([Result])
EXEC xp_cmdshell #Command;
-- Get results via SELECT [Result] FROM #Results ORDER BY [ResultID];
-- Do something with the data in #Results
DELETE FROM #Results;
FETCH NEXT
FROM srvrs
INTO #ServerName;
END;
CLOSE srvrs;
DEALLOCATE srvrs;
And it wouldn't hurt to throw in a TRY / CATCH in there :-).
Even if not the most ideal of solutions, it is at least doesn't require adding 112 Linked Servers, and is dynamic and will adjust to servers being added and removed.
In SQL Server you are able to create a Linked Server that you can query from another server.
On the server you wish to write the query in:
Open the Object Explorer
Go to Server Objects
Right Click Linked Servers and add a New Linked Server
Add the Name of your networked server, select SQL server and make sure to define security roles.

Configure SQL Server startup parameters from batch file

I need to add start-up trace flags to a SQL Service from a batch file. I have struggled to work out how to do this, and have finally come-up with the following, slightly clumsy approach, of using undocumented TSQL procedure and then calling it with SQLCMD. The problem is that it throws an error: RegCreateKeyEx() returned error 5, 'Access is denied.' However, another script on the same server, allows me to change other registry values, such as TCP Port. Any idea what I'm doing wrong, or a more elegant way of doing it?
DECLARE #VALUE nvarchar(200)
DECLARE #Key nvarchar(2000)
SET #VALUE = '-T1118 -3604 -E'
SET #Key = 'SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.' + (select SUBSTRING(##SERVERNAME,CHARINDEX('\',##SERVERNAME)+1,LEN(##SERVERNAME)-1)) + '\MSSQLServer\Parameters'
EXECUTE master..xp_regwrite
'HKEY_LOCAL_MACHINE',
#Key,
'SQLArg3',
'REG_SZ',
#VALUE
The expression to create the #Key variable elavuates to: SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL11.MYINSTANCE\MSSQLServer\Parameters
which seems correct to me.
The registry key for the default instance of SQL Server is:
SET #Key = 'SOFTWARE\Microsoft\Microsoft SQL Server
\MSSQL11.MSSQLSERVER\MSSQLServer\Parameters';
(I wrapped the key above for readability - you need this all on one line.)
Unless you have named instances, you can use this #Key instead of yours. If you have named instances on the servers, you'd need to inspect SOFTWARE\Microsoft\Microsoft SQL Server\InstalledInstances to obtain the name(s) of the instance(s) on the server.
I imagine there is more elegant way of doing this using PowerShell.

Equivalent to sp_executesql in Sybase

What's the equivalent to sp_executesql of Sql Server in sybase.
I think exec() is what you're looking for. See http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_15.0.commands/html/commands/commands56.htm.
#jasir: the limitation to 255 characters results from your procedure definition. Isn't possible to extend this limit by defining e.g. varchar(1024)?
ASE Refrence Manual 15.7 (http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.infocenter.dc36272.1572/html/commands/X30305.htm)
states for command execute:
string is a literal string containing part of a Transact-SQL command
to execute. There are no restrictions to the number of characters
supplied with the literal string.
However, sometimes a system on top of sybase (e.g. Kondor+, a product for financial markets uses ASE 15.x as DB) may set limits. In this Kondor+ it is not possible to use
exec( #SQLQuery)
in its so called OpenReport (a stored procedure), if the variable #SQLQuery exceeds 256 characters.
For older ASE, you can use this workaround:
There is stored procedure sp_remotesql.
You can use it to run query on local server too, you just have to local server to servers:
sp_addserver local, NULL, <servername>
Where <servername> is name of local server (from sql.ini).
You can add shortcut procedure for running sql:
create procedure sp_exec_dynsql #p_cmd varchar(255)
as
begin
exec sp_remotesql "local", #p_cmd
end
Unfortunatelly, you are limited to 255 characters in your sql.

In SQL server is there any way to get the 'use database' command to accept a variable

I would like to write a 'generic' script to operate on a number of databases. The 'use databasename' commands will be distributed in the body of the script. I would rather show the database name information at the top of the script. So ideally:
declare #db varchar(100)
set #db = 'data_in'
use #db
This fails. So perhaps
declare #db varchar(100)
set #db = 'data_in'
exec ('use '+#db)
This executes, but presumably changes the database only in the context of the query in the string.
So is there any other way without placing the whole script into a string and executing it that way ie. a cure worse than the disease?
Check out Scripting Variables in SQLCMD.
This enables you to put variables into your scripts in the form:
USE $(db1_name)
...some code...
USE $(db2_name)
...some code...
And have the variable values interpolated from environment variables, parameters provided at runtime, or hard coded value assignments.
Ed already mentioned SQLCMD, a very good choice for scripting.
If that doesn't do it for you, and you have sa rights on the server, and you don't mind the risk of using undocumented features and modifying the master database, you might look into user-defined system stored procedures.
A user-defined system stored procedure (UDSSP) is created in the master database, prefixed with "sp_", and marked as a system object with the undocumented system proc sp_MS_marksystemobject (SQL2005).
It takes its database context from the current connection, or a three-part name if so called.
Sample invocation:
declare #db sysname
declare #sql nvarchar(max)
set #db = 'yourdatabase'
set #sql = 'exec '+quotename(#db)+'..sp_#yourproc'
exec (#sql)
Notes:
If you go this route, I strongly suggest using a unique prefix that sorts toward the top, like sp_#yourproc, rather than sp_yourproc, so you can find them again later, and other people know that they are something special.
Once the procedure is marked as system, it can't be updated. To make changes, you must drop, recreate and remark as system.
Don't do this unless you know what you are doing and have done a little more research. Don't do this if you are risk-averse. Don't do this if you don't have a development instance to test on first.
Backup the UDSSPs to file or CSV. A server upgrade can wipe them out.
You could NOT put a use statement in your script, and supply the database with the osql command (or whatever you're using) like this:
osql -S servername -d databasename -U username -P password -i script.sql
I think your presumption’s right; I had the same problem yesterday. I solved it by putting the commands into a single string. Why don’t you like that solution?

Resources