2016-08-17 1 views
1

J'ai des données qui représentent des plages d'adresses le long des rues. J'ai besoin de regrouper les plages lorsqu'elles sont consécutives. J'ai essayé certaines des autres solutions pour les lacunes SQL &, mais je n'arrive pas à faire fonctionner quelque chose.Iles SQL - trouver des plages d'adresses consécutives

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 

Je dois être en mesure de trouver des valeurs consécutives en fonction de l'adresse faible (la à droite) et l'adresse haute (la A gauche) lorsque le nom de la rue et la ville sont les mêmes. Donc mon tableau de résultat ressemblerait à:

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 

Toute aide serait grandement appréciée!

+0

Êtes-vous sûr, ressemble 'E 1st Ave | Townsville | 100 | 398 | 301 | 399' manque 101..299. – Serg

+0

votre ensemble de résultats suppose que le droit sera toujours inférieur à gauche, mais considérez que la plage de droite est entièrement dans la plage de gauche, par ex. NW 2nd st FromRight 150 ToRight 180 FromLeft 101 ToLeft 199 puis la rangée suivante est FromRight 200 ToRight 298 FromLeft 201 ToLeft 199. Dans ce cas, quel serait le total FromRight? Cela ne peut pas être 150 parce que cela couperait 101-149 pour le côté gauche de la rue. Serait-ce alors FromLeft - 1 comme FromRight? – Matt

+0

Je suppose qu'avec les nuances je devrais me demander pourquoi combiner/simplifier les dossiers? Serait-il préférable de conserver les enregistrements supplémentaires et juste agréger après votre match? ou attribuer un numéro de bloc à un segment et l'agréger de cette façon? – Matt

Répondre

3

Cette solution repose sur une table ayant de pointage. Mais c'est assez simple une fois que c'est en place. (Astuce chapeau à John ci-dessus pour fournir les données de l'échantillon dans un format facile à consommer dans une autre réponse).

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]); 

L'observation clé ici est que si vous êtes à l'intérieur d'une plage contiguë, à la fois row_number() et l'augmentation de la table de pointage au même taux (soit un par ligne), de sorte que leur différence sera identique pour les lignes à l'intérieur le groupe.

+0

Très bon usage de la table de pointage. Je n'ai pas pensé à l'utiliser. Bien que, je l'utilise fortement pour les fonctions de séparation de chaînes avant SQL Server 2016 qui vient bien avec son propre – scsimon

+0

Cela semble prometteur, mais quand je l'essaie contre mon ensemble de données complet, je reçois des routes avec de grands résultats et certains qui ont un beaucoup de doublons. Je vais devoir jouer plus avec ça et comprendre ce qui se passe. Merci quand même. – sernst

+0

Je suis curieux de voir ce qui est différent dans vos données source qui causerait des dupes. L'agrégation * devrait * prendre soin de tout cela. –

0

MANIPULER LACUNES À JOUR Juxtaposables

Ce n'est pas la meilleure approche, mais je suis sûr que cela fonctionne avec vos données de test. C'était amusant de jouer avec de toute façon. Je ne comprends pas l'utilisation réelle en elle, et juste parce que vous peut ne signifie pas que vous devriez tout le temps, mais encore une fois ce fut un défi amusant :)

;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 

Si quelqu'un veut jouer avec ça ... voici quelques données de test. Il suffit de remplacer YourTable dans le premier CTE avec #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 
+1

Je peux penser à quelques cas d'utilisation de l'adresse cela se décomposera probablement comme lorsque les droits sont entièrement contenus dans les gauches, les gauches se terminent avant que 1+ soit continu. Accordé ces cas d'utilisation ne sont pas dans le PO. Comme je casse votre requête je regarde/lit comme il ne devrait pas fonctionner mais il est dû à la partition RUNNING MAX(), RUNNING MIN() aurait le même effet mais MIN() pour FromRight fait plus depuis lors de la lecture du code. En tout cas, bonne réflexion! Je vais noter que parce que vous partitionnez par Fullname et la ville, vous ne devriez pas avoir besoin de ceux dans l'ordre, mais ils ne leur font pas mal non plus – Matt

+0

haha ​​merci @matt je ne suis pas sûr si c'était le bon processus de réflexion, mais c'est là mon Noggin m'a conduit. Comme je l'ai dit, je suis sûr qu'il y a une meilleure façon de nettoyer, et probablement plus correct – scsimon

+0

Merci - malheureusement, je suis sur 2008 R2 et avance/décalage ne sont pas disponibles. Je pourrais essayer et obtenir un test de DB en 2012 afin que je puisse essayer cette approche. – sernst

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 

Retours

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 
+0

Belle approche. Je l'ai testé avec mes données de test ci-dessous qui jettent de nouvelles lignes et des lacunes dans les données et cela a fonctionné comme le mien. J'essaie toujours de comprendre le vôtre, mais je suis sûr que le mien est encore plus confus haha. – scsimon

+0

@scsimon Très gentil et réconfortant de savoir que vous avez obtenu les mêmes résultats. Personnellement, je creuse voir d'autres approches. Il y a toujours quelque chose à gagner. Cela dit, le brillant XML de Shnugo me fait saigner le cerveau. –

+0

Idem. +1 pour toi. – scsimon

1

Si vous êtes sur la version moderne et en supposant que 1) ne FromRight et ToLeft matière selon vos commentaires, 2) des intervalles ne se chevauchent

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;