change data format from rows to columns - sql

I have a SQL table in the below format. Every ID has 2 entries in the table for a particular date.
Input:
ID date rownum subid value1 value2
A 200911 1 X 10 20
A 200911 2 Y 15 25
B 201001 2 S 60 35
B 201001 1 R 40 50
I want to write a SQL query to change this to the below format, so that every ID/date combination has only 1 entry as shown below. The rownum is already included and the values should be represented so that rownnum 1 is displayed first and then the value with rownum second as shown below.
Output:
ID date row1subid row1value1 row1value2 row2subid row2value1 row2value2
A 200911 X 10 20 Y 15 25
B 201001 R 40 50 S 60 35
Let me know if something is not clear.
Thanks for all your help!

Here is what you need to do as a SQL Fiddle.
And for reference:
CREATE TABLE TestData
([ID] varchar(1),
[date] int,
[rownum] int,
[subid] varchar(1),
[value1] int,
[value2] int)
;
INSERT INTO TestData
([ID], [date], [rownum], [subid], [value1], [value2])
VALUES
('A', 200911, 1, 'X', 10, 20),
('A', 200911, 2, 'Y', 15, 25),
('B', 201001, 2, 'S', 60, 35),
('B', 201001, 1, 'R', 40, 50)
;
SELECT A.ID, A.date, A.rownum, A.subid, A.value1, A.value2, B.value1 AS r2value1, B.value2 AS r2value2
FROM TestData AS A
INNER JOIN TestData B ON A.id = B.id AND B.rownum = 2
WHERE A.rownum = 1

Related

Multiple conditions on multiple columns

