TSQL query to find un-used stored procedures - sql-server

I am trying to track down all stored procedures in a database that have never been used, or that have not been used in many months.
I would like to find a query to show all the stored procedures that are not in use so that those stored procedures can be analyzed to determine if they can be removed.
I am familiar with sys.procedures, but don't know how to determine if a procedure is in use or not.
SELECT *
FROM sys.procedures;
Using SQL Server 2008 R2.
UPDATE UPDATE UPDATE
Using the query from Aaron Bertrand below, slightly modified, this is what I ended up using, and it was perfect.
SELECT p.*
FROM sys.procedures AS p
LEFT JOIN sys.dm_exec_procedure_stats AS s ON s.[object_id] = p.[object_id]
WHERE s.object_id IS NULL;
Thanks for the hlep.

DMVs will record stats for procedures, but they only possibly go as far back as the last restart (and often not that far, depending on how long a plan lives in cache):
SELECT * FROM sys.dm_exec_procedure_stats AS s
INNER JOIN sys.procedures AS p
ON s.[object_id] = p.[object_id]
ORDER BY p.name;
So if your system has only been up for a short time, this is not a reliable measure. The link #Siva points out is useful as well for some other ideas. Unfortunately SQL Server doesn't really track this for you overall so unless you add tracing or logging you are stuck with the trust you place in the DMV...
EDIT it was a good point, I was solving for the procedures that have run. Instead you may want this:
SELECT sc.name, p.name
FROM sys.procedures AS p
INNER JOIN sys.schemas AS sc
ON p.[schema_id] = sc.[schema_id]
LEFT OUTER JOIN sys.dm_exec_procedure_stats AS st
ON p.[object_id] = st.[object_id]
WHERE st.[object_id] IS NULL
ORDER BY p.name;
Or you may want to also include procedures that have run as well, but order them by when they last ran:
SELECT sc.name, p.name
FROM sys.procedures AS p
INNER JOIN sys.schemas AS sc
ON p.[schema_id] = sc.[schema_id]
LEFT OUTER JOIN sys.dm_exec_procedure_stats AS st
ON p.[object_id] = st.[object_id]
ORDER BY st.last_execution_time, p.name;
This will order first the procedures that haven't run since a restart, then the rest by when they were executed last, oldest first.

Here's a variation on the accepted answer that was the most useful for me:
SELECT sc.NAME + '.' + p.NAME [Procedure]
,s.last_execution_time
FROM sys.procedures AS p
LEFT JOIN sys.dm_exec_procedure_stats AS s ON p.[object_id] = s.[object_id]
INNER JOIN sys.schemas sc ON p.schema_id = sc.schema_id
ORDER BY s.last_execution_time
,sc.NAME
,p.NAME
The modifications display the last execution time and include the procedure's schema.

For some reason, my dm_exec_procedure_stats view clears itself out through the day. So I just ran it right now and the earliest execution times are from this morning, but I know we didn't restart SQL this morning or anything.
Anyway, I wanted to create a proc that I could put into a job to run every hour and capture which procs had run recently. Here is what I did:
Created a table to log the procs that get executed and their first and last execution times.
create table ProcsThatExecute (procName varchar(500), firstExecutionTime datetime, lastExecutionTime datetime)
Created a procedure that inserts or updates the ProcsThatExecute table. Run this every hour in a job and after a few days or weeks or months you have a log of which procs get used:
alter procedure LogProcsThatExecute as begin
--If they don't already exist in this table, add them.
insert into ProcsThatExecute(procName, firstExecutionTime, lastExecutionTime)
SELECT p.NAME ProcName, s.last_execution_time, null
FROM sys.procedures AS p
LEFT OUTER JOIN sys.dm_exec_procedure_stats AS s ON p.[object_id] = s.[object_id]
where not exists (
select 1 from ProcsThatExecute pte where pte.procName = p.name
) and last_execution_time is not null
--If they do exist in this table, update the last execution time.
update ProcsThatExecute set lastExecutionTime = s.last_execution_time
from ProcsThatExecute pte
inner join sys.procedures AS p on pte.procName = p.name
LEFT OUTER JOIN sys.dm_exec_procedure_stats AS s ON p.[object_id] = s.[object_id]
where s.last_execution_time is not null
end

Related

How can I search for stored procedures executing other stored procedures?

