SQL岛 - 查找连续的地址范围

时间:2016-08-17 18:55:06

标签: sql sql-server gaps-and-islands

我有表示街道地址范围的数据。我需要在连续时对范围进行分组。我已经尝试了一些SQL差距的其他解决方案。岛屿,但似乎无法发挥作用。

FullName  | City       | FromRight | ToRight | FromLeft | ToLeft 
-----------------------------------------------------------------
W Main St | Townsville | 100       | 198     | 101       | 199
W Main St | Townsville | 200       | 298     | 201       | 299
W Main St | Townsville | 500       | 598     | 501       | 599
W Main St | Cityburg   | 600       | 698     | 601       | 699
E 1st Ave | Townsville | 100       | 398     | 301       | 399
E 1st Ave | Townsville | 400       | 416     | 401       | 417
E 1st Ave | Townsville | 418       | 458     | 419       | 459

当街道名称和城市相同时,我需要能够根据低地址(从右侧)和高地址(到左侧)找到连续值。所以我的结果表看起来像:

FullName  | City       | FromRight | ToLeft 
--------------------------------------------
W Main St | Townsville | 100       | 299     
W Main St | Townsville | 500       | 599
W Main St | Cityburg   | 600       | 699
E 1st Ave | Townsville | 100       | 459 

任何帮助将不胜感激!

4 个答案:

答案 0 :(得分:3)

此解决方案依赖于计数表。但是一旦到位,它就非常简单。 (给上面的John提示,在另一个答案中以易于使用的格式提供样本数据)。

Declare @YourTable Table (FullName varchar(100),City varchar(100),FromRight int,ToRight int,FromLeft int, ToLeft int)
Insert Into @YourTable values
('W Main St' , 'Townsville' , 100       , 198     , 101       , 199),
('W Main St' , 'Townsville' , 200       , 298     , 201       , 299),
('W Main St' , 'Townsville' , 500       , 598     , 501       , 599),
('W Main St' , 'Cityburg'   , 600       , 698     , 601       , 699),
('E 1st Ave' , 'Townsville' , 100       , 398     , 301       , 399),
('E 1st Ave' , 'Townsville' , 400       , 416     , 401       , 417),
('E 1st Ave' , 'Townsville' , 418       , 458     , 419       , 459);

WITH cte AS (
    SELECT [yt].[FullName], [yt].[City], [n].[n],
    [n].[n] - ROW_NUMBER() OVER (PARTITION BY [yt].[FullName], [yt].[City] ORDER BY [n]) AS [rn]
    FROM [Util].[dbo].[Numbers] AS [n]
    JOIN @YourTable AS [yt]
        ON [n].[n] BETWEEN [yt].[FromRight] AND [yt].[ToLeft]
)
SELECT [FullName], [City], MIN([n]), MAX([n])
FROM [cte]
GROUP BY [FullName] ,
         [City], [rn]
ORDER BY [FullName], [City], MIN([n]);

这里的关键观察是,如果你在一个连续的范围内,row_number()和计数表都以相同的速率增加(即每行一个),所以它们之间的差异将是相同的小组。

答案 1 :(得分:1)

Declare @YourTable Table (FullName varchar(100),City varchar(100),FromRight int,ToRight int,FromLeft int, ToLeft int)
Insert Into @YourTable values
('W Main St' , 'Townsville' , 100       , 198     , 101       , 199),
('W Main St' , 'Townsville' , 200       , 298     , 201       , 299),
('W Main St' , 'Townsville' , 500       , 598     , 501       , 599),
('W Main St' , 'Cityburg'   , 600       , 698     , 601       , 699),
('E 1st Ave' , 'Townsville' , 100       , 398     , 301       , 399),
('E 1st Ave' , 'Townsville' , 400       , 416     , 401       , 417),
('E 1st Ave' , 'Townsville' , 418       , 458     , 419       , 459)
;with cteBase  as (Select FullName,City,R1=FromRight,R2=ToLeft From @YourTable
    ),ctePass1 as (
                   Select A.FullName,A.City,R1=B.Pass1R1,R2=B.Pass1R2
                     From cteBase A
                    Cross Apply (Select Pass1R1=min(R1),Pass1R2=max(R2) 
                                  From  cteBase 
                                  Where FullName=A.FullName and City=A.City and (A.R1 Between R1-1 and R2+1 or A.R2 Between R1-1 and R2+1)) B
    ),ctePass2 as (
                    Select A.FullName,A.City,R1=B.Pass1R1,R2=B.Pass1R2
                     From  ctePass1 A
                     Cross Apply (Select Pass1R1=min(R1),Pass1R2=max(R2) 
                                   From  ctePass1 
                                   Where FullName=A.FullName and City=A.City and (A.R1 Between R1-1 and R2+1 or A.R2 Between R1-1 and R2+1)) B
)
Select Distinct 
       FullName
      ,City
      ,FromRight = R1
      ,ToLeft    = R2
 From  ctePass2
 Order By 1 Desc,2 Desc, 3

