Reputation: 325
[Apologies for the initial post without the DDL / DML statements to create the tables - now fixed]
I am creating a database which essentially logs the results of inspections of local facilities. An "inspection" may sometimes result in a "case" being raised, if it identifies failings. Cases can also be raised without an inspection, for example if a member of the public makes a complaint. One inspection can result in more than one case being raised - for example if the facility is a local park, it may be that an inspection results in a case being raised for (a) poor grass cutting, and (b) bins not being emptied.
A case cannot lead to an inspection, but it can lead to a further case, so in my Case table, I have DerivedFromInspectionID and DerivedFromCaseID columns.
There are two tables: Inspection and GMCase (for "Grounds Maintenance case" records)
The salient parts of the tables can be created as follows:
CREATE TABLE [Inspection](
[InspectionID] [int] NOT NULL PRIMARY KEY,
[InspectionDate] [datetime] NULL,
[InspectionType] [varchar](10) NULL
)
CREATE TABLE [GMCase](
[CaseID] [int] NOT NULL PRIMARY KEY,
[CaseDate] [datetime] NULL,
[CaseType] [varchar](20) NULL,
[DerivedFromCaseID] [int] NULL CONSTRAINT fk_Case_Case FOREIGN KEY REFERENCES GMCase (CaseID),
[DerivedFromInspectionID] [int] NULL CONSTRAINT fk_Case_Inspection FOREIGN KEY REFERENCES Inspection(InspectionID)
)
INSERT INTO Inspection(InspectionID, InspectionDate,InspectionType)
VALUES
(2,GETDATE(),'Scheduled'),
(3,GETDATE(),'Scheduled'),
(5,GETDATE(),'Ad-hoc')
INSERT INTO GMCase (CaseID,CaseDate,CaseType,DerivedFromCaseID,DerivedFromInspectionID)
VALUES
(1 , GETDATE(),'Compliment',NULL,NULL),
(2, GETDATE(),'Complaint',NULL,2),
(3,GETDATE(),'Instruction',2,NULL),
(5,GETDATE(),'Rectification',3,NULL),
(14,GETDATE(),'Complaint',NULL,2)
I want to create a view which will show the chain of
[Inspection1] (Optionally)
>>> Case1
>>> Case2
>>> Case3.
This is to be used within the database application, when looking at either Case or Inspection details.
In the application, I need to be able to look at any of the cases in the chain, and see how it fits within that chain. So, if I look at Case2, it should show all the items above.
In order to get the source data, I initially created a view containing a UNION query to bring together the equivalent columns from the Inspection and GMCase tables. As I needed to be able to identify each case/inspection, I brought in the CaseID and the InspectionID - initially into two separate columns, but I thought it would be useful to have a single ID column. I wanted to ensure that there wasn't apparent duplication in this ID (containing 2 for Inspection 2 but also for Case2), so I hit upon the idea of making the ID negative if it's the InspectionID (perhaps this is a mistake?)
I added a Level column to help identify whether a case is at a root level, or is derived from an inspection, and to help as a starting point for the level calculation within the CTE.
I also added a column called RootCase, which would, for Inspections, allow me to see which GMCase record the Inspection linked to, if any. This was to allow that if I look at Case 3, for example, I can see that it is ultimately derived from Inspection 2, because its root case would be the same RootCase of as the RootCase of Inspection 2. I'm also now wondering if this was a mistake...
The SQL for the view is as follows:
CREATE VIEW vwCaseHierarchySource
AS
SELECT CaseID,
CaseDate,
CaseType,
ISNULL(-g.DerivedFromInspectionID,g.DerivedFromCaseID) AS ParentCaseID,
CASE WHEN g.DerivedFromCaseID IS NULL AND g.DerivedFromInspectionID IS NULL THEN 0 ELSE 1 END AS [Level],
CaseID AS RootCase
FROM GMCase g
UNION
SELECT -i.InspectionID
,i.InspectionDate
,CONVERT(VARCHAR(20),i.InspectionType + ' inspection') AS CaseType
,NULL AS ParentCaseID
,CASE WHEN g.CaseID IS NULL THEN 0 ELSE 1 END AS [Level]
,g.CaseID AS RootCase
FROM Inspection i
LEFT JOIN GMCase g
ON i.InspectionID = g.DerivedFromInspectionID
I've then attempted numerous ways of creating a CTE to show this hierarchy. CTEs are not my forte, and I can't remember all the things I've tried, but below is my current effort.
The problem I have is, I suspect, due to the fact that an inspection can lead (and in my sample data does lead) to more than one case, because whenever I use the CTE, I get the correct information, but also incorrect information. If I try to get the results for Case 2, I get the hierarchy for case 2, but also the row for case 14. And if I try for case 14, I get that, but also all the rows for case 2.
Here's the CTE:
CREATE VIEW [dbo].[vwCaseInspectionHierarchy]
AS
WITH CaseList AS
(
SELECT
CaseID,
CaseDate,
CaseType,
ParentCaseID,
RootCase,
RootCase.[Level]
FROM vwCaseHierarchySource RootCase
WHERE RootCase.ParentCaseID IS NULL
UNION ALL
SELECT
ChildCase.CaseID,
ChildCase.CaseDate,
ChildCase.CaseType,
ChildCase.ParentCaseID,
CaseList.RootCase,
CaseList.[Level]+1
FROM vwCaseHierarchySource ChildCase
INNER JOIN CaseList ON
ChildCase.ParentCaseID=CaseList.CaseID
)
SELECT *
,REPLICATE(' ',[Level]*5) + CaseType AS IndentedCaseType
FROM CaseList
And my code to test it:
SELECT *
FROM [vwCaseInspectionHierarchy]
WHERE RootCase=2
Can anyone help me? Apologies for a long post, but hopefully I've included everything relevant...
Andrew
Upvotes: 2
Views: 222
Reputation: 29657
I don't think you actually need that view.
Just a recursive CTE on the tables themselves should do the trick.
And you can seed the recursive with the type string from both tables.
Example snippet:
declare @Inspection table (InspectionID int primary key, InspectionDate datetime, InspectionType varchar(30));
declare @GMCase table (CaseID int primary key, CaseDate date, CaseType varchar(30), DerivedFromCaseID int, DerivedFromInspectionID int);
insert into @Inspection (InspectionID, InspectionDate, InspectionType) values
(102, '2019-01-22 00:00:00.000', 'Scheduled'),
(103, '2019-02-06 14:25:55.133', 'Scheduled'),
(105, '2019-02-06 16:59:04.820', 'Ad-hoc');
insert into @GMCase (CaseID, CaseDate, CaseType, DerivedFromCaseID, DerivedFromInspectionID) values
(1, '2019-01-23', 'Compliment', NULL, NULL),
(2, '2018-12-04', 'Complaint', NULL, 102),
(3, '2018-12-04', 'Instruction', 2, NULL),
(5, '2018-12-21', 'Rectification', 3, NULL),
(14, '2019-02-06', 'Complaint', NULL, 2),
(15, '2019-02-07', 'Compliment', NULL, NULL),
(16, '2019-02-08', 'Complaint', 15, NULL);
with RCTE as
(
-- Seeding the recursive CTE
select i.InspectionID, n as Lvl, t.CaseID as RootCaseID, case when n = 1 then t.CaseID end as CaseID, t.DerivedFromCaseID, iif(n=0, i.InspectionType, t.CaseType) as [Type]
from @GMCase t
left join @Inspection i ON i.InspectionID = t.DerivedFromInspectionID
cross join (values (0),(1)) nums(n)
where t.DerivedFromCaseID is null
union all
select r.InspectionID, r.Lvl + 1, r.RootCaseID, t.CaseID, t.DerivedFromCaseID, t.CaseType
from RCTE r
join @GMCase t on t.DerivedFromCaseID = r.CaseID
)
select c.RootCaseID, c.CaseID, c.InspectionID, t.CaseDate,
concat(replicate(' ',c.Lvl*5),COALESCE([Type],'Inspection')) AS IndentedType
from RCTE c
left join @GMCase t on t.CaseID = c.CaseID
order by c.RootCaseID, c.Lvl;
Result:
RootCaseID CaseID InspectionID CaseDate IndentedType
1 NULL NULL NULL Inspection
1 1 NULL 2019-01-23 Compliment
2 NULL 102 NULL Scheduled
2 2 102 2018-12-04 Complaint
2 3 102 2018-12-04 Instruction
2 5 102 2018-12-21 Rectification
14 NULL NULL NULL Inspection
14 14 NULL 2019-02-06 Complaint
15 NULL NULL NULL Inspection
15 15 NULL 2019-02-07 Compliment
15 16 NULL 2019-02-08 Complaint
Upvotes: 1