Check hierarchically for parent number - sql-server

Table that I am using is CADIGR, and column DIGR_KEY1 is always a parent number (DIGR_KEY of the parent) in the row.
I must update CADIGR DIGR_KEY1 (parent number), but first I must check that I am not updating child with one of theirs parent.
I really don't have idea how to do this? Child can have a lot of parents, so check must be done on the whole tree up to the root (where DIGR_KEY1 IS NULL).
Only if the number does not exist as parent number of that child's tree, update is possible.
CREATE TABLE CADIGR
(
DIGR_KEY INT,
DIGR_KEY1 INT
)
INSERT INTO CADIGR (DIGR_KEY, DIGR_KEY1)
VALUES
(1, NULL),
(2,1),
(3,1),
(4,1),
(11,4),
(12,4),
(13,4),
(5,2),
(6,2),
(5,2),
(6,2),
(7,5),
(8,5),
(9,5),
(10,5),
(14,3),
(15,3)

You can use a recursive query like this, can use level to perform operations on each group of childs.
WITH Rec as
(
SELECT digr_key, digr_key1, 1 AS level
FROM CADIGR
WHERE digr_key1 is null
UNION ALL
SELECT c.digr_key, c.digr_key1, level = level + 1
FROM CADIGR c
INNER JOIN rec
ON c.digr_key1 = rec.digr_key
)
SELECT *
FROM Rec;
GO
digr_key | digr_key1 | level
-------: | --------: | ----:
1 | null | 1
2 | 1 | 2
3 | 1 | 2
4 | 1 | 2
11 | 4 | 3
12 | 4 | 3
13 | 4 | 3
14 | 3 | 3
15 | 3 | 3
5 | 2 | 3
6 | 2 | 3
5 | 2 | 3
6 | 2 | 3
7 | 5 | 4
8 | 5 | 4
9 | 5 | 4
10 | 5 | 4
7 | 5 | 4
8 | 5 | 4
9 | 5 | 4
10 | 5 | 4
dbfiddle here
Avoiding duplicated records
WITH DistData as
(
SELECT DISTINCT digr_key, digr_key1
FROM CADIGR
)
, Rec as
(
SELECT digr_key, digr_key1, 1 AS level
FROM DistData
WHERE digr_key1 is null
UNION ALL
SELECT c.digr_key, c.digr_key1, level = level + 1
FROM DistData c
INNER JOIN rec
ON c.digr_key1 = rec.digr_key
)
SELECT *
FROM Rec;
GO
digr_key | digr_key1 | level
-------: | --------: | ----:
1 | null | 1
2 | 1 | 2
3 | 1 | 2
4 | 1 | 2
11 | 4 | 3
12 | 4 | 3
13 | 4 | 3
14 | 3 | 3
15 | 3 | 3
5 | 2 | 3
6 | 2 | 3
7 | 5 | 4
8 | 5 | 4
9 | 5 | 4
10 | 5 | 4
dbfiddle here

Related

Consolidating 2 Recursive CTE Statements

