Hierarchical error - sql

Here is my code:
with t(branch_id, branch_code, branch, parent_branch_id, level) as
(
Select parent_b.BRANCH_ID, parent_b.BRANCH_CODE,
parent_b.BRANCH, parent_b.PARENT_BRANCH_ID, 0 as level
from table parent_b
Where parent_b.branch_Id is null--= 1
UNION ALL
Select child_b.BRANCH_ID, child_b.BRANCH_CODE,
child_b.BRANCH, child_b.PARENT_BRANCH_ID, (t.level+1)
from table child_b
INNER JOIN t parent
ON parent.branch_id = child_b.parent_branch_id
)
select * from t
And i get error in ouput. Where is the error, i can't find.
Error:
The multi-part identifier "t.level" could not be bound.

Change t.level to parent.level. You have given an alias to CTE as `parent'. you need to use that.
with t(branch_id, branch_code, branch, parent_branch_id, level) as
(
Select parent_b.BRANCH_ID, parent_b.BRANCH_CODE,
parent_b.BRANCH, parent_b.PARENT_BRANCH_ID, 0 as level
from table parent_b
Where parent_b.branch_Id is null--= 1
UNION ALL
Select child_b.BRANCH_ID, child_b.BRANCH_CODE,
child_b.BRANCH, child_b.PARENT_BRANCH_ID, (parent.level+1)
from table child_b
INNER JOIN t parent
ON parent.branch_id = child_b.parent_branch_id
)
select * from t

Related

How to find exact matching values in sql

How can I find the exact match in case of duplicate values in SQL
I am using the below query to find customers which are having write access based on the below values.
user **A write** user - {UI.ACCESS, API.ACCESS} and
user **B read** user - {UI.ACCESS, API.ACCESS, UI.READONLY, API.READONLY}
Query -
select ul.USERNAME,sp.PERMISSION_N,cus.ID
from CUSTOMER cus
join USER_L ul on cus.ID = ul.CUSTOMER_ID
join USER_ROLE ur on ul.ID = ur.USER_ID
join SECURITY_ROLE sr on ur.SECURITY_ROLE_ID = sr.SECURITY_ROLE_ID
join SECURITY_PERMISSION sp on sr.SECURITY_PERMISSION_ID = sp.ID
where sp.PERMISSION_NAME in ('UI.ACCESS','API.ACCESS')
above query return the B user as well but I am expecting only A.
You want to check across all the rows in the SECURITY_PERMISSION table that none of them have the forbidden roles.
You have tagged both Oracle and MySQL. In Oracle, you can use:
SELECT ul.USERNAME,
sp.permission_names,
cus.ID
FROM CUSTOMER cus
INNER JOIN USER_L ul
ON (cus.ID = ul.CUSTOMER_ID)
INNER JOIN USER_ROLE ur
ON (ul.ID = ur.USER_ID)
INNER JOIN SECURITY_ROLE sr
ON (ur.SECURITY_ROLE_ID = sr.SECURITY_ROLE_ID)
INNER JOIN (
SELECT id,
LISTAGG(permission_name, ',')
WITHIN GROUP (ORDER BY permission_name)
AS permission_names
FROM SECURITY_PERMISSION
GROUP BY id
HAVING COUNT(
CASE
WHEN PERMISSION_NAME in ('UI.ACCESS','API.ACCESS')
THEN 1
END
) > 0
AND COUNT(
CASE
WHEN PERMISSION_NAME in ('UI.READONLY', 'API.READONLY')
THEN 1
END
) = 0
) sp
ON (sr.SECURITY_PERMISSION_ID = sp.ID)
Which, for the sample data:
CREATE TABLE customer (id) AS
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE user_l (id, customer_id, username) AS
SELECT LEVEL, LEVEL, 'User' || LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE user_role (id, user_id, security_role_id) AS
SELECT LEVEL, LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE security_role (security_role_id, security_permission_id) AS
SELECT LEVEL, LEVEL FROM DUAL CONNECT BY LEVEL <= 2;
CREATE TABLE security_permission (id, permission_name) AS
SELECT 1, 'UI.ACCESS' FROM DUAL UNION ALL
SELECT 1, 'API.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'UI.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'API.ACCESS' FROM DUAL UNION ALL
SELECT 2, 'UI.READONLY' FROM DUAL UNION ALL
SELECT 2, 'API.READONLY' FROM DUAL;
Outputs:
USERNAME
PERMISSION_NAMES
ID
User1
API.ACCESS,UI.ACCESS
1
db<>fiddle here
For MySQL, you would need to replace LISTAGG with something to aggregate strings such as GROUP_CONCAT.

Hierarchical query outputs error

Here is my code:
with tbl as
(
Select parent_b.BRANCH_ID, parent_b.BRANCH_CODE,
parent_b.BRANCH, parent_b.PARENT_BRANCH_ID
from table parent_b
Where parent_b.branch_Id = 1
UNION ALL
Select child_b.BRANCH_ID, child_b.BRANCH_CODE,
child_b.BRANCH, child_b.PARENT_BRANCH_ID
from table child_b
INNER JOIN tbl parent
ON parent.branch_id = child_b.branch_id
)
select * from tbl
OPTION(MAXRECURSION 32767)
Above code gets me error with this message:
The statement terminated. The maximum recursion 100 has been exhausted
before statement completion.
I checked the table, and there no rows where row points to himself (infinity).
Where is my problem?
There's a logical error in the recursive part of your CTE - the join condition:
ON parent.branch_id = child_b.branch_id
is linking the parent's branch_id directly to the child's branch_id, when it should be linking to the child's parent_branch_id - like so:
ON parent.branch_id = child_b.parent_branch_id
The following query should work:
with tbl as
(
Select parent_b.BRANCH_ID, parent_b.BRANCH_CODE,
parent_b.BRANCH, parent_b.PARENT_BRANCH_ID
from table parent_b
Where parent_b.branch_Id = 1
UNION ALL
Select child_b.BRANCH_ID, child_b.BRANCH_CODE,
child_b.BRANCH, child_b.PARENT_BRANCH_ID
from table child_b
INNER JOIN tbl parent
ON parent.branch_id = child_b.parent_branch_id
)
select * from tbl
OPTION(MAXRECURSION 32767)
Specify the maxrecursion option at the end of the query:
select *
from tbl
option (maxrecursion 0)

SQL hierarchy count totals report

I'm creating a report with SQL server 2012 and Report Builder which must show the total number of Risks at a high, medium and low level for each Parent Element.
Each Element contains a number of Risks which are rated at a certain level. I need the total for the Parent Elements. The total will include the number of all the Child Elements and also the number the Element itself may have.
I am using CTEs in my query- the code I have attached isn't working (there are no errors - it's just displaying the incorrect results) and I'm not sure that my logic is correct??
Hopefully someone can help. Thanks in advance.
My table structure is:
ElementTable
ElementTableId(PK) ElementName ElementParentId
RiskTable
RiskId(PK) RiskName RiskRating ElementId(FK)
My query:
WITH cte_Hierarchy(ElementId, ElementName, Generation, ParentElementId)
AS (SELECT ElementId,
NAME,
0,
ParentElementId
FROM Extract.Element AS FirtGeneration
WHERE ParentElementId IS NULL
UNION ALL
SELECT NextGeneration.ElementId,
NextGeneration.NAME,
Parent.Generation + 1,
Parent.ElementId
FROM Extract.Element AS NextGeneration
INNER JOIN cte_Hierarchy AS Parent
ON NextGeneration.ParentElementId = Parent.ElementId),
CTE_HighRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS HighRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'High'
GROUP BY r.ElementId),
CTE_LowRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS LowRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'Low'
GROUP BY r.ElementId),
CTE_MedRisk
AS (SELECT r.ElementId,
Count(r.RiskId) AS MedRisk
FROM Extract.Risk r
WHERE r.RiskRating = 'Medium'
GROUP BY r.ElementId)
SELECT rd.ElementId,
rd.ElementName,
rd.ParentElementId,
Generation,
HighRisk,
MedRisk,
LowRisk
FROM cte_Hierarchy rd
LEFT OUTER JOIN CTE_HighRisk h
ON rd.ElementId = h.ElementId
LEFT OUTER JOIN CTE_MedRisk m
ON rd.ElementId = m.ElementId
LEFT OUTER JOIN CTE_LowRisk l
ON rd.ElementId = l.ElementId
WHERE Generation = 1
Edit:
Sample Data
ElementTableId(PK) -- ElementName -- ElementParentId
1 ------------------- Main --------------0
2 --------------------Element1-----------1
3 --------------------Element2 ----------1
4 --------------------SubElement1 -------2
RiskId(PK) RiskName RiskRating ElementId(FK)
a -------- Financial -- High ----- 2
b -------- HR --------- High ----- 3
c -------- Marketing -- Low ------- 2
d -------- Safety -----Medium ----- 4
Sample Output:
Element Name High Medium Low
Main ---------- 2 ---- 1 -------1
Here is your sample tables
SELECT * INTO #TABLE1
FROM
(
SELECT 1 ElementTableId, 'Main' ElementName ,0 ElementParentId
UNION ALL
SELECT 2,'Element1',1
UNION ALL
SELECT 3, 'Element2',1
UNION ALL
SELECT 4, 'SubElement1',2
)TAB
SELECT * INTO #TABLE2
FROM
(
SELECT 'a' RiskId, 'Fincancial' RiskName,'High' RiskRating ,2 ElementId
UNION ALL
SELECT 'b','HR','High',3
UNION ALL
SELECT 'c', 'Marketing','Low',2
UNION ALL
SELECT 'd', 'Safety','Medium',4
)TAB
We are finding the children of a parent, its count of High,Medium and Low and use cross join to show parent with all the combinations of its children's High,Medium and Low
UPDATE
The below variable can be used to access the records dynamically.
DECLARE #ElementTableId INT;
--SET #ElementTableId = 1
And use the above variable inside the query
;WITH CTE1 AS
(
SELECT *,0 [LEVEL] FROM #TABLE1 WHERE ElementTableId = #ElementTableId
UNION ALL
SELECT E.*,e2.[LEVEL]+1 FROM #TABLE1 e
INNER JOIN CTE1 e2 on e.ElementParentId = e2.ElementTableId
AND E.ElementTableId<>#ElementTableId
)
,CTE2 AS
(
SELECT E1.*,E2.*,COUNT(RiskRating) OVER(PARTITION BY RiskRating) CNT
from CTE1 E1
LEFT JOIN #TABLE2 E2 ON E1.ElementTableId=E2.ElementId
)
,CTE3 AS
(
SELECT DISTINCT T1.ElementName,C2.RiskRating,C2.CNT
FROM #TABLE1 T1
CROSS JOIN CTE2 C2
WHERE T1.ElementTableId = #ElementTableId
)
SELECT *
FROM CTE3
PIVOT(MIN(CNT)
FOR RiskRating IN ([High], [Medium],[Low])) AS PVTTable
SQL FIDDLE
RESULT
UPDATE 2
I am updating as per your new requirement
Here is sample table in which I have added extra data to test
SELECT * INTO #ElementTable
FROM
(
SELECT 1 ElementTableId, 'Main' ElementName ,0 ElementParentId
UNION ALL
SELECT 2,'Element1',1
UNION ALL
SELECT 3, 'Element2',1
UNION ALL
SELECT 4, 'SubElement1',2
UNION ALL
SELECT 5, 'Main 2',0
UNION ALL
SELECT 6, 'Element21',5
UNION ALL
SELECT 7, 'SubElement21',6
UNION ALL
SELECT 8, 'SubElement22',7
UNION ALL
SELECT 9, 'SubElement23',7
)TAB
SELECT * INTO #RiskTable
FROM
(
SELECT 'a' RiskId, 'Fincancial' RiskName,'High' RiskRating ,2 ElementId
UNION ALL
SELECT 'b','HR','High',3
UNION ALL
SELECT 'c', 'Marketing','Low',2
UNION ALL
SELECT 'd', 'Safety','Medium',4
UNION ALL
SELECT 'e' , 'Fincancial' ,'High' ,5
UNION ALL
SELECT 'f','HR','High',6
UNION ALL
SELECT 'g','HR','High',6
UNION ALL
SELECT 'h', 'Marketing','Low',7
UNION ALL
SELECT 'i', 'Safety','Medium',8
UNION ALL
SELECT 'j', 'Safety','High',8
)TAB
I have written the logic in query
;WITH CTE1 AS
(
-- Here you will find the level of every elements in the table
SELECT *,0 [LEVEL]
FROM #ElementTable WHERE ElementParentId = 0
UNION ALL
SELECT ET.*,CTE1.[LEVEL]+1
FROM #ElementTable ET
INNER JOIN CTE1 on ET.ElementParentId = CTE1.ElementTableId
)
,CTE2 AS
(
-- Filters the level and find the major parant of each child
-- ie, 100->150->200, here the main parent of 200 is 100
SELECT *,CTE1.ElementTableId MajorParentID,CTE1.ElementName MajorParentName
FROM CTE1 WHERE [LEVEL]=1
UNION ALL
SELECT CTE1.*,CTE2.MajorParentID,CTE2.MajorParentName
FROM CTE1
INNER JOIN CTE2 on CTE1.ElementParentId = CTE2.ElementTableId
)
,CTE3 AS
(
-- Since each child have columns for main parent id and name,
-- you will get the count of each element corresponding to the level you have selected directly
SELECT DISTINCT CTE2.MajorParentName,RT.RiskRating ,
COUNT(RiskRating) OVER(PARTITION BY MajorParentID,RiskRating) CNT
FROM CTE2
JOIN #RiskTable RT ON CTE2.ElementTableId=RT.ElementId
)
SELECT MajorParentName, ISNULL([High],0)[High], ISNULL([Medium],0)[Medium],ISNULL([Low],0)[Low]
FROM CTE3
PIVOT(MIN(CNT)
FOR RiskRating IN ([High], [Medium],[Low])) AS PVTTable
SQL FIDDLE

SQL Recursion: Get the main state off of a sub state

I found this nice example of SQL recursion with a CTE, but fail to apply it to my table:
http://walkingoncoals.blogspot.de/2009/12/fun-with-recursive-sql-part-1.html
I have the following table (ObjectStates):
ID Title ParentID
1 Draft null
2 Green null
3 Red null
4 Foo 1
5 Bar 4
I am trying to create a function which returns the "main" state when queried. Example:
GetMainState(5)
-- Shall return 1
GetMainState(4)
-- Shall return 1
GetMainState(2)
-- Shall return 2
I have so far:
CREATE FUNCTION [dbo].[GetMainObjectState] (#ObjectStateID INT)
RETURNS TABLE
AS
RETURN
(
WITH StateRecurcsion(ID, ParentID, Level) AS
(
SELECT ID, ParentID, 0
FROM ObjectStates
WHERE ID = #ObjectStateID
UNION ALL
SELECT uOS.ID, uOS.ParentID, sOS.Level+1
FROM ObjectStates uOS, StateRecurcsion sOS
WHERE uOS.ParentID= sOS.ID
)
SELECT os.ID, os.Title, sos.Level
FROM ObjectStates os, StateRecurcsion sos
WHERE os.ID = sos.ID
)
GO
I tried to create the function just as in the tutorial shown above, but somehow I'm not getting the correct results.
You could create a CTE containing a "root" value and then query it within your function e.g.:
;WITH CTEHierarchy
AS (
SELECT
ID
,0 AS LEVEL
,ID AS root
FROM ObjectStates
WHERE ParentID IS NULL
UNION ALL
SELECT
ObjectStates.ID
,LEVEL + 1 AS LEVEL
,[root]
FROM ObjectStates
INNER JOIN CTEHierarchy uh ON uh.id = ObjectStates.ParentID
)
SELECT [root]
FROM CTEHierarchy
WHERE ID = #ObjectStateID

How to get the deepest levels of a hierarchical sql query

I'm using SQLServer 2008.
Say I have a recursive hierarchy table, SalesRegion, whit SalesRegionId and ParentSalesRegionId. What I need is, given a specific SalesRegion (anywhere in the hierarchy), retrieve ALL the records at the BOTTOM level.
I.E.:
SalesRegion, ParentSalesRegionId
1, null
1-1, 1
1-2, 1
1-1-1, 1-1
1-1-2, 1-1
1-2-1, 1-2
1-2-2, 1-2
1-1-1-1, 1-1-1
1-1-1-2, 1-1-1
1-1-2-1, 1-1-2
1-2-1-1, 1-2-1
(in my table I have sequencial numbers, this dashed numbers are only to be clear)
So, if the user enters 1-1, I need to retrieve al records with SalesRegion 1-1-1-1 or 1-1-1-2 or 1-1-2-1 (and NOT 1-2-2). Similarly, if the user enters 1-1-2-1, I need to retrieve just 1-1-2-1
I have a CTE query that retrieves everything below 1-1, but that includes rows that I don't want:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, 1 AS Level /*Added as a workaround*/
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, Level + 1 AS Level
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
Thanks in advance!
Alejandro.
I found a quick way to do this, but I'd rather the answer to be in a single query. So if you can think of one, please share! If I like it better, I'll vote for it as the best answer.
I added a "Level" column in my previous query (I'll edit the question so this answer is clear), and used it to get the last level and then delete the ones I don't need.
INSERT INTO #SaleLocales
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
SELECT #LowestLevel = MAX(Level)
FROM #SaleLocales
DELETE #SaleLocales
WHERE Level <> #LowestLevel
Building off your post:
; WITH CTE AS
(
SELECT *
FROM SaleLocale_GetChilds(#SaleLocaleId)
)
SELECT
FROM CTE a
JOIN
(
SELECT MAX(level) AS level
FROM CTE
) b
ON a.level = b.level
There were a few edits in there. Kept hitting post...
Are you looking for something like this:
declare #SalesRegion as table ( SalesRegion int, ParentSalesRegionId int )
insert into #SalesRegion ( SalesRegion, ParentSalesRegionId ) values
( 1, NULL ), ( 2, 1 ), ( 3, 1 ),
( 4, 3 ), ( 5, 3 ),
( 6, 5 )
; with CTE as (
-- Get the root(s).
select SalesRegion, CAST( SalesRegion as varchar(1024) ) as Path
from #SalesRegion
where ParentSalesRegionId is NULL
union all
-- Add the children one level at a time.
select SR.SalesRegion, CAST( CTE.Path + '-' + cast( SR.SalesRegion as varchar(10) ) as varchar(1024) )
from CTE inner join
#SalesRegion as SR on SR.ParentSalesRegionId = CTE.SalesRegion
)
select *
from CTE
where Path like '1-3%'
I haven't tried this on a serious dataset, so I'm not sure how it'll perform, but I believe it solves your problem:
WITH SaleLocale_CTE AS (
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
WHERE SL.Deleted = 0
AND (#SaleLocaleId IS NULL OR SaleLocaleId = #SaleLocaleId)
UNION ALL
SELECT SL.SaleLocaleId, SL.SaleLocaleName, SL.AccountingLocationID, SL.LocaleTypeId, SL.ParentSaleLocaleId, CASE WHEN EXISTS (SELECT 1 FROM SaleLocal SL2 WHERE SL2.ParentSaleLocaleId = SL.SaleLocaleID) THEN 1 ELSE 0 END as HasChildren
FROM SaleLocale SL
INNER JOIN SaleLocale_CTE SLCTE ON SLCTE.SaleLocaleId = SL.ParentSaleLocaleId
WHERE SL.Deleted = 0
)
SELECT *
FROM SaleLocale_CTE
WHERE HasChildren = 0