I have a list of about 350 stored procedures like this:
usp_SP1,
usp_SP2
...
I want to search through each one looking to see if any of them call other stored procedures or other databases?
I guess I would look for a line like 'exec something' in each one or a specific name of a database. ex. some_other_database
How would I do this to give me a list of the stored procedures that call other stored procedures or contain some specific string? ex. "some other database name"
I can run this below but it finds just the text. is there any way I can ensure it's a exec call and not just text?
USE [Your_DB];
GO
SELECT
ROUTINE_NAME, ROUTINE_DEFINITION
FROM
INFORMATION_SCHEMA.ROUTINES
WHERE
ROUTINE_DEFINITION LIKE '%exec %'
AND ROUTINE_TYPE = 'PROCEDURE'
AND ROUTINE_NAME IN ('usp_SP1', 'usp_SP2')
GO
You can query the sys.sql_dependencies view, like this:
SELECT o1.name AS CallerSP, o2.name AS CalledSP
FROM sys.sql_dependencies sd
INNER JOIN sys.objects o1 ON o1.object_id = sd.object_id
INNER JOIN sys.objects o2 ON o2.object_id = sd.referenced_major_id
WHERE o1.type='P' AND o2.type='P'
You may need to call sp_refreshsqlmodule for all objects before executing this query, if the called SP was created after the caller.
Other options could be to query the sys.sql_expression_dependencies view or the sys.dm_sql_referenced_entities function.
Not to compete, Razvan Socol is correct. Adding the ways to do it in earlier version back to 2000. All the old tables and views are still query-able even if not visible in ssms.
select distinct /* I believe the sys.sys views were added in 2012 or so, still works in 2017 */
od.name caller_procedure_name
,o.name called_procedure_name
from sys.sysdepends d
inner join sys.sysobjects o on o.id = d.depid and o.type = 'P'
inner join sys.sysobjects od on od.id = d.id and od.type = 'P'
select distinct /* should work all the way back to sql 2000, still works in 2017 */
od.name caller_procedure_name
,o.name called_procedure_name
from dbo.sysdepends d
inner join dbo.sysobjects o on o.id = d.depid and o.type = 'P'
inner join dbo.sysobjects od on od.id = d.id and od.type = 'P'

SQL Cause of table updates

Is there a way to determine how a table is updated? I have a table that is being updated and I can't figure out how; by an agent job? ssis package? trigger?
I've queried against dm_exec_query_stats and dm_exec_sql_text to determine the statement that is being run, but I don't know where it's being executed from.
SELECT SQL_HANDLE, deqs.plan_handle, deqs.last_execution_time,
dest.text
FROM sys.dm_exec_query_stats AS deqs
CROSS APPLY sys.dm_exec_sql_text(deqs.sql_handle) AS dest
WHERE dest.text LIKE '%Update%'
ORDER BY deqs.last_execution_time desc
Use this query for any dependent stored procedure that has insert/update/select queries for a particular table . Alternatively if you have a trigger on the table which captures inserted datetime , you can analyse by checking current running queries.
It would need a lot of digging of any SSIS jobs are scheduled for table update, usually most updates are done through SP's. This below query is only a quick check.
SELECT DISTINCT QUOTENAME(OBJECT_SCHEMA_NAME(referencing.object_id)) + '.' + QUOTENAME(OBJECT_NAME(referencing.object_id)) AS SprocName
,QUOTENAME(OBJECT_SCHEMA_NAME(referenced.object_id)) + '.' + QUOTENAME(OBJECT_NAME(referenced.object_id)) AS ReferencedObjectName
,referenced.type_desc AS ReferencedObjectType
FROM sys.sql_dependencies d
INNER JOIN sys.procedures referencing ON referencing.object_id = d.object_id
INNER JOIN sys.objects referenced ON referenced.object_id = d.referenced_major_id
WHERE referencing.type_desc = 'SQL_STORED_PROCEDURE'
AND referenced.type_desc = 'USER_TABLE'
ORDER BY SprocName
,ReferencedObjectName
You can find out if your table is referenced in any stored procedure, function, view or triggger with this code
suppose the table you are looking for is called tblMyTable
SELECT DISTINCT
o.name AS Object_Name,
o.type_desc,
m.*
FROM sys.sql_modules m
INNER JOIN sys.objects o ON m.object_id = o.object_id
WHERE m.definition Like '%tblMyTable%';
This will return any stored procedure, function, view or triggger that has tblMyTable in its text, so even if it only just reads from it.
So you need to check each one to see if its updating or not.
Yes its some work, but at least it gives you a change to find out if the update is coming from any object in your database.
If this returns nothing then you know that you have to search in your client software.
I know this may not be a good way to do this and I might get burned for this but, here's what I did...
I executed an open transaction against the table; locking it.
BEGIN TRANSACTION
SELECT
TOP 1 *
FROM *tbl*
I then queried dm_exec_requests to find the session_id that is being run
SELECT
sqltext.TEXT,
req.session_id
FROM sys.dm_exec_requests req
CROSS APPLY sys.dm_exec_sql_text(sql_handle) AS sqltext
I then queried dm_exec_sessions to find the host_name and program_name that is running that session.
SELECT
host_name,
login_time,
program_name
FROM sys.dm_exec_sessions
WHERE session_id = 184
I was able to determine that the culprit was an SSIS package hosted on a different server. Close the locking transaction
Thanks everyone for all the help

