creating a stored procedure from a recursive query - sql

I'd like to create a mssql stored procedure to run a query like the following:
SELECT thingID
FROM things
WHERE thingParentID = #arguments.id#
recursively, accumulating thingIDs in a list which is then returned by the stored procedure.
Does anyone know of a example like this that they can link to? or some documentation that might help me?
Thanks.

This will work on SQL Server 2005 and up.
CREATE FUNCTION dbo.Ancestors (#thingID int)
RETURNS TABLE
AS
RETURN
WITH CTE AS
(
SELECT thingID, 1 [Level]
FROM dbo.things
WHERE thingParentID = #thingID
UNION ALL
SELECT p.thingID, [Level] + 1 [Level]
FROM CTE c
JOIN dbo.things p
ON p.thingParentID = c.thingID
)
SELECT thingID, [Level]
FROM CTE
GO
CREATE PROCEDURE GetAncestors (#thingID int)
AS
SELECT thingID, [Level]
FROM dbo.Ancestors(#thingID)
GO

Related

Create a scheduler to re-compile stored procedure

I am facing an issue on SQL Server in which my stored procedure becomes slow after couple of days.
I came to know that recompiling the stored procedure will work. However, I do not want to recompile that stored procedure every time it gets called.
Is it a good way to create a job on SQL Server which will execute following statement?
EXEC sp_recompile N'SP_NAME';
Will this cause any performance issues?
Below is my SP Structure.
#START_VALUE int=null,
#END_VALUE int=null`enter code here`
#UID NVARCHAR(MAX)=null,
AS
BEGIN
SELECT dbo.TABLE1.ID,
ROW_NUMBER() OVER (ORDER BY TABLE1.UPDATED_ON desc) AS RN,
CONVERT(VARCHAR(10), dbo.TABLE1.DATE, 101) AS TDATE,
CATEGORY
=(
SELECT TOP 1 COLUMN1 FROM TABLE5 CT1 WHERE
TABLE1.CATEGORY = CT1.CATEGORY_ID
)
,
TYPETEXT
=(
SELECT TOP 1 COLUMN1 FROM TABLE6 CT1 WHERE
TABLE1.TYPE = CT1.TYPE_ID
),
IMAGE = STUFF(( SELECT DISTINCT ',' + CAST(pm.C1 AS varchar(12))
FROM TABLE2 pm WHERE
pm.ID = TABLE1.ID AND pm.C1 IS NOT NULL AND pm.C1 <> ''
FOR XML PATH('')), 1, 1, '' )INTO #tempRecords
FROM dbo.TABLE1
WHERE
((#UID is null OR dbo.TABLE1.ID = #UID )
ORDER BY TABLE1.UPDATED DESC
SELECT #count = COUNT(*) FROM #tempRecords;
SELECT *,CONVERT([int],#count) AS 'TOTAL_RECORDS' FROM #tempRecords
WHERE #tempRecords.RN BETWEEN CONVERT([bigint],#START_VALUE) AND CONVERT([bigint],#END_VALUE)
END
GO
At very first: Replace Sub-Queries in the SELECT statement by Sub-Queries in FROM clause.
Here are some options to resolve 'slowing':
1. If procedure is executed not frequently try to use RECOMPILE option.
2. Look at statistics. Maybe it would be easier to UPDATE STATISTICS then recompile SP.
3. If statistics are the problem, you may try to use filtered statistics.
4. If none of these options works, you can just delete query plan for that procedure in automated manner from Query Cache and procedure will be daily recompiled itself.
However, at first, rebuild the main query.

Use column from temp table that is populated by stored proc

I know this is wrong and won't work but here's an idea of what I'm trying to do.
I would just join the table that the [GetFinalCountByGroupId] sp is using internally but I don't want to because the table it uses has some large varbinary data. Of course the sp is still querying it so maybe it is just as good performance wise as a join rather than a sp call. Either way I'm curious if I can get this to work first - if not I'll just try a join. Anyway, here's some code:
CREATE PROCEDURE [dbo].[GetFinalRequests]
AS
BEGIN
SET NOCOUNT ON
DECLARE #FinalTable TABLE
(
FinalCount TINYINT
)
INSERT INTO #FinalTable
EXEC [dbo].[GetFinalCountByGroupId] [GroupId]
SELECT [Id]
,[GroupId]
,[SubmitBy]
,[InUse]
FROM [dbo].[Requests]
WHERE [InUse] = 1
AND #FinalTable.FinalCount > 0
END
edit: here is the result of executing this...
Must declare the scalar variable "#FinalTable".
You have a syntax error here:
SELECT [Id]
,[GroupId]
,[SubmitBy]
,[InUse]
FROM [dbo].[Requests]
WHERE [InUse] = 1
AND #FinalTable.FinalCount > 0
You can not access FinalCount column like that in SQL.
I don't know the purpose of the temp table but just to make it work:
SELECT [Id]
,[GroupId]
,[SubmitBy]
,[InUse]
FROM [dbo].[Requests]
WHERE [InUse] = 1
AND (select sum(FinalCount) from #FinalTable) > 0
Ok - I abandoned this and just did a join:
SELECT [Id]
,r.[GroupId]
,[SubmitBy]
,[InUse]
FROM [dbo].[Requests] r
JOIN [dbo].[AgreementDocuments] d
ON r.[GroupId] = d.[GroupId]
WHERE r.[InUse] = 1
AND d.[Final] = 1

Syntax error using multiple CTEs

I have a rather complicated CTE that I'm attempting to incorporate into a stored procedure. It works when just operating straight from SQL Server Management Studio. When I try to create my stored procedure, I get an error:
Msg 102, Level 15, State 1, Procedure spMyCrazyProc, Line 56
Incorrect syntax near ','.
What have I syntactically done incorrectly when trying to incorporate my CTE into a stored procedure?
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[spMyCrazyProc]') and OBJECTPROPERTY(id, N'IsProcedure') = 1)
drop procedure [dbo].[spMyCrazyProc]
GO
SET ANSI_NULLS ON
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE dbo.spMyCrazyProc
#CompanyId Int,
#EmployeeIds varchar(MAX)
AS
;with SelectedEmployees as (
select * from vwEmployee e
where e.CompanyId = #CompanyId
and (#EmployeeIds is null or #EmployeeIds='' or exists(select ce.SelectedEmployeeId from #myTmpTable ce where ce.SelectedEmployeeId=e.EmployeeId)
), MyStuffA as (
select * from SelectedEmployees
)
select * from MyStuffA
GO
Using sensible coding conventions and thinking about readability (indenting, carriage returns) would have yielded this simple error much more clearly. Your code with 500 character-wide lines removed:
;with SelectedEmployees as
(
select * from vwEmployee e
where e.CompanyId = #CompanyId
and
(
#EmployeeIds is null or #EmployeeIds='' or exists
(
select ce.SelectedEmployeeId from #myTmpTable ce
where ce.SelectedEmployeeId=e.EmployeeId
)
----^----- oops! Missing closing paren here.
), MyStuffA as
(
select * from SelectedEmployees
)
select * from MyStuffA

How to optimize stored procedures?

Following is my Stored Proc.
ALTER PROCEDURE [GetHomePageObjectPageWise]
#PageIndex INT = 1
,#PageSize INT = 10
,#PageCount INT OUTPUT
,#AccountID INT
,#Interests Varchar(3000)
AS
BEGIN
SET NOCOUNT ON;
SELECT StoryID
, AlbumID
, StoryTitle
, CAST(NULL as varchar) AS AlbumName
, (SELECT URL FROM AlbumPictures WHERE (AlbumID = Stories.AlbumID) AND (AlbumCover = 'True')) AS AlbumCover
, Votes
, CAST(NULL as Int) AS PictureId
, 'stories' AS tableName
, (SELECT CASE WHEN EXISTS (
SELECT NestedStories.StoryID FROM NestedStories WHERE (StoryID = Stories.StoryID) AND (AccountID=#AccountID)
)
THEN CAST(1 AS BIT)
ELSE CAST(0 AS BIT) END) AS Flag
, (SELECT UserName FROM UserAccounts WHERE Stories.AccountID=UserAccounts.AccountID) AS Username
INTO #Results1
FROM Stories WHERE FREETEXT(Stories.Tags,#Interests) AND AccountID <> #AccountID AND IsActive='True' AND Abused < 10
I have 7 more SELECT Statements (not included in the question for brevity) in the Stored Proc similar to SELECT StoryID statement, which i UNION ALL like this
SELECT * INTO #Results9 FROM #Results1
UNION ALL
SELECT * FROM #Results2
UNION ALL
SELECT * FROM #Results3
UNION ALL
SELECT * FROM #Results4
UNION ALL
SELECT * FROM #Results5
UNION ALL
SELECT * FROM #Results6
UNION ALL
SELECT * FROM #Results7
UNION ALL
SELECT * FROM #Results8
SELECT ROW_NUMBER() OVER
(
ORDER BY [tableName] DESC
)AS RowNumber
, * INTO #Results
FROM #Results9
DECLARE #RecordCount INT
SELECT #RecordCount = COUNT(*) FROM #Results
SET #PageCount = CEILING(CAST(#RecordCount AS DECIMAL(10, 2)) / CAST(#PageSize AS DECIMAL(10, 2)))
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
DROP TABLE #Results
DROP TABLE #Results1
DROP TABLE #Results2
DROP TABLE #Results3
DROP TABLE #Results4
END
This takes around 6 seconds to return the result. How can i improve this stored proc? I have very little knowledge about stored procedures.
Raise a nonclustered index on columns in where clause, IsActive, AccountID and Abused.
Well, you can only optimize it by getting rid of the temporary tables. Your approach sucks not because it is a stored procedure (so the SP part is simply totally irrelevant) but because you do a lot of temporary table stuff that forces linear execution and makes it hard for the query optimizer to find a better day to go forward.
In this particular case, it may be that your db design may be horrifically bad (why #result 1 to #result 8 to start with) and then you have tons of "copy into temp table" on every stored procedure.
Query Optimization in SQL works "statement by statement" and execution is never paralleled between statements - so the temp table stuff really gets into your way here. Get rid of the temp tables.
Never ever use directly SELECT * INTO #temp
INSTEAD
Always create #temp tables then INSERT INTO #temp
this will reduce query execution time by 70%
Though it might be frustration to create #temp table with exact structures,
so here is a short cut for that:This will be once performed
CREATE dbo.tableName by using SELECT * INTO tableName from Your calling query
then
sp_help TableName will provide structures.
Then create #temp table in Store Procedure.
I have optimized query for one of our client which was taking 45 minutes to execute, just replaced with this logic It worked !!!
Now it takes 5 Minutes !!

SQL with clause dynamic where parameter

I have a tree-style database with the following structure:
Table fields:
NodeID int
ParentID int
Name varchar(40)
TreeLevel int
I would like to use a variable #NodeID in the first part of the with clause to don't get all the table just start from the piece I'm interested in (see where Parent=#ParentID and comment).
with RecursionTest (NodeID,ParentID,ThemeName)
as
(
--if i remove the where from here it spends too much time (the tree is big)--
select Nodeid,ParentID,Name from TreeTable where ParentID=#ParentID
union all
select T0.Nodeid,
T0.ParentID,
T0.Name
from
TreeTable T0
inner join RecursionTest as R on T0.ParentID = R.NodeID
)
select * from RecursionTest
This throws some errors, but my question is:
Is possible to pass a variable to a with clause ?
Thanks a lot in advance.
Best regards.
Jose
Yes.
declare #ParentID int
set #ParentID = 10;
with RecursionTest (NodeID,ParentID,ThemeName) ....
You could wrap the whole thing up in a parameterised inline TVF as well. Example of this last approach.
CREATE FUNCTION dbo.RecursionTest (#ParentId INT)
RETURNS TABLE
AS
RETURN
(
WITH RecursionTest (NodeID,ParentID,ThemeName)
AS
(
/*... CTE definition goes here*/
)
SELECT NodeID,ParentID,ThemeName
FROM RecursionTest
)
GO
SELECT NodeID,ParentID,ThemeName
FROM dbo.RecursionTest(10)
OPTION (MAXRECURSION 0)
Unfortunately <11g this will throw an ORA-32033 - unsupported column aliasing, as this functionality is not supported < that version