Get user defined functions hierarchy - sql-server

I have around 500 functions out of which 100 functions are having dependency with other functions, I mean they are getting called from other functions.
I need to find the hierarchy list of functions which are having dependency.
Expected output:
Function Name Level
----------------------
Fn_test 0
Fn_abc 0
Fn_xa 1
Fn_zi 2
Fn_my 3
Note:
Level - 0 for the independent function.
Level - 1 for parent function
Level - 2...n for child functions

You can use sys.sql_dependencies to find out which functions calls other functions:
create function fn_a() returns int
begin
return 0;
end;
go
create function fn_b() returns int
begin
return dbo.fn_a();
end;
go
select f1.name, f2.name, d.* from sys.sql_dependencies d
inner join sys.objects f1 on d.object_id = f1.object_id and f1.type='FN'
inner join sys.objects f2 on d.referenced_major_id = f2.object_id and f2.type='FN'
This returns fn_b, fn_a.
Create a CTE based on this query to get the function call hierarchy.

Related

The multi-part identifier "[column name]" could not be bound in UPDATE of TEMP Table

I am trying to create a stored procedure whereupon I input a (simple for now) query into a temp table, and then replace some of the data with data from a different table based on a key.
Here is the complete code:
CREATE PROCEDURE GetInquiryList
AS
BEGIN
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Inq ') IS NOT NULL
DROP TABLE #Inq
SELECT i.*,q.QuoteID INTO #Inq FROM Inquiries i left join Quotes q on i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0
--SELECT * FROM #Inq
UPDATE #Inq
SET j.InquiryCustomerName = c.CustomerName,
j.InquiryCustomerEmail = c.CustomerEmail,
j.InquiryCustomerPhone = c.CustomerPhone1,
j.InquiryBestTimetoCall = c.CustomerBestTimetoCall,
j.InquiryDay = c.customerDay,
j.InquiryNight = c.CustomerNight
SELECT c.CustomerName,
c.CustomerEmail,
c.CustomerPhone1,
c.CustomerBestTimetoCall,
c.customerDay,
c.CustomerNight
FROM Customers c
INNER JOIN #Inq j ON
j.InquiryCustomerID = c.CustomerID
SELECT * FROM #Inq
END
I get the following error:
Msg 4104, Level 16, State 1, Line 15 The multi-part identifier "j.InquiryCustomerName" could not be bound
I get this error for whatever column is placed first after the SET command.
Both query pieces of this work independently (the first select creating the temp table and the joined query at the bottom). The data returned is correct. I have tried using aliases (SELECT c.CustomerName AS Name, ...).
Originally, I used "#Inq i" in the second command, but changed to "j" out of an abundance of caution.
I have also run the command against the original table (substituting the Inquiry table for the temp table #Inq, and that fails as well).
Shortening it to this:
UPDATE #Inq
SET j.InquiryCustomerName = c.CustomerName,
j.InquiryCustomerEmail = c.CustomerEmail,
j.InquiryCustomerPhone = c.CustomerPhone1,
j.InquiryBestTimetoCall = c.CustomerBestTimetoCall,
j.InquiryDay = c.customerDay,
j.InquiryNight = c.CustomerNight
FROM Customers c
INNER JOIN #Inq j ON
j.InquiryCustomerID = c.CustomerID
I get a different error:
Msg 4104, Level 16, State 1, Line 15 The multi-part identifier "j.InquiryCustomerName" could not be bound
I'm sure it's probably something simple,(so simple that I can't find any references in any of my searches).
I'm sure it has something to do with the fact that you can't update the same instance of the table used in the join (I'm going to have to re-join again with a "k" alias). How do I do this?
data from the first query
data from the first query
data from the second select statement on the actual temp table
Here is what I updated the stored procedure to, which works exactly how I need it to:
SET NOCOUNT ON
IF OBJECT_ID('tempdb..#Inq ') IS NOT NULL
DROP TABLE #Inq
SELECT i.* INTO #Inq FROM (
select inquiries.InquiryId,
inquiries.InquiryDateReceived,
inquiries.InquiryCustomerID,
cust.CustomerName as InquiryCustomerName,
cust.CustomerEmail as InquiryCustomerEmail,
cust.CustomerPhone1 as InquiryCustomerPhone,
cust.CustomerBestTimeToCall as InquiryBestTimeToCall,
cust.CustomerDay as InquiryDay,
cust.CustomerNight as InquiryNight,
inquiries.InquiryServiceType,
inquiries.InquiryServiceID,
inquiries.InquiryTimeframe,
inquiries.InquiryProjectDescription,
inquiries.InquiryDateResponded,
inquiries.InquiryCustomerReply,
inquiries.YNMigrated,
inquiries.InquiryDateClosed,
inquiries.YNClosed,
inquiries.YNDeleted
from inquiries inner join dbo.Customers as cust
on inquiries.InquiryCustomerID = cust.CustomerID and inquiries.InquiryCustomerID > 0
UNION ALL
select inquiries.InquiryId,
inquiries.InquiryDateReceived,
inquiries.InquiryCustomerID,
InquiryCustomerName,
InquiryCustomerEmail,
InquiryCustomerPhone,
InquiryBestTimeToCall,
InquiryDay,
InquiryNight,
inquiries.InquiryServiceType,
inquiries.InquiryServiceID,
inquiries.InquiryTimeframe,
inquiries.InquiryProjectDescription,
inquiries.InquiryDateResponded,
inquiries.InquiryCustomerReply,
inquiries.YNMigrated,
inquiries.InquiryDateClosed,
inquiries.YNClosed,
inquiries.YNDeleted
from inquiries WHERE inquiries.InquiryCustomerID = 0
) i
select i.*, q.QuoteID
FROM #Inq i left join dbo.Quotes as q
on i.InquiryId = q.InquiryId
WHERE i.YNDeleted = 0
END
Just stop using this pattern without a really good reason. Here it only appears to create more work for the database engine with no obvious benefit. Your procedure - as posted - has trivially simple queries so why bother with the temp table and the update?
It is also time to start learning and using best practices. Terminate EVERY statement - eventually it will be required. Does order of the rows in your resultset matter? Usually it does and that is only guaranteed when that resultset is produced by a query that includes an ORDER BY clause.
As a developing/debugging short cut, you can harness the power of CTEs to help you build a working query. In this case, you can "stuff" your first query into a CTE and then simply join the CTE to Customers and "adjust" the columns you need in that resultset.
WITH inquiries as (
select inq.*, qt.QuoteID
FROM dbo.Inquiries as inq left join dbo.Quotes as qt
on inq.InquiryId = qt.InquiryId
WHERE inq.YNDeleted = 0
)
select inquiries.<col>,
...,
cust.CustomerName as "InquiryCustomerName",
...
from inquiries inner (? guessing) dbo.Customers as cust
on inquiries.InquiryCustomerID = cust.CustomerID
order by ...
;
Schema names added as best practice. Listing the columns you actually need in your resultset is another best practice. Note I did not do that for the query in the CTE but you should. You can choose to create aliases for your resultset columns as needed. I listed one example that corresponds to your UPDATE attempt.
It is odd and very suspicious that all of the columns you intended to UPDATE exist in the Inquiries table. Are you certain you need to do that at all? Do they actually differ from the related columns in the Customer table? Also odd that the value 0 exists in InquiryCustomerID - suggesting you might have not a FK to enforce the relationship. Perhaps that means you need to outer join rather than inner join (as I wrote). If an outer join is needed, then you will need to use CASE expressions to "choose" which value (the CTE value or the Customer value) to use for those columns.
After learning a lot more about how things get bound to models, and how to further use sql, here is what my stored procedure looks like:
ALTER PROCEDURE [dbo].[GetInquiryList]
#InquiryID int = 0
AS
BEGIN
SET NOCOUNT ON
select i.InquiryId,
i.InquiryDateReceived,
i.InquiryCustomerID,
InquiryCustomerName =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerName
ELSE c.CustomerName
END,
InquiryCustomerEmail =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerEmail
ELSE c.CustomerEmail
END,
InquiryCustomerPhone =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryCustomerPhone
ELSE c.CustomerPhone1
END,
InquiryBestTimetoCall =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryBestTimetoCall
ELSE c.CustomerBestTimetoCall
END,
InquiryDay =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryDay
ELSE c.CustomerDay
END,
InquiryNight =
CASE i.InquiryCustomerID
WHEN 0 THEN i.InquiryNight
ELSE c.CustomerNight
END,
i.InquiryServiceType,
i.InquiryServiceID,
i.InquiryTimeframe,
i.InquiryProjectDescription,
i.InquiryDateResponded,
i.InquiryCustomerReply,
i.YNMigrated,
i.InquiryDateClosed,
i.YNClosed,
i.YNDeleted, ISNULL(q.QuoteId,0) AS Quoteid
FROM dbo.Inquiries i
LEFT JOIN dbo.Quotes q ON i.InquiryId = q.InquiryId
LEFT JOIN dbo.Customers c ON i.InquiryCustomerID = c.CustomerId
WHERE i.YNDeleted = 0
END
I'm sure there are additional enhancements that could be made, but avoiding the union is a big savings. Thanks, everyone.

Setting a variable using a stored procedure without printing an output

I have a stored procedure as follows which returns a value:
DECLARE #outputValue decimal(18,10)
set #outputValue = (
select TOP 1 TotalUsageFactor as '801 UNR Usage Factor'
from MarketMessage as a
inner join messagetype591 as b on a.MarketMessageID = b.MarketMessageID
inner join AdditionalAggregationInformation as c on b.MessageType591ID = c.MessageType591ID
inner join AdditionalAggregationData as d on c.AdditionalAggregationInformationID = d.AdditionalAggregationInformationID
where SettlementRunIndicator = 20
and LoadProfileCode = 801
and TimeOfUse = 'UNR'
order by SettlementDate desc)
RETURN #outputValue
Despite this being a decimal value I seem to always be only able to return an int, this is used within another parent stored procedure and is called as follows:
DECLARE #UsageFactor801UNR decimal(18,10)
EXEC #UsageFactor801UNR = GetLatestUsageFactor801UNR
If I do it this way there is no output value being returned and at the end of the query I am being returned the final table of data as expected. If I however change the code to the following:
select TOP 1 TotalUsageFactor as '801 UNR Usage Factor'
from MarketMessage as a
inner join messagetype591 as b on a.MarketMessageID = b.MarketMessageID
inner join AdditionalAggregationInformation as c on b.MessageType591ID = c.MessageType591ID
inner join AdditionalAggregationData as d on c.AdditionalAggregationInformationID = d.AdditionalAggregationInformationID
where SettlementRunIndicator = 20
and LoadProfileCode = 801
and TimeOfUse = 'UNR'
order by SettlementDate desc
RETURN
I am now able to retrieve the correct decimal value but in my parent query I am returning these values before returning the table of information that I actually require, how can I have it so that I am able to return a decimal value from the function with no output or have it so that there is no return from the execution of the query within the parent

Find all lines where a value exists in one line

Here is my query
select order_no, pkg_no, zone_no
from T_DETAIL_ITEM a
where order_no = 495
order by order_no, pkg_no
For a given package I have zone number = 0
What I need to do is return all the lines with the pkg_no = 1597. Because one where exists with a zero zone.
I tried a few different 'where exists' lines and it isn't working.
Try to self join.
This way you can put your requirement in the second table "instance" but retrieve everything from that table matches based on another common field.
select distinct a.order_no, a.pkg_no, a.zone_no
from T_DETAIL_ITEM a
join T_DETAIL_ITEM b on b.pkg_no = a.pkg_no
where b.zone_no = 0
order by a.order_no, a.pkg_no
The accepted answer is good, but I had saw that you noted you tried EXISTS, so I wrote this example up using that method.
SELECT *
FROM T_Detail_Item d
WHERE exists (SELECT * FROM T_Detail_Item dx WHERE dx.pkg_no = d.pkg_no AND dx.zone_no = 0)
One way is to self reference the table in a left join and only include those with a zone_no=0, within the join clause. The filter out non-matching records by excluding records that do not match from the left join, T2.pkg_no = NULL.
SELECT
T1.pkg_no, T1.zone_no
FROM
T_DETAIL_ITEM T1
LEFT OUTER JOIN T_DETAIL_ITEM T2 ON T2.pkg_no = T1.pkg_no AND T2.zone_no = 0
WHERE
NOT T2.pkg_no IS NULL
If I correctly understood the question, you need something like this
BEGIN
declare #str varchar(max);
set #str='';
SELECT #str=#str+ [YOURCOLUMB]
FROM [YOURTABLE]
SELECT #str
END

Function in SQL for JOIN condition

This is my query:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable main JOIN
dbo.MainDataTable mainData ON
(main.dataId = mainData.dataId) AND
(mainData.Type = 1 OR mainData.Type = 2 OR mainData.Type = 3)
I use similar query in many views. But the last condition is always the same everywhere: main.Type = 1 OR main.Type = 2 OR main.Type = 3.
I wondering how I can extract it to some SQL function. I never do any function before.
So it would be looks like this:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable main
JOIN dbo.MainDataTable mainData ON main.dataId = mainData.dataId
AND (GetConditionForType()) -- more or less ;)
You can create a view like this:
create view dbo.FilteredMainDataTable
as
select ...
from dbo.MainDataTable
where [Type] in (1,2,3);
And then use this view in all your queries instead of dbo.MainDataTable. What the compiler does then it "opens" the view as its definition in every query that uses this view and that is what you want. Functions do not do this and they are not thought as "macro substitution"
If you insist on function you can create it but it will not have a "look" as you want. It can be inline table-valued function like this:
create function dbo.fn_FilteredMainDataTable(#n1 int, #n2 int, #n3 int)
returns TABLE
return select Id, ...
from FilteredMainDataTable
where type in (#n1, #n2, #n3);
Then you join to this function instead of MainDataTable like this:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable main
JOIN dbo.fn_FilteredMainDataTable(1,2,3) mainData ON main.dataId = mainData.dataId
^^^^^^^^^^^^^^^^^^^^^^^
The following code shows how inline table function like a view push in the seek predicate:
if object_id('dbo.num') is not null drop table dbo.num;
go
select top 1000000
isnull(row_number() over(order by 1 / 0), 0) as n,
isnull(row_number() over(order by 1 / 0), 0) as n1
into dbo.num
from sys.columns c1 cross join sys.columns c2 cross join sys.columns c3;
go
alter table dbo.num add constraint PK_num_n primary key (n);
go
create index ix_n1_n on dbo.num (n1, n);
go
if object_id('dbo.fn_num_between') is not null drop function dbo.fn_num_between;
go
create function dbo.fn_num_between(#n1 int, #n2 int)
returns table
as
return
select n, n1
from dbo.num
where n between #n1 and #n2;
go
select *
from dbo.fn_num_between(1, 1000)
where n1 = 5;
You can do this with an inline table valued function.
You would define the function as:
create function fnFilteredTypes ()
returns table as return
(
select 1 as type
union
select 2 as type
union
select 3 as type
);
Then you can join against this function like so:
SELECT main.SomeValues, mainData.Name
FROM dbo.MainTable AS main
JOIN dbo.MainDataTable AS mainData
ON main.dataId = mainData.dataId
JOIN fnFilteredTypes() As typeFilter
ON(mainData.Type=typeFilter.type)
I'm not sure this query is really what you want, but basically you get the idea that you can use the function like it's a parameterized view & join directly against it's fields. Just don't forget it's a function & you call it with round brackets.

Using recursive CTE to group all children of list of parents

I have a table with two columns - ChildPersonId and ParentPersonId. The same ID cannot be in both columns.
CREATE TABLE Relationship
(PkId int IDENTITY(1,1) PRIMARY KEY, ChildPersonId int, GuardianPersonId int)
INSERT INTO Relationship (ChildPersonId, GuardianPersonId) VALUES
(42,24),(42,25),(42,56),(42,56),(43,24),(43,25),(43,26),(43,27),
(43,56),(44,29),(44,30),(45,31),(45,33),(46,34),(47,35),(48,36),
(48,37),(49,36),(49,37),(50,38),(50,39),(51,38),(51,39),(52,40),
(52,41),(53,40),(53,41),(57,24),(57,25),(57,26),(57,27),(57,56),
(63,24),(63,25),(63,26),(63,27),(63,56),(63,59),(64,59),(64,61),
(65,61),(65,62)
I want a query where I can pass a ChildPersonId and return ALL related children based on the relationships defined in the data. So if a child has a parent, I need to then find all OTHER children of that parent, then with those children find their parents and then with those parents, find children...you get the recursive picture.
The query in the link below ALMOST works, but falls way short in terms of performance:
Using recursive CTE to resolve a group, not hierarchy
The below example actual does return the results I want:
SELECT ChildPersonId FROM Relationship M5 INNER JOIN
(SELECT GuardianPersonId FROM Relationship M4 INNER JOIN
(SELECT ChildPersonId FROM Relationship M3 INNER JOIN
(SELECT GuardianPersonId FROM Relationship M2 INNER JOIN
(SELECT ChildPersonId FROM Relationship m1 INNER JOIN
(SELECT GuardianPersonId FROM Relationship
WHERE ChildPersonId = 42) g1
ON m1.GuardianPersonId = g1.GuardianPersonId) c1
ON m2.ChildPersonId = c1.ChildPersonId) g2
ON m3.GuardianPersonId = g2.GuardianPersonId) c2
ON m4.ChildPersonId = c2.ChildPersonId) g3
ON m5.GuardianPersonId = g3.GuardianPersonId
GROUP BY ChildPersonId
However, this is ugly code and I will only recurse as many times as I am willing to cut & paste.
Can anyone tip me off to the Recursive CTE logic I need to pull this off - without the WHERE clause in the above example that is crushing the execution plan?
The link Getting all the children of a parent using MSSQL query shows another method, but doesn't return ChildPersonIds 64 and 65 - it doesn't seem to recurse far enough.
Any help would be much appreciated - thanks in advance.
As you noted, a conventional CTE approach will match all but person ids 64 and 65. The approach easily finds element 63. It fails to find 64 and 65 because there is no recursive path to reach either directly.
For instance, 64 is not reached because the only link is to 63 through parent 59. However, 59 is not linked to any other element since it is basically one of multiple root nodes in the tree. Additionally, 65 is not reached because the only path to it is to find 64 as stated above, then traverse through its parent 61 (also a root node) to it's other child 65.
Conventional CTE recursion will not find those alternate paths, so I have included a traditional approach as well as an extension to also iterate parental siblings as a relationship path. This extension need more than just CTE due to the inability to exit the recursive condition due to the data structure.
Conventional CTE Approach
DECLARE #ChildPersonId INT = 42
; WITH cte AS (
SELECT
ChildPersonId,
GuardianPersonId
FROM Relationship R
WHERE ChildPersonId = #ChildPersonId
UNION ALL
SELECT
R.ChildPersonId,
R.GuardianPersonId
FROM cte
INNER JOIN Relationship R
ON cte.GuardianPersonId = R.ChildPersonId
)
SELECT DISTINCT
R.ChildPersonId
FROM cte
INNER JOIN Relationship R
ON cte.GuardianPersonId = R.GuardianPersonId
The conventional approach will yield
ChildPersonId
-------------
42
43
57
63
Extension to Conventional Approach
CREATE FUNCTION dbo.f_GetRelations(
#ChildPersonId INT
) RETURNS #Results TABLE (
ChildPersonId INT
)
AS
BEGIN
;WITH cte AS (
SELECT
ChildPersonId,
GuardianPersonId
FROM Relationship R
WHERE ChildPersonId = #ChildPersonId
UNION ALL
SELECT
R.ChildPersonId,
R.GuardianPersonId
FROM cte
INNER JOIN Relationship R
ON cte.GuardianPersonId = R.ChildPersonId
)
INSERT #Results
SELECT DISTINCT
R.ChildPersonId
FROM cte
INNER JOIN Relationship R
ON cte.GuardianPersonId = R.GuardianPersonId
DECLARE #Rows INT = -1
WHILE #Rows <> 0 BEGIN
INSERT #Results
SELECT DISTINCT
C.ChildPersonId
FROM #Results A
INNER JOIN Relationship B
ON A.ChildPersonId = B.ChildPersonId
INNER JOIN Relationship C
ON B.GuardianPersonId = C.GuardianPersonId
WHERE NOT EXISTS (SELECT 1 FROM #Results WHERE ChildPersonId = C.ChildPersonId)
SET #Rows = ##ROWCOUNT
END
RETURN
END
GO
SELECT A.ChildPersonId FROM f_GetRelations(42) A
The output of this method yields a match to your expected output
ChildPersonId
-------------
42
43
57
63
64
65

Resources