SQL Server to SalesForce Linked Server Nightmare

I am getting inconsistent results from joining Linked Servers. It's something that should be relatively simple... but has taken me hours to get this figured out. I am using SQL Server 2014 and the CData ODBC Driver to Join to SalesForce. I'm not doing anything fancy just trying to perform standard CRUD operations but again and again it seems that when ever I filter these linked server tables that sometimes results do not produce.
My current and main issue right now is I am having difficulty JOINING two Linked tables to two Local tables. If I remove one of the Linked tables from the join results are produced. But whenever I add two linked tables to the joins it produces and empty record set.
And yes all the related identifiers exist so it really is an issue with the Linked Server. Here are the three variations that I've tried:
SELECT * FROM Offer_Interest oi
INNER JOIN Offer o ON oi.Offer_ID_SQL = o.Offer_ID_SQL
INNER JOIN OPENQUERY([TR-SF-PROD], 'SELECT Id, OFFER_ID_SQL__C FROM Offer__c') osf ON o.Offer_ID_SQL =osf.OFFER_ID_SQL__C
INNER JOIN Interest i ON oi.Interest_ID_SQL = i.Interest_ID_SQL
INNER JOIN OPENQUERY([TR-SF-PROD], 'SELECT INTEREST_ID_SQL__C, Id FROM Interest__c') isf ON i.Interest_ID_SQL =isf.Interest_ID_SQL__c
WHERE o.PrimaryContact_ID_SQL = 2803
I've also tried without OPENQUERY:
SELECT * FROM FROM Offer_Interest oi
INNER JOIN Offer o ON oi.Offer_ID_SQL = o.Offer_ID_SQL
INNER JOIN [TR-SF-PROD].[CDataSalesforce].[Salesforce].[Offer__c] osf ON o.Offer_ID_SQL =osf.OFFER_ID_SQL__C
INNER JOIN Interest i ON oi.Interest_ID_SQL = i.Interest_ID_SQL
INNER JOIN [TR-SF-PROD].[CDataSalesforce].[Salesforce].[Interest__c] isf ON i.Interest_ID_SQL =isf.Interest_ID_SQL__c
WHERE o.PrimaryContact_ID_SQL = 2803
And Lastly I've also created Synonyms to the Linked Server tables. All of these work using the same filter or WHERE CLAUSE if I run them seperately although the linked server tables seem buggy if I filter them without OPENQUERY.
This is my first experience Linking a Server to SQL Server so anyone with experience in this or what the issue may be would be greatly appreciated!
Not the answer I was hoping as it should just work. But a temp fix I came up with is stuffing the linked server values I need into Temp Tables then joining on the temp tables, which worked... but of course this adds time onto the execution of the overall stored procedure so it is definitely not the ideal solution. If anyone has a better idea please still answer!!
IF EXISTS(SELECT [NAME] FROM tempdb.sys.tables WHERE [NAME] like '#TempOffer%') BEGIN
DROP TABLE #TempOffer;
END;
SELECT * INTO #TempOffer FROM OPENQUERY([TR-SF-PROD], 'SELECT Id, OFFER_ID_SQL__C FROM Offer__c')
IF EXISTS(SELECT [NAME] FROM tempdb.sys.tables WHERE [NAME] like '#TempInterest%') BEGIN
DROP TABLE #TempInterest;
END;
SELECT * INTO #TempInterest FROM OPENQUERY([TR-SF-PROD], 'SELECT INTEREST_ID_SQL__C, Id FROM Interest__c')
SELECT * FROM Offer_Interest oi
INNER JOIN Offer o ON oi.Offer_ID_SQL = o.Offer_ID_SQL
INNER JOIN #TempOffer osf ON o.Offer_ID_SQL =osf.OFFER_ID_SQL__C
INNER JOIN Interest i ON oi.Interest_ID_SQL = i.Interest_ID_SQL
INNER JOIN #TempInterest isf ON i.Interest_ID_SQL =isf.Interest_ID_SQL__c
WHERE o.PrimaryContact_ID_SQL = 2803