I have table that looks like this
WO | PS | C
----------------
12 | 1 | a
12 | 2 | b
12 | 2 | b
12 | 2 | c
13 | 1 | a
I want to find values from WO column where PS has value 1 and C value a AND PS has value 2 and C has value b. So on one column I need to have multiple conditions and I need to find it within WO column. If there is no value that matches two four conditions I don't want to have column WO included.
I tried using condition:
WHERE PS = 1 AND C = a AND PS = 2 AND C = b
but it does not work and does not have connection to WO column as mentioned above.
Edit:
I need to find WO which has (PS = 1 AND C = a) and at the same time it also has rows where (PS = 2 and C = b).
The result should be:
WO | PS | C
----------------
12 | 1 | a
12 | 2 | b
12 | 2 | b
If either of rows: (PS = 1 and C = a) or (PS = 2 and C = b) does not exist then nothing should be returned.
WHERE (PS = 1 AND C = a) or (PS = 2 AND C = b)
try this condition
As I understand this, you need two IN clauses or two EXIST clauses, something like this:
SELECT DISTINCT wo, ps, c
FROM yourtable
WHERE wo IN
(SELECT wo FROM yourtable WHERE ps = 1 and c = 'a')
AND wo IN
(SELECT wo FROM yourtable WHERE ps = 2 and c = 'b');
This will produce this outcome:
WO | PS | C
----------------
12 | 1 | a
12 | 2 | b
12 | 2 | c
Please note that in the last row of the result, the column C has value c instead of b as you have shown in your question. I guess this was your mistake when creating the sample outcome?
If I understand your question incorrect, please let me know and explain what's wrong, then I would review it.
Edit: To create the same result as shown in your question, this query would do:
SELECT wo, ps, c
FROM yourtable
WHERE ps IN (1,2) AND c IN ('a','b')
AND wo IN
(SELECT wo FROM yourtable WHERE ps = 1 and c = 'a')
AND wo IN
(SELECT wo FROM yourtable WHERE ps = 2 and c = 'b');
But I really don't believe this is what you were looking for ;)
Try out: db<>fiddle
I think you can make use of an exists criteria here to filter your rows correctly, I would like to see a wider sample data set to be sure though.
select *
from t
where ps in (1,2) and C in ('a','b')
and exists (
select * from t t2 where t2.WO = t.WO
and t2.PS != t.PS and t2.C != t.C
);
Just to throw in one more solution, you can do this with a single reference to your table, but this may not necessarily mean that it is more efficient. The first part is to filter based on the combinations you want:
DECLARE #T TABLE (WO INT, PS INT, C CHAR(1))
INSERT #T (WO, PS, C)
VALUES (12, 1, 'a'), (12, 2, 'b'), (12, 2, 'b'), (12, 2, 'c'), (13, 1, 'a');
SELECT *
FROM #T AS t
WHERE (t.PS = 1 AND t.C = 'a')
OR (t.PS = 2 AND t.C = 'B');
WO
PS
C
12
1
a
12
2
b
12
2
b
13
1
a
But you want to exclude WO 13 because this doesn't have both combinations, so what we ideally need is a count distinct of WS and C to find those with a distinct count of 2. You can't do COUNT(DISTINCT ..) in a windowed function directly, but you can do this indirectly with DENSE_RANK():
DECLARE #T TABLE (WO INT, PS INT, C CHAR(1))
INSERT INTO #T (WO, PS, C)
VALUES (12, 1, 'a'), (12, 2, 'b'), (12, 2, 'b'), (12, 2, 'c'), (13, 1, 'a');
SELECT *,
CntDistinct = DENSE_RANK() OVER(PARTITION BY t.WO ORDER BY t.PS, t.C) +
DENSE_RANK() OVER(PARTITION BY t.WO ORDER BY t.PS DESC, t.C DESC) - 1
FROM #T AS t
WHERE (t.PS = 1 AND t.C = 'a')
OR (t.PS = 2 AND t.C = 'B');
Which gives:
WO
PS
C
CntDistinct
12
1
a
2
12
2
b
2
12
2
b
2
13
1
a
1
You can then put this in a subquery and chose only the rows with a count of 2:
DECLARE #T TABLE (WO INT, PS INT, C CHAR(1))
INSERT INTO #T (WO, PS, C)
VALUES (12, 1, 'a'), (12, 2, 'b'), (12, 2, 'b'), (12, 2, 'c'), (13, 1, 'a');
SELECT t.WO, t.PS, t.C
FROM ( SELECT t.*,
CntDistinct = DENSE_RANK() OVER(PARTITION BY t.WO ORDER BY t.PS, t.C) +
DENSE_RANK() OVER(PARTITION BY t.WO ORDER BY t.PS DESC, t.C DESC) - 1
FROM #T AS t
WHERE (t.PS = 1 AND t.C = 'a')
OR (t.PS = 2 AND t.C = 'B')
) AS t
WHERE t.CntDistinct = 2;
Finally, if the combinations are likely change, or are a lot more than 2, you may find building a table of the combinations you are looking for a more maintainable solution:
DECLARE #T TABLE (WO INT, PS INT, C CHAR(1))
INSERT INTO #T (WO, PS, C)
VALUES (12, 1, 'a'), (12, 2, 'b'), (12, 2, 'b'), (12, 2, 'c'), (13, 1, 'a');
DECLARE #Combinations TABLE (PS INT, C CHAR(1), PRIMARY KEY (PS, C));
INSERT #Combinations(PS, C)
VALUES (1, 'a'), (2, 'b');
SELECT t.WO, t.PS, t.C
FROM ( SELECT t.*,
CntDistinct = DENSE_RANK() OVER(PARTITION BY t.WO ORDER BY t.PS, t.C) +
DENSE_RANK() OVER(PARTITION BY t.WO ORDER BY t.PS DESC, t.C DESC) - 1
FROM #T AS t
INNER JOIN #Combinations AS c
ON c.PS = t.PS
AND c.C = t.C
) AS t
WHERE t.CntDistinct = (SELECT COUNT(*) FROM #Combinations);
Let's chat about demo data. You provided some useful data that helps us see what your problem is, but no DDL. If you provide your demo data similar to this, it makes it easier for us to understand the issue:
DECLARE #table TABLE (WO INT, PS INT, C NVARCHAR(10))
INSERT INTO #table (WO, PS, C) VALUES
(12, 1, 'a'), (12, 2, 'b'),
(12, 2, 'b'), (12, 2, 'c'),
(13, 1, 'a')
Now on to your question. It looks to me like you just need a composite conditions and that one of them needs to evaluate to fully true. Consider this:
SELECT *
FROM #table
WHERE (
PS = 1
AND C = 'a'
)
OR (
PS = 2
AND C = 'b'
)
The predicates wrapped in the parens are evaluated as a whole in the WHERE clause. If one of the predicates is false, the whole thing is. If either composite evaluates to true, we return the row.
WO PS C
---------
12 1 a
12 2 b
12 2 b
13 1 a
This result set does include WO 13, as by your definition it should be there. I don't know if there are additional things you wanted to evaluate which may exclude it, but it does have a PS of 1 and a C of a.
Edit:
if the question is as discussed in the comments that a single WO must contain BOTH then this may be the answer:
SELECT *
FROM #table t
INNER JOIN (
SELECT t1.WO
FROM #table t1
INNER JOIN #table t2
ON t1.WO = t2.WO
WHERE t1.PS = 1
AND t1.C = 'a'
AND t2.PS = 2
AND t2.C = 'b'
GROUP BY t1.WO
) a
ON t.WO = a.WO
WHERE (
t.PS = 1
AND t.C = 'a'
)
OR (
t.PS = 2
AND t.C = 'b'
)
WO PS C WO
--------------
12 1 a 12
12 2 b 12
12 2 b 12

SQL sort and last record

this is my first post so pardon me if my question is not in it's appropriate places or tittle
I have a table like this
ID DATE Cat VALUE
-------------------------
1 07/07/2018 A 100
2 07/07/2018 A 200
3 07/07/2018 B 300
4 07/07/2018 B 400
5 07/07/2018 C 500
6 07/07/2018 C 600
7 08/07/2018 A 700
8 08/07/2018 A 800
9 08/07/2018 B 900
10 08/07/2018 B 110
11 08/07/2018 C 120
I would like to return
distinct category, sum of value, last record of the category
something like this
Cat sumValue lastrecord
--------------------------
A 1800 800
B 1710 110
C 1220 120
is it possible to do it in a single query
thanks
I am able to find the SUM
SELECT cat, SUM(value) FROM table GROUP BY cat;
and
find the last ID (autonumber key) using MAX
SELECT MAX(ID), cat FROM table GROUP BY cat;
but i just can't get the value for the last record
SQLFiddle
SELECT
t.cat,
SUM(t.value) as sumValue,
(
SELECT
t3.value
FROM
`table` t3
WHERE
t3.id = MAX(t2.id)
) as lastrecord
FROM
`table` t
JOIN
`table` t2 ON t.id = t2.id
GROUP BY
cat
EDIT shorter Version:
SELECT
t.cat,
SUM(t.value) as sumValue,
(SELECT value FROM `table` t2 WHERE t2.id = MAX(t.id)) lastValue
FROM
`table` t
GROUP BY
t.cat
This should do it
declare #t table (id int, cat char, value int);
insert into #t values
(1, 'A', 100),
(2, 'A', 200),
(3, 'B', 300),
(4, 'B', 400),
(5, 'C', 500),
(6, 'C', 600),
(7, 'A', 700),
(8, 'A', 800),
(9, 'B', 900),
(10, 'B', 110),
(11, 'C', 120);
select cat, value, sum
from
( select *
, sum(value) over (partition by cat) as sum
, ROW_NUMBER() over (partition by cat order by id desc) as rn
from #t
) tt
where tt.rn = 1
I hope you're looking for something like this,
Please replace the table name with your table name.
SELECT A.id,
A.cat,
A.date,
A.total_value,
A1.value
FROM (SELECT Max(id) AS id,
cat,
Max(date) AS Date,
Sum(value) AS Total_Value
FROM tbl_sof
GROUP BY cat) AS A
INNER JOIN tbl_sof A1
ON A.id = A1.id

I need use CTE with two tables

I have 2 tables and I need to use a CTE.
I need a set of rows of Table_2 on rows from Table_1, such that dActiveDate is biggest in table_1, and table_1.dActiveDate <= table_2.dDateFactor for table_1.dcidKala = table_2.dcidKala.
CREATE TABLE #Table_1
(
dcidKala INT,
dcPercentDiscount FLOAT,
dActiveDate DATE
)
CREATE TABLE #Table_2
(
dcRow INT,
dcidKala INT,
dcNum FLOAT,
dDateFactor DATE
)
INSERT INTO #Table_1
(
dcidKala,
dcPercentDiscount,
dActiveDate
)
VALUES
(109,10,'2017-08-23' ),
(109, 15, '2017-10-12'),
(100, 20, '2017-01-20'),
(102, 20, '2017-01-20')
INSERT INTO #Table_2
(
dcRow,
dcidKala,
dcNum,
dDateFactor
)
VALUES
( 1,109,1, '2017-10-05' ),
(2, 109, 2, '2017-10-07'),
(3, 109, 1, '2017-10-14'),
(4, 109, 5, '2017-10-19'),
(5, 100, 2, '2017-01-25')
;WITH cte AS
(
SELECT th.dcPercentDiscount,
tb.dcRow,
ROW_NUMBER() OVER(PARTITION BY th.dcidKala, tb.dcRow ORDER BY th.dActiveDate) AS
rn
FROM #Table_1 th
INNER JOIN #Table_2 tb
ON tb.dcidKala = th.dcidKala
AND tb.dDateFactor >= th.dActiveDate
)
SELECT *
FROM #Table_2 t2
LEFT JOIN cte t3
ON t2.dcRow = t3.dcRow
AND t3.rn = 1
DROP TABLE [#Table_1]
DROP TABLE [#Table_2]
--result myCode is:
--1 109 1 2017-10-05 10 1 1
--2 109 2 2017-10-07 10 2 1
--3 109 1 2017-10-14 10 3 1
--4 109 5 2017-10-19 10 4 1
--5 100 2 2017-01-25 20 5 1
--Rows 3 and Rows 4 is wrong
--i need this result :
-- on Result From table_1 on table_2
--1 109 1 2017-10-05 10 1 1
--2 109 2 2017-10-07 10 2 1
--3 109 1 2017-10-14 15 3 1
--4 109 5 2017-10-19 15 4 1
--5 100 2 2017-01-25 20 5 1
For each row in table_2 just one result from table_1 that is biggest on
dActiveDate and smaller dDateFactor
Please help me
thank you
You can use this.
;WITH cte AS
(
SELECT
th.dcPercentDiscount, tb.dcRow,
ROW_NUMBER() OVER(PARTITION BY th.dcidKala, tb.dcRow ORDER BY th.dActiveDate DESC) AS rn
FROM
Table_1 th
INNER JOIN
Table_2 tb ON tb.dcidKala = th.dcidKala
AND tb.dDateFactor >= th.dActiveDate
)
SELECT *
FROM Table_2 t2
LEFT JOIN cte t3 ON t2.dcRow = t3.dcRow and t3.rn=1
CREATE TABLE #Table_1 (dcidKala INT,
dcPercentDiscount FLOAT,
dActiveDate DATE)
CREATE TABLE #Table_2 (dcRow INT,
dcidKala INT,
dcNum FLOAT,
dDateFactor DATE)
INSERT INTO #Table_1 (dcidKala,
dcPercentDiscount,
dActiveDate)
VALUES (100, 10, '2017-01-01'),
(101, 15, '2017-01-02'),
(100, 20, '2017-01-20'),
(102, 20, '2017-01-20')
INSERT INTO #Table_2 (dcRow,
dcidKala,
dcNum,
dDateFactor)
VALUES (1, 100, 1, '2017-01-05'),
(2, 100, 2, '2017-01-09'),
(3, 101, 1, '2017-01-01'),
(4, 101, 5, '2017-01-20'),
(5, 100, 2, '2017-01-25')
SELECT *
FROM [#Table_2] AS [t2]
CROSS APPLY ( SELECT TOP 1 *
FROM ( SELECT TOP 1 [t1].[dcidKala],
[t1].[dcPercentDiscount],
[t1].[dActiveDate]
FROM [#Table_1] AS [t1]
WHERE [t1].[dcidKala] = [t2].[dcidKala]
AND [t2].[dDateFactor] >= [t1].[dActiveDate]
ORDER BY [t1].[dActiveDate] DESC
UNION ALL
SELECT NULL,
NULL,
NULL) t3
ORDER BY CASE
WHEN [t2].[dcidKala] IS NOT NULL THEN 0
ELSE 1
END) AS t1
DROP TABLE [#Table_1]
DROP TABLE [#Table_2]

Distribute values to several rows in SQL Server

I need help with SQL Server on how to distribute a row value to several rows with the same id. To illustrate,
Id = ProductInventoryCode
Qty = QuantityInStock
ForDistribution:
Id | Qty | TotalNoOfBranchesWithId
---+--------+-------------------------
1 | 40 | 2
2 | 33 | 3
3 | 21 | 2
A table that will receive the distributed values
Id | BranchCode | Qty | QtyFromForDistributionTable
-------------------------------------------------------
1 101 13 20
1 102 8 20
2 101 10 11
2 102 2 10
2 103 3 12
3 101 1 11
3 102 12 10
As much as possible the distribution should be near equal for each id and branches.
I got something like below, but somewhat got confused and lost path.
with rs as
(
select
r.*, cume.cumequantity,
coalesce(s.shipped, 0) AS shipped
from
tmpForDistribution r
cross apply
(SELECT SUM([QuantityInStock]) AS cumequantity
FROM tmpForDistribution r2
WHERE r2.ProductInventoryCode = r.ProductInventoryCode) cume
left join
(SELECT ProductInventoryCode, COUNT(ProductInventoryCode) AS shipped
FROM tmpDistributed s
GROUP BY s.ProductInventoryCode) s ON r.ProductInventoryCode = s.ProductInventoryCode
)
select
rs.ProductInventoryCode, rs.cumequantity, rs.QuantityInStock,
***"how to distribute"***
from rs
I'm currently using SQL Server 2008
Here's a sample screen output
The upper result is 145 Branches, below we use to distribute the ForDistributionQty field which is 3130, I am ending up with a fraction (DistVal = 21.586) which is not correct for this problem, it should be a whole number such as 21, however, if its just 21, then 21 x 145 is just 3045 which is shy of 85 units.
Here we distribute the values, and then make a final "adjustment" to the record which has the largest quantity (arbitrary). But at the end of the day, the math works and the distributed values are square.
Note: Not sure why in your sample why ID 2 did not get an even distribution
Declare #Table table (Id int,BranchCode int,Qty int)
Insert Into #Table values
(1, 101, 13),
(1, 102, 8),
(2, 101, 10),
(2, 102, 2),
(2, 103, 3),
(3, 101, 1),
(3, 102, 12)
Declare #Dist table (ID int,Qty int)
Insert Into #Dist values
(1,40),
(2,33),
(3,49)
;with cte0 as (
Select A.*
,ToDist = cast(D.Qty as int)
,DistVal = cast(D.Qty as int)/C.Cnt
,RN = Row_Number() over (Partition By A.ID Order By cast(D.Qty as int)/C.Cnt Desc,A.Qty Desc)
From #Table A
Join (Select ID,Cnt=count(*) from #Table Group By ID) C on A.ID=C.ID
Join #Dist D on A.ID=D.ID )
, cte1 as (
Select ID,AdjVal=Sum(DistVal)-max(ToDist) From cte0 Group By ID
)
Select A.ID
,A.BranchCode
,A.Qty
,DistVal = DistVal - case when A.RN<=abs(AdjVal) then 1*sign(AdjVal) else 0 end
From cte0 A
Join cte1 B on (A.ID=B.Id)
Order By 1,2
Returns
ID BranchCode Qty DistVal
1 101 13 20
1 102 8 20
2 101 10 11
2 102 2 11
2 103 3 11
3 101 1 24
3 102 12 25
If you can tolerate decimal values, a subquery seems to give a better query plan (tested on SQL 2014, with some sensible keys in place, this avoids a table spool and some additional index scans):
Declare #Table table (Id int,BranchCode int,Qty int, primary key(id, branchcode))
Insert Into #Table values
(1, 101, 13),
(1, 102, 8),
(2, 101, 10),
(2, 102, 2),
(2, 103, 3),
(3, 101, 1),
(3, 102, 12)
Declare #Dist table (ID int primary key,Qty int)
Insert Into #Dist values
(1,40),
(2,33),
(3,21)
SELECT
t.id
,t.BranchCode
,t.Qty
,(d.Qty / CAST((SELECT COUNT(*) as cnt FROM #table t2 where t.id = t2.id) AS decimal(10,2))) as DistributedQty
FROM #Table t
INNER JOIN #Dist d
ON d.id = t.Id
outputs:
Id BranchCode Qty DistributedQty
1 101 13 20.00000000000
1 102 82 20.00000000000
2 101 10 11.00000000000
2 102 21 11.00000000000
2 103 31 11.00000000000
3 101 11 10.50000000000
3 102 12 10.50000000000
If you need DistributedQty to be an int and retain remainders then I can't think of a better solution than #John Cappelletti's, noting that uneven quantities may not be as exactly even as you might hope (e.g. 32 distributed by three would result in a 12/10/10 distribution instead of an 11/11/10 distribution).

Merging groups of interval data - SQL Server

I have two sets of interval data I.E.
Start End Type1 Type2
0 2 L NULL
2 5 L NULL
5 7 L NULL
7 10 L NULL
2 3 NULL S
3 5 NULL S
5 8 NULL S
11 12 NULL S
What I'd like to do is merge these sets into one. This seems possible by utilising an islands and gaps solution but due to the non-continuous nature of the intervals I'm not sure how to go about applying it... The output I'm expecting would be:
Start End Type1 Type2
0 2 L NULL
2 3 L S
3 5 L S
5 7 L S
7 8 L S
8 10 L NULL
11 12 NULL S
Anyone out there done something like this before??? Thanks!
Create script below:
CREATE TABLE Table1
([Start] int, [End] int, [Type1] varchar(4), [Type2] varchar(4))
;
INSERT INTO Table1
([Start], [End], [Type1], [Type2])
VALUES
(0, 2, 'L', NULL),
(2, 3, NULL, 'S'),
(2, 5, 'L', NULL),
(3, 5, NULL, 'S'),
(5, 7, 'L', NULL),
(5, 8, NULL, 'S'),
(7, 10, 'L', NULL),
(11, 12, NULL, 'S')
;
I assume that Start is inclusive, End is exclusive and given intervals do not overlap.
CTE_Number is a table of numbers. Here it is generated on the fly. I have it as a permanent table in my database.
CTE_T1 and CTE_T2 expand each interval into the corresponding number of rows using a table of numbers. For example, interval [2,5) generates rows with Values
2
3
4
This is done twice: for Type1 and Type2.
Results for Type1 and Type2 are FULL JOINed together on Value.
Finally, a gaps-and-islands pass groups/collapses intervals back.
Run the query step-by-step, CTE-by-CTE and examine intermediate results to understand how it works.
Sample data
I added few rows to illustrate a case when there is a gap between values.
DECLARE #Table1 TABLE
([Start] int, [End] int, [Type1] varchar(4), [Type2] varchar(4))
;
INSERT INTO #Table1 ([Start], [End], [Type1], [Type2]) VALUES
( 0, 2, 'L', NULL),
( 2, 3, NULL, 'S'),
( 2, 5, 'L', NULL),
( 3, 5, NULL, 'S'),
( 5, 7, 'L', NULL),
( 5, 8, NULL, 'S'),
( 7, 10, 'L', NULL),
(11, 12, NULL, 'S'),
(15, 20, 'L', NULL),
(15, 20, NULL, 'S');
Query
WITH
e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
) -- 10
,e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b) -- 10*10
,e3(n) AS (SELECT 1 FROM e1 CROSS JOIN e2) -- 10*100
,CTE_Numbers
AS
(
SELECT ROW_NUMBER() OVER (ORDER BY n) AS Number
FROM e3
)
,CTE_T1
AS
(
SELECT
T1.[Start] + CA.Number - 1 AS Value
,T1.Type1
FROM
#Table1 AS T1
CROSS APPLY
(
SELECT TOP(T1.[End] - T1.[Start]) CTE_Numbers.Number
FROM CTE_Numbers
ORDER BY CTE_Numbers.Number
) AS CA
WHERE
T1.Type1 IS NOT NULL
)
,CTE_T2
AS
(
SELECT
T2.[Start] + CA.Number - 1 AS Value
,T2.Type2
FROM
#Table1 AS T2
CROSS APPLY
(
SELECT TOP(T2.[End] - T2.[Start]) CTE_Numbers.Number
FROM CTE_Numbers
ORDER BY CTE_Numbers.Number
) AS CA
WHERE
T2.Type2 IS NOT NULL
)
,CTE_Values
AS
(
SELECT
ISNULL(CTE_T1.Value, CTE_T2.Value) AS Value
,CTE_T1.Type1
,CTE_T2.Type2
,ROW_NUMBER() OVER (ORDER BY ISNULL(CTE_T1.Value, CTE_T2.Value)) AS rn
FROM
CTE_T1
FULL JOIN CTE_T2 ON CTE_T2.Value = CTE_T1.Value
)
,CTE_Groups
AS
(
SELECT
Value
,Type1
,Type2
,rn
,ROW_NUMBER() OVER
(PARTITION BY rn - Value, Type1, Type2 ORDER BY Value) AS rn2
FROM CTE_Values
)
SELECT
MIN(Value) AS [Start]
,MAX(Value) + 1 AS [End]
,Type1
,Type2
FROM CTE_Groups
GROUP BY rn-rn2, Type1, Type2
ORDER BY [Start];
Result
+-------+-----+-------+-------+
| Start | End | Type1 | Type2 |
+-------+-----+-------+-------+
| 0 | 2 | L | NULL |
| 2 | 8 | L | S |
| 8 | 10 | L | NULL |
| 11 | 12 | NULL | S |
| 15 | 20 | L | S |
+-------+-----+-------+-------+
A step-by-step way is:
-- Finding all break points
;WITH breaks AS (
SELECT Start
FROM yourTable
UNION
SELECT [End]
FROM yourTable
) -- Finding Possible Ends
, ends AS (
SELECT Start
, (SELECT Min([End]) FROM yourTable WHERE yourTable.Start = breaks.Start) End1
, (SELECT Max([End]) FROM yourTable WHERE yourTable.Start < breaks.Start) End2
FROM breaks
) -- Finding periods
, periods AS (
SELECT Start,
CASE
WHEN End1 > End2 And End2 > Start THEN End2
WHEN End1 IS NULL THEN End2
ELSE End1
END [End]
FROM Ends
WHERE NOT(End1 IS NULL AND Start = End2)
) -- Generating results
SELECT p.Start, p.[End], Max(Type1) Type1, Max(Type2) Type2
FROM periods p, yourTable t
WHERE p.start >= t.Start AND p.[End] <= t.[End]
GROUP BY p.Start, p.[End];
In above query some situations may not fit at analyzing all of them, you can improve it as you want ;).
First getting all the numbers of start and end via a Union.
Then joining those numbers on both the 'L' and 'S' records.
Uses a table variable for the test.
DECLARE #Table1 TABLE (Start int, [End] int, Type1 varchar(4), Type2 varchar(4));
INSERT INTO #Table1 (Start, [End], Type1, Type2)
VALUES (0, 2, 'L', NULL),(2, 3, NULL, 'S'),(2, 5, 'L', NULL),(3, 5, NULL, 'S'),
(5, 7, 'L', NULL),(5, 8, NULL, 'S'),(7, 10, 'L', NULL),(11, 12, NULL, 'S');
select
n.Num as Start,
(case when s.[End] is null or l.[End] <= s.[End] then l.[End] else s.[End] end) as [End],
l.Type1,
s.Type2
from
(select Start as Num from #Table1 union select [End] from #Table1) n
left join #Table1 l on (n.Num >= l.Start and n.Num < l.[End] and l.Type1 = 'L')
left join #Table1 s on (n.Num >= s.Start and n.Num < s.[End] and s.Type2 = 'S')
where (l.Start is not null or s.Start is not null)
order by Start, [End];
Output:
Start End Type1 Type2
0 2 L NULL
2 3 L S
3 5 L S
5 7 L S
7 8 L S
8 10 L NULL
11 12 NULL S