返回

FullName    City        FromRight   ToLeft
W Main St   Townsville  100         299
W Main St   Townsville  500         599
W Main St   Cityburg    600         699
E 1st Ave   Townsville  100         459

答案 2 :(得分:1)

如果您使用的是现代版本并假设1)根据您的评论只有FromRightToLeft,2)间隔永远不会重叠

Declare @YourTable Table (FullName varchar(100),City varchar(100),FromRight int,ToRight int,FromLeft int,ToLeft int)
Insert Into @YourTable values
('W Main St' , 'Townsville' , 100       , 198     , 101       , 199),
('W Main St' , 'Townsville' , 200       , 298     , 201       , 299),
('W Main St' , 'Townsville' , 500       , 598     , 501       , 599),
('W Main St' , 'Cityburg'   , 600       , 698     , 601       , 699),
('E 1st Ave' , 'Townsville' , 100       , 398     , 301       , 399),
('E 1st Ave' , 'Townsville' , 400       , 416     , 401       , 417),
('E 1st Ave' , 'Townsville' , 418       , 458     , 419       , 459);

select FullName, City
   , FromRight = max(case startFlag when 1 then FromRight end)
   , ToLeft = max(case endFlag when 1 then ToLeft end)
from (
    select *
       , grp = sum(startFlag) over(partition by FullName, City order by FromRight)
    from (
        select *
          , startFlag = Case FromRight when lag(ToLeft,1,-1) over (partition by FullName , City order by FromRight) + 1 then 0 else 1 end
          , endFlag = Case ToLeft when lead(FromRight,1,-1) over (partition by FullName , City order by ToLeft) - 1 then 0 else 1 end
        from @YourTable
        ) flags
    ) groups
group by FullName, City, grp
order by FullName, City, FromRight;

答案 3 :(得分:0)

更新处理行中的差距

这不是我确定的最佳方法,但它适用于您的测试数据。无论如何,玩这个很有趣。我不明白它的实际用途,只是因为你可以并不意味着你应该一直都是这样,但同样又很有趣挑战:))

;with cte1 as(
select
    FullName,
    City,
    FromRight,
    ToRight,
    FromLeft,
    ToLeft,
    case 
        when lag(ToLeft) over(PARTITION BY FullName, City ORDER BY FullName, City, FromRight) is null  or lag(ToLeft) over(PARTITION BY FullName, City ORDER BY FullName, City, FromRight) + 1 <> FromRight then FromRight
    end as NewFromRight,
    case
        when lead(FromRight) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight) - 1 = ToLeft then NULL
        when lead(FromRight) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight) is null then ToLeft
        when lead(FromRight) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight) - 1 <> ToLeft then ToLeft
        else ToLeft
    end as NewToLeft
from #cities),

------this CTE is needed because I couldn't figure out how to do it without it
------It takes the max of the previous CTE for the given partition. 
------Nested windows functions aren't allowed hence the second cte
cte2 as(
select distinct
    FullName,
    City,
    NewFromRight as FromRight,
    NewToLeft as ToLeft
from
    cte1
where
    NewFromRight is not null and NewToLeft is not null
union all
select distinct
    FullName,
    City,
    --max(NewFromRight) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight) as FromRight,
    --max(NewToLeft) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight) as ToLeft
    case 
        when NewFromRight is null then lag(NewFromRight) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight)
        else NewFromRight
    end as FromRight,
    case 
        when NewToLeft is null then lead(NewToLeft) over (PARTITION BY FullName, City ORDER BY FullName, City, FromRight)
        else NewToLeft
    end as ToLeft
from cte1
where
    NewFromRight is null or NewToLeft is null)

select * from cte2
where FromRight is not null and ToLeft is not null 
order by FullName, FromRight

如果有人想玩这个...这里有一些测试数据。只需使用YourTable

替换第一个CTE中的#cities即可
select
'W Main St' as FullName,'Townsville' as City,100 as FromRight,198 as ToRight,101 as FromLeft,199 as ToLeft
into #cities
UNION ALL SELECT 'W Main St','Townsville',200,298,201,299
UNION ALL SELECT 'W Main St','Townsville',500,598,501,599
UNION ALL SELECT 'W Main St','Cityburg',600,698,601,699
UNION ALL SELECT 'E 1st Ave','Townsville',100,398,301,399
UNION ALL SELECT 'E 1st Ave','Townsville',400,416,401,417
UNION ALL SELECT 'E 1st Ave','Townsville',418,458,419,459

UNION ALL SELECT 'E 1st Ave','Townsville',470,458,419,479
UNION ALL SELECT 'E 1st Ave','Townsville',490,458,419,499
UNION ALL SELECT 'E 1st Ave','Townsville',500,458,419,501