I want to dynamically create a Drop/Create Script. For that I can calculate the Create part as below.
SELECT definition FROM sys.sql_modules
My Query - can you suggest something for the Drop Stored Proc Part...
I am trying a Create/Drop Script for Stored Proc
DECLARE #proc NVARCHAR(511); -- presumably a parameter
SET #proc = N'dbo.sp_help_job';
DECLARE #sql NVARCHAR(MAX);
SELECT #sql = N'DROP PROCEDURE ' + #proc + ';
GO
' + definition
FROM sys.sql_modules
WHERE [object_id] = OBJECT_ID(#proc);
PRINT #sql;
-- EXEC sp_executesql #sql;
Comparisons of join vs. OBJECTPROPERTY method for Pankaj:
Click to enlarge:
Don't copy and try to use the OUTPUT from the second screen shot. Why on earth would you do this? This is SQL Server's internal representation of a parameterized version of the query. Let me paste the code so that you aren't doing unexplainable things with what I'm trying to show you:
SELECT OBJECT_NAME(m.[object_id]), m.[object_id]
FROM sys.all_sql_modules AS m
INNER JOIN sys.all_objects AS o
ON m.[object_id] = o.[object_id]
WHERE o.type = N'P';
SELECT OBJECT_NAME([object_id]), [object_id]
FROM sys.all_sql_modules
WHERE OBJECTPROPERTY([object_id], 'IsProcedure') = 1;
Run those and check the results in profiler. Am I still "wrong" Pankaj? Would you like to waste any more of everyone's time today?
Related
I have found examples where a stored procedure is dropped or altered in case if it exists.
But how do I write a T-SQL batch script that does strictly the following:
checks if the stored procedure exists:
if it exists, the script does nothing at all - I do not want to drop and rewrite or alter the existing procedure because it might have changes made by someone else. Also, the script should not abort with an error because it might be a part of a larger batch of changes.
if it doesn't exist, only then create a new procedure with my code
?
When creating a Stored Procedure, it can be the only statement in the batch. This means that something like the below will fail:
IF NOT EXISTS(SELECT 1 FROM sys.schemas s JOIN sys.procedures p ON s.schema_id = p.schema_id WHERE s.[name] = N'dbo' AND p.[name] = N'YourProcedure') BEGIN
CREATE PROC dbo.YourProcedure #TableName sysname AS
BEGIN
SELECT *
FROM sys.tables t
WHERE t.name = #TableName;
END;
END;
Therefore, if you want to check if the procedure exists first, and then create it if not, you have to use "dynamic" SQL (it's not really dynamic, as there's nothing dynamic in it):
IF NOT EXISTS(SELECT 1 FROM sys.schemas s JOIN sys.procedures p ON s.schema_id = p.schema_id WHERE s.[name] = N'dbo' AND p.[name] = N'YourProcedure') BEGIN
EXEC sys.sp_executesql N'CREATE PROC dbo.YourProcedure #TableName sysname AS
BEGIN
SELECT *
FROM sys.tables t
WHERE t.name = #TableName;
END;';
END;
This means that you will need to escape any single quotes in the Stored Procedure's definition when pasting it into the dynamic statement.
In below scenario, I'm trying to import hundreds of procedures into other database.
Solution: SQL Server
Source
SERVER: A
DATABASE: Apple
PROCEDURES: SP1, SP2, SP3 ... SP100
Destination
SERVER: B
DATABASE: Orange
First thing I did was find only non-existing procedures when compared to both databases.
To do so, I used INFORMATION_SCHEMA.ROUTINES from each database and compared in Excel.
After finding a list of procedures to be imported, I wanted to import all procedures at once.
However, if I create procedure in one-lined text, it will be saved one line which has zero visibility.
So, I used sp_helptext to copy by line for each. Then, created little query like below:
create table #proceduretext (runquery varchar(max))
insert into #proceduretext
exec sp_helptext 'SP1'
insert into #proceduretext select 'go'
insert into #proceduretext
exec sp_helptext 'SP2'
insert into #proceduretext select 'go'
insert into #proceduretext
exec sp_helptext 'SP3'
insert into #proceduretext select 'go'
.
.
.
insert into #proceduretext
exec sp_helptext 'SP100'
insert into #proceduretext select 'go'
Then do a select from temp table, paste result, run.
However, above was still insufficient.
Please help with below questions:
Is there a way to use INFORMATION_SCHEMA.ROUTINES for linked servers?
If there is an answer to question 1, how can I loop below query for all missing procedures?
insert into #proceduretext
exec sp_helptext 'SP100'
insert into #proceduretext select 'go'
You can do that with a linked server pretty easily. Here's an example I prepared for you. Run it from within the target database and change the [GREGT580] to whatever your linked server name is.
USE tempdb;
GO
-- Copying from [GREGT580] to local server
SET NOCOUNT ON;
DECLARE #MissingProcedures TABLE
(
MissingProcedureID int IDENTITY(1,1) PRIMARY KEY,
SchemaName sysname,
ProcedureName sysname,
ProcedureDefinition nvarchar(max)
);
INSERT #MissingProcedures
(
SchemaName, ProcedureName, ProcedureDefinition
)
SELECT s.[name], p.[name], sm.definition
FROM [GREGT580].AdventureWorks.sys.procedures AS p
INNER JOIN [GREGT580].AdventureWorks.sys.schemas AS s
ON p.schema_id = s.schema_id
INNER JOIN [GREGT580].AdventureWorks.sys.sql_modules AS sm
ON sm.object_id = p.object_id
WHERE NOT EXISTS (SELECT 1
FROM sys.procedures AS pl
INNER JOIN sys.schemas AS sl
ON sl.schema_id = pl.schema_id
AND sl.[name] = s.[name] COLLATE DATABASE_DEFAULT
AND pl.[name] = p.[name] COLLATE DATABASE_DEFAULT);
DECLARE #SchemaName sysname;
DECLARE #ProcedureName sysname;
DECLARE #ProcedureDefinition nvarchar(max);
DECLARE #Counter int = 1;
WHILE #Counter < (SELECT MAX(MissingProcedureID) FROM #MissingProcedures AS mp)
BEGIN
SELECT #SchemaName = mp.SchemaName,
#ProcedureName = mp.ProcedureName,
#ProcedureDefinition = mp.ProcedureDefinition
FROM #MissingProcedures AS mp
WHERE mp.MissingProcedureID = #Counter;
PRINT #ProcedureDefinition; -- Change to EXEC (#ProcedureDefinition) to create
SET #Counter += 1;
END;
I made the code just print out the procedures, so you could test it. Change the PRINT line to the EXEC option shown when you want to actually created them. (Note that PRINT will truncate what it shows if they are long but EXEC will be fine).
Hope that helps you.
try using https://www.red-gate.com/ compare tool you can download a trail version. Works great
I can change the ownership of a single table using sp_changeobjectowner.
If I want to change ownership of all the objects in a database, should I write a stored procedure to iterate through each object or is there another way?
UPDATE
I also found that changing the default schema for my user solved the issue that was causing me to think I needed to change ownership of all the objects.
Try running this query and then just select all results and execute in separate query
select 'EXEC sp_changeobjectowner ''' + S.name + '.' + O.name + '' + ''', ''new_owner'''
from sys.all_objects O
inner join sys.schemas S
on O.schema_id = S.schema_id
where O.type in ('FN','IF','P','TF','U','V', 'TT', 'TF')
If you are looking to change owners of just tables you can use the undocumented sp_MSforeachtable like this:
sp_MSforeachtable #command1="sp_changeobjectowner '?', 'new_owner'"
If you really need all objects then you'll need to iterate. One way (but not the only way) would be a cursor like this one:
DECLARE #currentObject nvarchar(517)
DECLARE #qualifiedObject nvarchar(517)
DECLARE #currentOwner varchar(50)
DECLARE #newOwner varchar(50)
SET #currentOwner = 'ASPNET'
SET #newOwner = 'dbo'
DECLARE alterOwnerCursor CURSOR FOR
SELECT [name] FROM dbo.sysobjects
WHERE
xtype in ('FN','IF','P','TF','U','V', 'TT', 'TF') --Modify list to add and remove object types*
OPEN alterOwnerCursor
FETCH NEXT FROM alterOwnerCursor INTO #currentObject
WHILE ##FETCH_STATUS = 0
BEGIN
SET #qualifiedObject = CAST(#currentOwner as varchar) + '.' + CAST(#currentObject as varchar)
EXEC sp_changeobjectowner #qualifiedObject, #newOwner
FETCH NEXT FROM alterOwnerCursor INTO #currentObject
END
CLOSE alterOwnerCursor
DEALLOCATE alterOwnerCursor
Cursor above is an untested and modified version of this.
*Note: The cursor query refers to a list of xtypes. Please review this for the full list.
I am trying to write this query to find all tables with specific column with some specific value. This is what I've done so far -
EXEC sp_MSforeachtable
#command1='
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=PARSENAME("?",2) AND TABLE_NAME=PARSENAME("?",1) AND COLUMN_NAME="EMP_CODE")
BEGIN
IF (SELECT COUNT(*) FROM ? WHERE EMP_CODE="HO081")>0
BEGIN
SELECT * FROM ? WHERE EMP_CODE="HO081"
END
END
'
I hope my intensions are clear, I just want to select only those tables where the column EMP_CODE is present and in those tables I want to select those rows where EMP_CODE='HO081'.
Edit -
Now it stands like this. But I'm not able to replace #EMPCODE variable in the query.
DECLARE #EMPCODE AS VARCHAR(20)
SET #EMPCODE='HO081'
EXEC sp_MSforeachtable
#command1='
DECLARE #COUNT AS INT
SELECT #COUNT=COUNT(*) FROM ? WHERE EMP_CODE='''+#EMPCODE+'''
IF #COUNT>0
BEGIN
PRINT PARSENAME("?",1)+'' => ''+CONVERT(VARCHAR,#COUNT)+'' ROW(S)''
--PRINT ''DELETE FROM ''+PARSENAME("?",1)+'' WHERE EMP_CODE='''''+#EMPCODE+'''''''
END
',#whereand='AND O.ID IN (SELECT OBJECT_ID FROM SYS.COLUMNS C WHERE C.NAME='''+#EMPCODE+''')'
You know how sp_MSforeachtable is undocumented, and may go away at any time/be modified?
Well, if you're happy to ignore that, it has another parameter called #whereand, which is appended to the WHERE clause of the internal query that is being used to find the tables (and should start with an AND).
You also have to know that there's an alias, o against sysobjects, and a second alias syso against sys.all_objects.
Using this knowledge, you might craft your #whereand parameter as:
EXEC sp_MSforeachtable
#command1='...',
#whereand='AND o.id in (select object_id from sys.columns c where c.name=''EMP_CODE'')'
You can now also simplify your command1, since you know it will only be run against tables containing an EMP_CODE column. I'd probably take out the COUNT(*) condition also, since I don't see what value it's adding.
Updated based on your further work, and tested against one table:
DECLARE #EMPCODE AS VARCHAR(20)
SET #EMPCODE='HO081'
declare #sql nvarchar(2000)
set #sql = '
DECLARE #COUNT AS INT
SELECT #COUNT=COUNT(*) FROM ? WHERE EMP_CODE='''+#EMPCODE+'''
IF #COUNT>0
BEGIN
PRINT PARSENAME("?",1)+'' => ''+CONVERT(VARCHAR,#COUNT)+'' ROW(S)''
--PRINT ''DELETE FROM ''+PARSENAME("?",1)+'' WHERE EMP_CODE='''''+#EMPCODE+'''''''
END
'
EXEC sp_MSforeachtable
#command1=#sql,#whereand='AND O.ID IN (SELECT OBJECT_ID FROM SYS.COLUMNS C WHERE C.NAME=''EMP_CODE'')'
(I've reverted the #whereand to query for EMP_CODE, since you don't want to replace the value there).
The issue is that, you can pass parameters to a stored procedure, or literals, but you can't perform calculations/combining actions between them - so I moved the construction of the sql statement out into a separate action.
I guess you get an error of some kind, perhaps Invalid column name 'EMP_CODE'?
It's because the code is compiled before you check for the column.
You could do like this instead.
EXEC sp_MSforeachtable
#command1='
IF EXISTS (SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=PARSENAME("?",2) AND TABLE_NAME=PARSENAME("?",1) AND COLUMN_NAME="EMP_CODE")
BEGIN
EXEC(''
IF (SELECT COUNT(*) FROM ? WHERE EMP_CODE="HO081")>0
BEGIN
SELECT * FROM ? WHERE EMP_CODE="HO081"
END
'')
END
'
So far in the website I have only found stored procedures to list all the tables in a database, but what I really want to know is how to create a stored procedure in SQL Server to display all data of all tables in a specific database.
I don't want to join the tables and then display a huge table, I want to create some kind of loop that takes the first table of the database, performs a
SELECT * FROM <Table>
query, and then continues with the next table of the database and so on until all tables are displayed.
I know it should be easy but I have never created a stored procedure before so I don't know how to use the variables or go through the tables.
Thank you
Something like this should work:
CREATE Procedure [dbo].[procSelectAllFromAllTables]
AS
DECLARE #table nvarchar(500)
DECLARE #sql nvarchar(520)
DECLARE CursorSelect CURSOR FOR
select table_name from INFORMATION_SCHEMA.tables where table_name not like 'sys%'
OPEN CursorSelect
FETCH NEXT FROM CursorSelect
INTO #table
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = 'select * from ' + #table
exec(#sql)
FETCH NEXT FROM CursorSelect
INTO #table
END
CLOSE CursorSelect
DEALLOCATE CursorSelect
RETURN
learn how to create a stored procedure
learn how to use variables in a stored procedure
Get a list of all the table names
use a cursor to create a while loop on a list of all table names
use dynamic sql on 'select * from ' + #tablename
As others have said, this is a silly idea from a practical standpoint, but as an academic exercise, it is fairly simple if you use a bit of dynamic sql and COALESCE(). No cursors or loops required.
DECLARE #SQL VARCHAR(MAX)
SELECT #SQL = COALESCE(#SQL, '') + ' SELECT * FROM ' + s.name + '.' + t.name
FROM sys.tables t INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
EXEC #SQL