The following statement uses 2 separate recursive CTE statements to build a list of steps from a starting location to an ending location based on trips. The desired output is correct, however I am wondering if it is possible to consolidate the 2 CTE statements into one.
The difficulty I am having is relating the endLocation to the startLocation in the first recursive iteration cte1.
The database is SQL Server 2017. I have added the SQL fiddle below:
[SQL Fiddle][1]
SQL Server 2017 Schema Setup:
Create Table TripLocation
(TripID int,
LocationID int,
StopOrder int
)
Create table FromTo
(tripID int,
fromLocationID int,
fromStopOrder int,
toLocationID int,
toStopOrder int
)
Create table cte1Temp
(startTripID int,
startLocationID int,
tripID int,
fromLocationID int,
toLocationID int,
step int)
Create table cte2Temp
(startTripID int,
startLocationID int,
endLocationID int,
tripID int,
fromLocationID int,
toLocationID int,
step int)
--LIST OF LOCATIONS FOR EACH TRIP
Insert into TripLocation
Values
(1,1,0),
(1,2,1),
(1,1,2),
(2,2,0),
(2,3,1),
(2,2,2),
(3,3,0),
(3,4,1),
(3,3,2)
--LIST OF POSSIBLE TO/FROM COMBINATIONS FOR EACH TRIP BASED ON STOPORDER
insert into FromTo
select
FromLocation.tripID,
FromLocation.LocationID [fromLocationID],
FromLocation.StopOrder [fromStopOrder],
ToLocation.LocationID [toLocationID],
ToLocation.StopOrder [toStopOrder]
from
TripLocation FromLocation
join TripLocation ToLocation
on FromLocation.tripID = ToLocation.tripID
and ToLocation.StopOrder >= FromLocation.StopOrder
and FromLocation.LocationID <> ToLocation.LocationID
;
--FIND ALL POSSIBLE END LOCATIONS FOR EACH START LOCATION IF TRIPS SHARE A COMMON LOCATION
with cte1 as
(
select
tripID [startTripID],
fromLocationID [startLocationID],
tripID,
fromLocationID,
toLocationID,
1 [step]
from
FromTo
union all
select
anchor.startTripID,
anchor.startLocationID,
member.tripID,
member.fromLocationID,
member.toLocationID,
anchor.step + 1 [step]
from
FromTo member
join cte1 anchor
on anchor.toLocationID = member.fromLocationID
and member.toLocationID <> anchor.fromLocationID
and member.tripID <> anchor.tripID
)
insert into cte1Temp
select
*
from
cte1
;
--GENERATE PLAN FOR EACH START LOCATION TO AN END LOCATION
with cte2 as
(
select
startTripID,
StartLocationID,
ToLocationID [EndLocationID],
tripID,
FromLocationID,
ToLocationID,
step
from
cte1Temp
union all
select
b.startTripID,
b.StartLocationID,
b.ToLocationID,
a.tripID,
a.FromLocationID,
a.ToLocationID,
a.step
from
cte1Temp b
join CTE2 a
on a.endLocationID = b.FromLocationID
and a.startLocationID = b.startLocationID
)
insert into cte2Temp
select
*
from
cte2
Query 1:
select
*
from
cte2Temp
order by
startlocationID, endLocationID, step
Results:
| startTripID | startLocationID | endLocationID | tripID | fromLocationID | toLocationID | step |
|-------------|-----------------|---------------|--------|----------------|--------------|------|
| 1 | 1 | 2 | 1 | 1 | 2 | 1 |
| 1 | 1 | 3 | 1 | 1 | 2 | 1 |
| 1 | 1 | 3 | 2 | 2 | 3 | 2 |
| 1 | 1 | 4 | 1 | 1 | 2 | 1 |
| 1 | 1 | 4 | 2 | 2 | 3 | 2 |
| 1 | 1 | 4 | 3 | 3 | 4 | 3 |
| 1 | 2 | 1 | 1 | 2 | 1 | 1 |
| 2 | 2 | 3 | 2 | 2 | 3 | 1 |
| 2 | 2 | 4 | 2 | 2 | 3 | 1 |
| 2 | 2 | 4 | 3 | 3 | 4 | 2 |
| 2 | 3 | 1 | 2 | 3 | 2 | 1 |
| 2 | 3 | 1 | 1 | 2 | 1 | 2 |
| 2 | 3 | 2 | 2 | 3 | 2 | 1 |
| 3 | 3 | 4 | 3 | 3 | 4 | 1 |
| 3 | 4 | 1 | 3 | 4 | 3 | 1 |
| 3 | 4 | 1 | 2 | 3 | 2 | 2 |
| 3 | 4 | 1 | 1 | 2 | 1 | 3 |
| 3 | 4 | 2 | 3 | 4 | 3 | 1 |
| 3 | 4 | 2 | 2 | 3 | 2 | 2 |
| 3 | 4 | 3 | 3 | 4 | 3 | 1 |
You can try this
Create Table TripLocation
(TripID int,
LocationID int,
StopOrder int
)
Create table FromTo
(tripID int,
fromLocationID int,
fromStopOrder int,
toLocationID int,
toStopOrder int
)
Create table cte1Temp
(startTripID int,
startLocationID int,
tripID int,
fromLocationID int,
toLocationID int,
step int,
path varchar(max)
)
--LIST OF LOCATIONS FOR EACH TRIP
Insert into TripLocation
Values
(1,1,0),
(1,2,1),
(1,1,2),
(2,2,0),
(2,3,1),
(2,2,2),
(3,3,0),
(3,4,1),
(3,3,2)
--LIST OF POSSIBLE TO/FROM COMBINATIONS FOR EACH TRIP BASED ON STOPORDER
insert into FromTo
select
FromLocation.tripID,
FromLocation.LocationID [fromLocationID],
FromLocation.StopOrder [fromStopOrder],
ToLocation.LocationID [toLocationID],
ToLocation.StopOrder [toStopOrder]
from
TripLocation FromLocation
join TripLocation ToLocation
on FromLocation.tripID = ToLocation.tripID
and ToLocation.StopOrder >= FromLocation.StopOrder
and FromLocation.LocationID <> ToLocation.LocationID
;
--FIND ALL POSSIBLE END LOCATIONS FOR EACH START LOCATION IF TRIPS SHARE A COMMON LOCATION
with cte1 as
(
select
tripID [startTripID],
fromLocationID [startLocationID],
tripID,
fromLocationID,
toLocationID,
1 [step],
cast('1_' + ltrim(str(tripID)) + '-' + ltrim(str(tolocationID)) as varchar(max)) [path]
from
FromTo
union all
select
anchor.startTripID,
anchor.startLocationID,
member.tripID,
member.fromLocationID,
member.toLocationID,
anchor.step + 1 [step],
anchor.path + ',' + ltrim(str(anchor.step + 1)) + '_' + + ltrim(str(member.tripID)) + '-' + ltrim(str(member.toLocationID))
from
FromTo member
join cte1 anchor
on anchor.toLocationID = member.fromLocationID
and member.toLocationID <> anchor.fromLocationID
and member.tripID <> anchor.tripID
)
insert into cte1Temp
select
*
from
cte1
;
select
StartLocationID,
TolocationId,
Substring(Value, 1,Charindex('-', Value)-1) as Trip,
Substring(Value, Charindex('-', Value)+1, LEN(Value)) as CrossingLocationID
from
cte1Temp
CROSS APPLY STRING_SPLIT(path, ',')
order by StartLocationId, ToLocationId, Trip
Results:
StartLocationID TolocationId Trip CrossingLocationID
1 2 1_1 2
1 3 1_1 2
1 3 2_2 3
1 4 1_1 2
1 4 2_2 3
1 4 3_3 4
2 1 1_1 1
2 3 1_2 3
2 4 1_2 3
2 4 2_3 4
3 1 1_2 2
3 1 2_1 1
3 2 1_2 2
3 4 1_3 4
4 1 1_3 3
4 1 2_2 2
4 1 3_1 1
4 2 1_3 3
4 2 2_2 2
4 3 1_3 3