SQL query returning not all data w.r.t to Territory Name

I'm trying to get Sales Person's data w.r.t Territory they work. I'm Using Adventureworks database tables Employee, Contact, SalesPerson, SalesTerritory.
Here is my query :
Select p.FirstName, p.LastName, h.EmployeeID, t.Name as "Territory Name"
from Person.Contact p
INNER JOIN HumanResources.Employee h ON p.ContactID = h.ContactID
INNER JOIN Sales.SalesPerson s ON s.SalesPersonID = h.EmployeeID
INNER JOIN Sales.SalesTerritory t ON s.TerritoryID = t.TerritoryID
Issue with this query is it results exactly one Territory. However there are sales persons who work in multiple territories... I need to result all of them.
Any help on this will be appreciated.
You must mean you want to know where Employees worked over time. As far as I can see in AdventureWorks2008 and AdventureWorks2008R2, Sales.SalesPerson is assigned a single TerritoryID, then in Sales.SalesTerritoryHistory there can be multiple TerritoryIDs per BusinessEntityID. For those DBs, use this:
select p.FirstName
,p.LastName
,e.LoginID -- there is no EmployeeID in this version of AdventureWorks
,st.Name as [TerritoryName]
from Sales.SalesPerson as sp
join Person.Person as p
on p.BusinessEntityID = sp.BusinessEntityID
join HumanResources.Employee as e
on e.BusinessEntityID = sp.BusinessEntityID
join Sales.SalesTerritoryHistory as sth
on sth.BusinessEntityID = sp.BusinessEntityID
join Sales.SalesTerritory as st
on sth.TerritoryID = st.TerritoryID
-- keep the where clause if you want to find only current TerritoryIDs
-- remove the where clause if you want to know all across time
where GETDATE() between sth.StartDate and isnull(sth.EndDate,'9999-1-1')
Clarify the version of AdventureWorks DB and I can correct the error (but don't say 2012, cause I ain't installing SQL Server 2012 :)

How do I get a list of tables affected by a set of stored procedures?

I have a huge database with some 100 tables and some 250 stored procedures. I want to know the list of tables affected by a subset of stored procedures. For example, I have a list of 50 stored procedures, out of 250, and I want to know the list of tables that will be affected by these 50 stored procedures. Is there any easy way for doing this, other than reading all the stored procedures and finding the list of tables manually?
PS: I am using SQL Server 2000 and SQL Server 2005 clients for this.
This would be your SQL Server query:
SELECT
[NAME]
FROM
sysobjects
WHERE
xType = 'U' AND --specifies a user table object
id in
(
SELECT
sd.depid
FROM
sysobjects so,
sysdepends sd
WHERE
so.name = 'NameOfStoredProcedure' AND
sd.id = so.id
)
Hope this helps someone.
sp_depends 'StoredProcName' will return the object name and object type that the stored proc depends on.
EDIT: I like #KG's answer better. More flexible IMHO.
I'd do it this way in SQL 2005 (uncomment the "AND" line if you only want it for a particular proc):
SELECT
[Proc] = SCHEMA_NAME(p.schema_id) + '.' + p.name,
[Table] = SCHEMA_NAME(t.schema_id) + '.' + t.name,
[Column] = c.name,
d.is_selected,
d.is_updated
FROM sys.procedures p
INNER JOIN sys.sql_dependencies d
ON d.object_id = p.object_id
AND d.class IN (0,1)
INNER JOIN sys.tables t
ON t.object_id = d.referenced_major_id
INNER JOIN sys.columns c
ON c.object_id = t.object_id
AND c.column_id = d.referenced_minor_id
WHERE p.type IN ('P')
-- AND p.object_id = OBJECT_ID('MyProc')
ORDER BY
1, 2, 3
One very invasive option would be to get a duplicate database and set a trigger on every table that logs that something happened. Then run all the SP's. If you can't do lots of mods to the DB that wont work
Also, be sure to add the logging to existing triggers rather than replace them with logging if you also want tables that the SP's effect via triggers.

Resources