select hierarchical item using function - sql

I tried to generate Html from table. And to achieve that, I used function.
TABLE:
LayoutId LayoutDetailsId ParentId DetailSyntax DetailValue
--------------- --------------- --------------- --------------- ---------------
2 15 0 table htmlchild
2 16 15 tbody htmlchild
2 17 16 tr htmlchild
2 18 17 th No.
2 19 17 th Name
2 20 17 th Address
2 21 16 tr htmlchild
2 22 21 td 1
2 23 21 td Asha
2 24 21 td Flamboyan Street
2 25 16 tr htmlchild
2 26 25 td 2
2 27 25 td Jack
2 28 25 td Manggo Street
note that htmlchild in DetailValue is like "HasChild"
FUNCTION:
CREATE FUNCTION FnHtmlGenerator(#layoutId BIGINT, #parentId BIGINT = 0, #prevTopLevelHtml VARCHAR(MAX) = '')
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #currentHtml VARCHAR(MAX);
DECLARE #parentTable TABLE
(
layoutDetailsId BIGINT,
parentId BIGINT,
syntax VARCHAR(50),
val VARCHAR(50)
);
INSERT INTO #parentTable
SELECT
ld.LayoutDetailsId,
ld.ParentId,
ld.DetailSyntax,
ld.DetailValue
FROM Alert.LayoutDetails ld
WHERE ld.ParentId = #parentId
AND ld.LayoutId = #layoutId;
SELECT
#currentHtml = '<' + pt.syntax + '>{0}</' + pt.syntax + '>',
#parentId = pt.layoutDetailsId
FROM #parentTable pt;
SELECT
#prevTopLevelHtml =
CASE CHARINDEX('{0}', #prevTopLevelHtml)
WHEN 0 THEN #currentHtml
ELSE STUFF(#prevTopLevelHtml, CHARINDEX('{0}', #prevTopLevelHtml), 3, #currentHtml)
END;
SELECT
#prevTopLevelHtml =
CASE
(SELECT COUNT(0)
FROM Alert.LayoutDetails ld
WHERE ld.ParentId = #parentId)
WHEN 0 THEN #prevTopLevelHtml
ELSE FnHtmlGenerator(#layoutId, #parentId, #prevTopLevelHtml)
END;
RETURN #prevTopLevelHtml;
END
RESULT:
html
---------------------------------------------------
<table><tbody><tr><td>{0}</td></tr></tbody></table>
The problem is. My function only trace 1 path. the first tr and first td even th is not traced. How to make it trace all path ?

Because when you call
SELECT
#currentHtml = '<' + pt.syntax + '>{0}</' + pt.syntax + '>',
#parentId = pt.layoutDetailsId
FROM #parentTable pt;
... that returns the last row in the table, and assigns that to the variables, so the path is 2/15/16/25/28
You may want to look at the recursive abilities of common table expressions (eg: http://msdn.microsoft.com/en-us/library/ms186243(v=sql.105).aspx) and/or the Hierarchical data type if your database platform supports it (SQL Server 2008+,)

Related

Scalar Function Coalesce Rows

I am trying to get a scalar function to work that combines all the rows of display values.
When I run my distinct query I get results I expect:
SELECT DISTINCT [tblMSC_Notes].[Display]
FROM [tblMSC_Notes]
INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID]
WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = 15))
ORDER BY [tblMSC_Notes].[Display]
Rows Displayed:
♦
ML
No Cloak
What I am trying to do now is output the Display as a single field "♦, ML, No Cloak"
Looking at the various examples I think I am close but I keep getting errors with the query:
BEGIN
-- Declare the return variable here
DECLARE #Result nvarchar(Max)
SET #Result = ''
WITH Display_CTE (Display)
AS
(
SELECT DISTINCT [tblMSC_Notes].[Display]
FROM [tblMSC_Notes]
INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID]
WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = #UnitID))
ORDER BY [tblMSC_Notes].[Display]
)
SET #Result = (SELECT COALESCE(#Result + ', ','') as DisplayResult
FROM (
SELECT [tblMSC_Notes].[Display]
FROM [tblMSC_Notes]
WHERE [Display] IN (SELECT Display FROM Display_CTE)
ORDER BY [tblMSC_Notes].[Display]
)
)
-- Return the result of the function
RETURN #Result
END
I was going to make this a scalar function in another query.
Here is some sample data from the tblMSC_Notes table:
ID UnitID NotesID
1 1513 154
2 1513 154
3 5032 152
4 5032 155
5 5033 152
6 5033 155
7 5033 43
8 5034 43
9 5034 152
10 5034 155
11 5035 152
12 5035 155
13 5035 43
Here is join using the Note ID
ID UnitID NotesID Display
1 1513 154 LB
2 1513 154 LB
3 5032 152 AL
4 5032 155 PL
5 5033 152 AL
6 5033 155 PL
7 5033 43 N
8 5034 43 N
9 5034 152 AL
10 5034 155 PL
Mohd - Thanks for the help so I would just need to change my create function to:
CREATE FUNCTION [dbo].[fnAnnex03_MasterShipChart_Notes] (
-- Add the parameters for the function here
#UnitID int
)
RETURNS nvarchar(Max)
AS
BEGIN
-- Declare the return variable here
DECLARE #Result nvarchar(Max)
SET #Result = ''
SET #Result = (SELECT string_agg(DISTINCT [tblMSC_Notes].[Display], ',') FROM [tblMSC_Notes] INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID] WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = #UnitID)) ORDER BY [tblMSC_Notes].[Display])
-- Return the result of the function
RETURN #Result
END
GO
Could you please provide a FIDDLE for this example with data.
You just need to use string_agg in this query as below:
SELECT string_agg(DISTINCT [tblMSC_Notes].[Display], ',')
FROM [tblMSC_Notes]
INNER JOIN [tblUnits_Notes] ON [tblUnits_Notes].[NotesID] = [tblMSC_Notes].[NoteID]
WHERE (([tblMSC_Notes].[Display] IS NOT NULL) AND ([tblUnits_Notes].[UnitID] = #UnitID))
ORDER BY [tblMSC_Notes].[Display]

Use PATINDEX to extract a substring in SQL Server?

I have some specific values I want to extract out of a string in SQL Server, but I'm not sure exactly how to get it done with PATINDEX.
Take this string:
declare #Command nvarchar(500) = 'IF dbo.SomeFunctionFn() = 1 BEGIN EXEC SomeStoredProcPR #RowsPerRun=500000, #RowsPerBatch=10000, #NbrDaysToKeepRpt=7 END'
I want to extract out the values of 500000 (for #RowsPerRun), 10000 for #RowsPerBatch and the value of 7 for #NbrDaysToKeepRpt. The values will be of variable length, so I can't guarantee the #RowsPerRun value will be 6 characters.
Is that possible?
DECLARE #Command NVARCHAR(500) =
'IF dbo.SomeFunctionFn() = 1 BEGIN EXEC SomeStoredProcPR #RowsPerRun=500000, #RowsPerBatch=10000, #NbrDaysToKeepRpt=7 END'
SELECT
SearchItem = srch.Txt,
ItemIndex = st.Pos,
ItemLen = t.Ln,
Item = SUBSTRING(pfx.Txt,1,t.Ln)
FROM (VALUES('#RowsPerRun='),('#RowsPerBatch='),('#NbrDaysToKeepRpt=')) AS srch(Txt)
CROSS APPLY (VALUES(CHARINDEX(srch.Txt,#Command),LEN(srch.Txt))) AS st(Pos,Ln)
CROSS APPLY (VALUES(SUBSTRING(#Command, st.Pos+st.Ln, 500))) AS pfx(Txt)
CROSS APPLY (VALUES(PATINDEX('%[^0-9]%',pfx.Txt)-1)) AS t(Ln);
Returns:
SearchItem ItemIndex ItemLen Item
------------------ ----------- ----------- --------
#RowsPerRun= 59 6 500000
#RowsPerBatch= 79 5 10000
#NbrDaysToKeepRpt= 100 1 7
Note that I included a few extra columns to help you understand what's happening.
Update: Against a table
This is how you would apply this logic to a series of values:
DECLARE #sometable TABLE (CommandId INT IDENTITY, Command NVARCHAR(500));
INSERT #sometable (Command)
VALUES
('IF dbo.SomeFunctionFn() = 1 BEGIN EXEC SomeStoredProcPR #RowsPerRun=500000, #RowsPerBatch=10000, #NbrDaysToKeepRpt=7 END'),
('IF dbo.SomeFunctionFn() = 5 BEGIN EXEC SomeStoredProcPR #RowsPerRun=123, #RowsPerBatch=500, #NbrDaysToKeepRpt=20 END'),
('IF dbo.SomeFunctionFn() = 5 BEGIN EXEC XXX #RowsPerRun=43, #RowsPerBatch=1000, #NbrDaysToKeepRpt=120 END'),
('IF dbo.SomeFunctionFn() = 5 BEGIN EXEC abc.yyy #RowsPerRun=43, #RowsPerBatch=1000, #NbrDaysToKeepRpt=120 END');
SELECT t.CommandId, f.SearchItem, f.Item
FROM #sometable AS t
CROSS APPLY
(
SELECT
SearchItem = srch.Txt,
ItemIndex = st.Pos,
ItemLen = t.Ln,
Item = SUBSTRING(pfx.Txt,1,t.Ln)
FROM (VALUES('#RowsPerRun='),('#RowsPerBatch='),('#NbrDaysToKeepRpt=')) AS srch(Txt)
CROSS APPLY (VALUES(CHARINDEX(srch.Txt,t.Command),LEN(srch.Txt))) AS st(Pos,Ln)
CROSS APPLY (VALUES(SUBSTRING(t.Command, st.Pos+st.Ln, 500))) AS pfx(Txt)
CROSS APPLY (VALUES(PATINDEX('%[^0-9]%',pfx.Txt)-1)) AS t(Ln)
) AS f;
Returns:
CommandId SearchItem Item
----------- ------------------ --------
1 #RowsPerRun= 500000
1 #RowsPerBatch= 10000
1 #NbrDaysToKeepRpt= 7
2 #RowsPerRun= 123
2 #RowsPerBatch= 500
2 #NbrDaysToKeepRpt= 20
3 #RowsPerRun= 43
3 #RowsPerBatch= 1000
3 #NbrDaysToKeepRpt= 120
4 #RowsPerRun= 43
4 #RowsPerBatch= 1000
4 #NbrDaysToKeepRpt= 120

SQL query to get multihierarchy items

in my SQL Table i have following data
ID Level Description Code MasterID
1 1 Cars AD0 NULL
2 1 Trucks JF1 NULL
3 1 Items YU2 NULL
4 2 New Cars AS3 1
5 2 Used Cars TG4 1
6 2 Car parts UJ5 1
7 2 New trucks OL6 2
8 2 Used trucks PL7 2
9 2 Truck parts KJL8 2
10 2 Factory stuff UY9 3
11 2 Custom stuff RT10 3
12 3 Toyota 6YH11 4
13 3 BMW 9OKH12 4
14 3 VW 13 5
15 3 Tiers Type I J14 6
16 3 Tiers Type II J15 6
17 3 Tiers Type III ADS16 9
18 3 Seats SA17 6
19 3 Doors UU18 6
20 3 Lights 9OL19 6
21 4 Left light GH20 20
22 4 Right light H21 20
23 4 Left door JHJ22 19
24 4 Michelin UY23 16
25 4 Vredestein GTF24 17
26 4 Dunlop 25 15
My achievement is to get all hierarchy data for each single item. For Exmaple, the outpu should look like as following
ID Level Description Code MasterId1 Description1 MasterId2 Description2 MasterId3 Description3
24 4 Michelin UY23 16 Tiers Type II 6 Car Parts 1 Cars
.
.
19 3 Doors UU18 6 Car Parts 1 Cars NULL NULL
.
.
10 2 Factory Stuff UY9 3 Items NULL NULL NULL NULL
.
.
3 1 Items NULL NULL NULL NULL NULL NULL NULL
.
.
If somebody can help or give an advise how to achieve this?
This is not dynamic but it could be pretty easily.
Using a recursive cte you can get the hierarchy for the entire table and self join a few times to get the table structure you want.
;WITH cte AS
(
SELECT *, ID AS [RootID], 1 AS [MasterLevel] FROM Table1
UNION ALL
SELECT t1.*, cte.[RootID], cte.[MasterLevel] + 1 FROM Table1 t1
JOIN cte ON t1.ID = cte.MasterID
)
SELECT r.ID, r.[Level], r.[Description], r.[Code],
m1.ID AS MasterId1, m1.[Description] AS Description1,
m2.ID AS MasterId2, m1.[Description] AS Description2,
m3.ID AS MasterId3, m1.[Description] AS Description3
FROM cte r
LEFT JOIN cte m1 ON m1.[RootID] = r.[RootID] AND m1.MasterLevel = 2
LEFT JOIN cte m2 ON m2.[RootID] = r.[RootID] AND m2.MasterLevel = 3
LEFT JOIN cte m3 ON m3.[RootID] = r.[RootID] AND m3.MasterLevel = 4
WHERE r.MasterLevel = 1
ORDER BY r.RootID DESC, r.MasterLevel
This would build a dynamic sql to get master and desciption fields based on the maximum Level value. or you could define how many levels you want to see by changing the #MaxLevel
DECLARE #Sql VARCHAR(MAX) = '',
#SelectSql VARCHAR(MAX) = '',
#JoinSql VARCHAR(MAX) = '',
#MaxLevel INT,
#idx INT = 1
SET #MaxLevel = (SELECT MAX([Level]) FROM Table1)
WHILE #idx < #MaxLevel
BEGIN
SET #SelectSql = #SelectSql + REPLACE(', m<index>.ID AS MasterId<index>, m<index>.[Description] AS Description<index> ', '<index>', #idx)
SET #JoinSql = #JoinSql + REPLACE(' LEFT JOIN cte m<index> ON m<index>.[RootID] = r.[RootID] AND m<index>.MasterLevel = <index> ', '<index>', #idx)
SET #idx = #idx + 1
END
SET #Sql = '
;WITH cte AS
(
SELECT *, ID AS [RootID], 0 AS [MasterLevel] FROM Table1
UNION ALL
SELECT t1.*, cte.[RootID], cte.[MasterLevel] + 1 FROM Table1 t1
JOIN cte ON t1.ID = cte.MasterID
)
SELECT r.ID, r.[Level], r.[Description], r.[Code]' + #SelectSql
+ 'FROM cte r ' + #JoinSql
+ 'WHERE r.MasterLevel = 0
ORDER BY r.RootID DESC, r.MasterLevel'
EXEC(#Sql)

SQL - How to select a column alias from another table?

I have a table in SQL with data and another table that holds the alias for that column. It is used for translation purposes.
I was wondering how can I do a select on those columns but retrieve the alias from another table?
This is the table that holds the real column names:
ID PageID ColName Order Type Width IsDeleted
1 7 CustType 2 NULL NULL 0
2 7 Description 3 NULL NULL 0
3 7 ApplyVAT 4 NULL NULL 0
4 7 ProduceInvoices 5 NULL NULL 0
5 7 PurchaseSale 6 NULL NULL 0
6 7 TermsDays 7 NULL NULL 0
7 7 DateTimeLastUpdated 8 NULL NULL 0
This is the table that holds the alias (text):
ID ColID UserID Text Order Enabled?
50 22 1 id 1 1
51 1 1 CustTypes 2 1
52 2 1 Description 3 1
53 3 1 ApplyVAT NULL 0
54 4 1 ProduceInvoices NULL 0
55 5 1 PurchaseSale NULL 0
56 6 1 TermsDays NULL 0
57 7 1 DateTimeLastUpdated NULL 0
I believe you will need to use dynamic sql to do this, e.g.:
DECLARE #Sql NVARCHAR(MAX);
SELECT TOP 1 #Sql = 'SELECT dt.ID as ' + at.IDAlias + ', dt.Town as ' + at.TownAlias
+ ' FROM DataTable dt'
FROM AliasTable at
WHERE at.LanguageID = 2;
EXEC(#Sql)
Given the example of Data Table
CREATE TABLE DataTable
(
ID INT,
Town NVARCHAR(50)
);
And a table holding language - dependent aliases for the columns in the above:
CREATE TABLE AliasTable
(
LanguageId INT,
IDAlias NVARCHAR(100),
TownAlias NVARCHAR(100)
);
SqlFiddle here
One of the (many) caveats with dynamic Sql is you will need to ensure that the alias data is validated against Sql Injectin attacks.

How to check id-s of parents and then set value

I have table like this :
ID object_id parent_id allowed
1 1 0 0
2 23 25 1
3 25 44 0
4 44 38 0
5 38 1 0
6 52 55 1
7 55 58 0
8 58 60 0
9 60 1 0
Now want select row-s where allowed = 1 and then set allowed = 1 for parents of the row which i select. For example it will be like :
step 1. select object_id , parent_id from myTbl where allowed = 1 Displays:
ID object_id parent_id allowed
2 23 25 1
6 52 55 1
step 2: It checks if the object_id is IN the parent_id from the above result and sets allowed = 1 when the object_id is equal to any of the parent_id's.
The exact same step2 repeats until it reaches a point where there is no match between object_id and parent_id
ID object_id parent_id allowed
2 23 25 1
6 52 55 1
3 25 44 0 --update to 1
7 55 58 0 -- update to 1
The exact same principle is being applied to the folling records, too:
for 25,44,1 - 44,38,0 (allowed is 0 want set 1) when set allowed = 1 it will be
44,38,1
for 55,58,1 - 58,60,0 (allowed is 0 want set 1) when set allowed = 1 it will be
58,60,1
How to do it ? In table My table contains multiple records with status allowed=1 and only 2 of them are used in this particular example.
Try:
UPDATE tbl
SET allowed = 1
FROM (SELECT *
FROM tbl
WHERE allowed = 0) A
INNER JOIN
(SELECT *
FROM tbl
WHERE allowed = 1) B
ON A.objectid = B.parentid