Extract Data by comparing 4 different rows

The Table data is as below, need to extract records that met the below conditions
Here Value = Value2-Value1
Value of two days back data should be > 2
Value of last day data is < 0
Value of next day data is < 4 and >0
Value of after next day data > 4
All the dates are weekdays,if any date falls on friday, need to compare with next day ie.. Monday. and comparision is with alternative days only
from below output have to be.
1 4-1-2018 15 18
2 3-1-2018 3 0
-----------------------------------
code Date Value1 Value2
---------------------------------------
1 1-1-2018 13 14
1 2-1-2018 14 18
1 3-1-2018 15 11
1 4-1-2018 15 18
1 5-1-2018 15 18
1 6-1-2018 11 18
1 7-1-2018 15 18
2 1-1-2019 1 3
2 2-1-2018 2 5
2 3-1-2018 3 0
2 4-1-2018 3 7
2 5-1-2018 3 4
2 6-1-2018 3 9
2 7-1-2018 3 7
I am pretty much confused at comparing multiple rows, any help is greatly appreciated.
Starting with v2012 we have support for LAG() and LEAD(). Try this out:
SET DATEFORMAT dmy;
DECLARE #tbl TABLE(code INT,[Date] DATE,Value1 INT,Value2 INT);
INSERT INTO #tbl VALUES
(1,'1-1-2018',13,14)
,(1,'2-1-2018',14,18)
,(1,'3-1-2018',15,11)
,(1,'4-1-2018',15,18)
,(1,'5-1-2018',15,18)
,(1,'6-1-2018',11,18)
,(1,'7-1-2018',15,18)
,(2,'1-1-2019', 1, 3)
,(2,'2-1-2018', 2, 5)
,(2,'3-1-2018', 3, 0)
,(2,'4-1-2018', 3, 7)
,(2,'5-1-2018', 3, 4)
,(2,'6-1-2018', 3, 9)
,(2,'7-1-2018', 3, 7);
WITH cte AS
(
SELECT *
,LAG(Value2-Value1,2) OVER(PARTITION BY code ORDER BY [Date]) TwoDaysBack
,LAG(Value2-Value1,1) OVER(PARTITION BY code ORDER BY [Date]) Yesterday
,LEAD(Value2-Value1,1) OVER(PARTITION BY code ORDER BY [Date]) tomorrow
,LEAD(Value2-Value1,2) OVER(PARTITION BY code ORDER BY [Date]) TwoDaysAhead
FROM #tbl
)
SELECT *
FROM cte;
I do not really understand, how you want to use these values in a filter to get the expected output. If you need help with this, just come back...
The result
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| code | Date | Value1 | Value2 | TwoDaysBack | Yesterday | tomorrow | TwoDaysAhead |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-01 | 13 | 14 | NULL | NULL | 4 | -4 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-02 | 14 | 18 | NULL | 1 | -4 | 3 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-03 | 15 | 11 | 1 | 4 | 3 | 3 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-04 | 15 | 18 | 4 | -4 | 3 | 7 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-05 | 15 | 18 | -4 | 3 | 7 | 3 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-06 | 11 | 18 | 3 | 3 | 3 | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 1 | 2018-01-07 | 15 | 18 | 3 | 7 | NULL | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-02 | 2 | 5 | NULL | NULL | -3 | 4 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-03 | 3 | 0 | NULL | 3 | 4 | 1 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-04 | 3 | 7 | 3 | -3 | 1 | 6 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-05 | 3 | 4 | -3 | 4 | 6 | 4 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-06 | 3 | 9 | 4 | 1 | 4 | 2 |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2018-01-07 | 3 | 7 | 1 | 6 | 2 | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
| 2 | 2019-01-01 | 1 | 3 | 6 | 4 | NULL | NULL |
+------+------------+--------+--------+-------------+-----------+----------+--------------+
The idea in short:
Both, LAG() and LEAD() take an argument for the needed value, a second, how many rowswe want to skip, and as a third argument a default value you might specify to avoid NULLs in the result, when there is no row in scope.
The OVER() clause will tell any windowing function if we want to think of the set as divided in groups and the sort order (otherwise the system would not know what is leading or lagging.
With lead and lag window functions:
with cte as (
select *,
lag(value2 - value1, 2) over (partition by code order by date) prev2,
lag(value2 - value1, 1) over (partition by code order by date) prev1,
lead(value2 - value1, 1) over (partition by code order by date) next1,
lead(value2 - value1, 2) over (partition by code order by date) next2
from tablename
)
select code, date, value1, value2
from cte
where prev2 > 2 and prev1 < 0 and next1 > 0 and next1 < 4 and next2 > 4
See the demo.
Results:
code | date | value1 | value2
---: | :------------------ | -----: | -----:
1 | 01/04/2018 00:00:00 | 15 | 18
2 | 01/04/2018 00:00:00 | 3 | 7
There is a difference between your expected results for code = 2 and my results, so check its validity.

Same ID in multiple rows using SQL Server

I am working on two tables with column headers ID_1, X1, Value as below
Table 1:
ID_1| X1 | Value
A01 | A | 10
A01 | B | 5
A02 | B | 3
A03 | A | 4
A02 | A | 8
Table 2:
ID_1| X1 | Value
Z01 | A | 7
Z01 | B | 2
Z01 | C | 9
Z03 | A | 1
Z02 | B | 3
Z03 | B | 6
I want to combine these two tables with and additional column with header N at the beginning as below (Expected Output):
N |ID_1| X1 | Value
1 |A01 | A | 10
1 |A01 | B | 5
2 |A02 | B | 3
2 |A02 | A | 8
3 |A03 | A | 4
4 |Z01 | A | 7
4 |Z01 | B | 2
4 |Z01 | C | 9
5 |Z02 | B | 3
6 |Z03 | A | 1
6 |Z03 | B | 6
I think you want union all and dense_rank():
select dense_rank() over (order by id_1) as n,
id_1, x, value
from ((select id_1, x, value from t1
) union all
(select id_1, x, value from t2
)
) t;

Create Tree Query From Numeric Mapping Table in SQL (Specific Format)

I have an exported table from accounting software like below.
AccountID AccountName
--------- -----------
11 Acc11
12 Acc12
13 Acc13
11/11 Acc11/11
11/12 Acc11/12
11/111 Acc11/111
11/11/001 Acc11/11/001
11/11/002 Acc11/11/002
12/111 Acc12/111
12/112 Acc12/112
I want to convert it to tree query in MS-SQL Server 2008 to use it as a Treelist datasource in my win aaplication.
I raised this question before and it's answered with a way that it was very very slow for my big table with more than 5000 records (Create Tree Query From Numeric Mapping Table in SQL). But I think counting "/" and separating AccountID field with "/" can solve my problem easier and very faster.
Anyway, My expected result must be like below:
AccountID AccountName ID ParentID Level HasChild
--------- ----------- --- --------- ------ --------
11 Acc11 1 Null 1 1
12 Acc12 2 Null 1 1
13 Acc13 3 Null 1 0
11/11 Acc11/11 4 1 2 1
11/12 Acc11/12 5 1 2 0
11/111 Acc11/111 6 1 2 0
11/11/001 Acc11/11/001 7 4 3 0
11/11/002 Acc11/11/002 8 4 3 0
12/111 Acc12/111 9 2 2 0
12/112 Acc12/112 10 2 2 0
Please Help Me.
I modified my answer given in the first question...
It would be best, if your table would keep the relation data directly in indexed columns. Before you change your table's structure you might try this:
A table with test data
DECLARE #tbl TABLE ( AccountID VARCHAR(100), AccountName VARCHAR(100));
INSERT INTO #tbl VALUES
('11','Acc11')
,('12','Acc12')
,('13','Acc13')
,('11/11','Acc11/11')
,('11/12','Acc11/12')
,('11/111','Acc11/111')
,('11/11/001','Acc11/11/001')
,('11/11/002','Acc11/11/002')
,('12/111','Acc12/111')
,('12/112','Acc12/112');
This will get the needed data into a newly created temp table called #tempHierarchy
SELECT AccountID
,AccountName
,ROW_NUMBER() OVER(ORDER BY LEN(AccountID)-LEN(REPLACE(AccountID,'/','')),AccountID) AS ID
,Extended.HierarchyLevel
,STUFF(
(
SELECT '/' + A.B.value('.','varchar(10)')
FROM Extended.IDsXML.nodes('/x[position() <= sql:column("HierarchyLevel")]') AS A(B)
FOR XML PATH('')
),1,2,'') AS ParentPath
,Extended.IDsXML.value('/x[sql:column("HierarchyLevel")+1][1]','varchar(10)') AS ownID
,Extended.IDsXML.value('/x[sql:column("HierarchyLevel")][1]','varchar(10)') AS ancestorID
INTO #tempHierarchy
FROM #tbl
CROSS APPLY(SELECT LEN(AccountID)-LEN(REPLACE(AccountID,'/','')) + 1 AS HierarchyLevel
,CAST('<x></x><x>' + REPLACE(AccountID,'/','</x><x>') + '</x>' AS XML) AS IDsXML) AS Extended
;
The intermediate result
+-----------+--------------+----+----------------+------------+-------+------------+
| AccountID | AccountName | ID | HierarchyLevel | ParentPath | ownID | ancestorID |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11 | Acc11 | 1 | 1 | | 11 | |
+-----------+--------------+----+----------------+------------+-------+------------+
| 12 | Acc12 | 2 | 1 | | 12 | |
+-----------+--------------+----+----------------+------------+-------+------------+
| 13 | Acc13 | 3 | 1 | | 13 | |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/11 | Acc11/11 | 4 | 2 | 11 | 11 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/111 | Acc11/111 | 5 | 2 | 11 | 111 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/12 | Acc11/12 | 6 | 2 | 11 | 12 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 12/111 | Acc12/111 | 7 | 2 | 12 | 111 | 12 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 12/112 | Acc12/112 | 8 | 2 | 12 | 112 | 12 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/11/001 | Acc11/11/001 | 9 | 3 | 11/11 | 001 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
| 11/11/002 | Acc11/11/002 | 10 | 3 | 11/11 | 002 | 11 |
+-----------+--------------+----+----------------+------------+-------+------------+
And now a similar recursive approach takes place as in my first answer. But - as it is using a real table now and all the string splitting has taken place already - it should be faster...
WITH RecursiveCTE AS
(
SELECT th.*
,CAST(NULL AS BIGINT) AS ParentID
,CASE WHEN EXISTS(SELECT 1 FROM #tempHierarchy AS x WHERE x.ParentPath=th.AccountID) THEN 1 ELSE 0 END AS HasChild
FROM #tempHierarchy AS th WHERE th.HierarchyLevel=1
UNION ALL
SELECT sa.AccountID
,sa.AccountName
,sa.ID
,sa.HierarchyLevel
,sa.ParentPath
,sa.ownID
,sa.ancestorID
,(SELECT x.ID FROM #tempHierarchy AS x WHERE x.AccountID=sa.ParentPath)
,CASE WHEN EXISTS(SELECT 1 FROM #tempHierarchy AS x WHERE x.ParentPath=sa.AccountID) THEN 1 ELSE 0 END AS HasChild
FROM RecursiveCTE AS r
INNER JOIN #tempHierarchy AS sa ON sa.HierarchyLevel=r.HierarchyLevel+1
AND r.AccountID=sa.ParentPath
)
SELECT r.AccountID
,r.AccountName
,r.ID
,r.ParentID
,r.HierarchyLevel
,r.HasChild
FROM RecursiveCTE AS r
ORDER BY HierarchyLevel,ParentID;
And finally I clean up
DROP TABLE #tempHierarchy;
And here's the final result
+-----------+--------------+----+----------+----------------+----------+
| AccountID | AccountName | ID | ParentID | HierarchyLevel | HasChild |
+-----------+--------------+----+----------+----------------+----------+
| 11 | Acc11 | 1 | NULL | 1 | 1 |
+-----------+--------------+----+----------+----------------+----------+
| 12 | Acc12 | 2 | NULL | 1 | 1 |
+-----------+--------------+----+----------+----------------+----------+
| 13 | Acc13 | 3 | NULL | 1 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/11 | Acc11/11 | 4 | 1 | 2 | 1 |
+-----------+--------------+----+----------+----------------+----------+
| 11/111 | Acc11/111 | 5 | 1 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/12 | Acc11/12 | 6 | 1 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 12/111 | Acc12/111 | 7 | 2 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 12/112 | Acc12/112 | 8 | 2 | 2 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/11/001 | Acc11/11/001 | 9 | 4 | 3 | 0 |
+-----------+--------------+----+----------+----------------+----------+
| 11/11/002 | Acc11/11/002 | 10 | 4 | 3 | 0 |
+-----------+--------------+----+----------+----------------+----------+

Flatten SQL for 4 tables

In SQL Server 2012, I have four tables that look like:
Issues:
IssueID | IssueTitle
1 | Light Bulb Burnt Out
2 | Thermostat not working
LocationTypes:
TypeID | Type
1 | Building
2 | Floor
3 | Room
Locations:
LocaltionID | TypeID | Location | ParentLocation
0 | 1 | default | 0
1 | 1 | Sears Tower | 0
2 | 1 | IDS | 0
3 | 2 | Floor 1 | 1
4 | 2 | Floor 2 | 1
5 | 2 | Floor 3 | 1
6 | 2 | Floor 4 | 1
7 | 2 | Floor 5 | 1
8 | 2 | Floor 6 | 1
9 | 2 | Floor 7 | 1
10 | 2 | Floor 8 | 1
108 | 3 | Room 101 | 3
109 | 3 | Room 102 | 3
110 | 3 | Room 110 | 3
111 | 3 | Room 202 | 4
112 | 3 | Room 300 | 5
175 | 2 | 1st Floor | 2
185 | 2 | 2nd Floor | 2
186 | 3 | Suite 295 | 185
IssueLocations:
IssueID | LocationId
1 | 1
1 | 5
1 | 112
2 | 2
2 | 185
And what I want to do is combine the tables so that I end up with one row for each issuer, with field names as column headers and the field values, so I end up with:
Result:
IssueID | IssueTitle | Building | Floor | Room
--------------------------------------------------------------------------
1 | Light Bulb Burnt Out | Sears Tower | Floor 1 | Room 300
2 | Thermostat not working | IDS | 2nd Floor |
Notice the second issue doesn't have a room (no locations are required), location less issues are valid. Note other constraints might cause a required location but I don't think that is not relevant for this question.
You need to use Pivot to transpose your rows to columns.
SQL FIDDLE DEMO
SELECT *
FROM (SELECT il.IssueID,
l.Location,
i.IssueTitle,
lt.Type
FROM Locations l
JOIN LocationTypes lt
ON l.TypeID = lt.TypeID
JOIN IssueLocations il
ON il.LocationId = l.LocaltionID
JOIN issues i
ON i.IssueID = il.IssueID) a
PIVOT (Max(location)
FOR type IN([Building],
[Floor],
[Room]))piv

Resources