• This works but it's quite expensive to run:

    WITH Pass1 AS (

    SELECT

    PoolId = DENSE_RANK() OVER(ORDER BY ResidentId),

    HouseId, ResidentId

    FROM #HouseResident

    ),

    Pass2 AS (

    SELECT

    PoolId = MIN(PoolId) OVER (PARTITION BY HouseId),

    HouseId, ResidentId

    FROM Pass1

    ),

    Pass3 AS (

    SELECT

    PoolId = MIN(PoolId) OVER (PARTITION BY ResidentId),

    HouseId, ResidentId

    FROM Pass2

    ),

    Pass4 AS (

    SELECT

    PoolId = MIN(PoolId) OVER (PARTITION BY HouseId),

    HouseId, ResidentId

    FROM Pass3

    )

    SELECT

    PoolId = MIN(PoolId) OVER (PARTITION BY ResidentId),

    HouseId,

    ResidentId

    FROM Pass4;

    Also, you'd have to add more blocks until no changes are found, in order to "reach the end". Nesting CTE's like this can get very expensive after 3 or 4 blocks. It might be quicker to do the same thing using updates.

    “Write the query the simplest way. If through testing it becomes clear that the performance is inadequate, consider alternative query forms.” - Gail Shaw

    For fast, accurate and documented assistance in answering your questions, please read this article.
    Understanding and using APPLY, (I) and (II) Paul White
    Hidden RBAR: Triangular Joins / The "Numbers" or "Tally" Table: What it is and how it replaces a loop Jeff Moden