Concatinating the results of selected rows

  • I have three tables

    A Table listing Animals (Names)

    A Table listing Food (Names)

    A Table linking the Animal to the Food it likes (a many to many relationship)

    I need a sproc that for a given food ID,

    ...return a Sigle String (concat'd comma separated)

    ...of the Animals (names) that currently like that food.

    ...The names must be in alphabetical order.

    ...No Cursors allowed.

    Here is the schema and test data:

    CREATE TABLE dbo.Animal(

    AnimalID int NOT NULL,

    AnimalName varchar(50) NOT NULL,

    Deleted bit NOT NULL,

    CONSTRAINT [PK_Animal] PRIMARY KEY CLUSTERED

    (

    AnimalID ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

    ) ON [PRIMARY]

    GO

    INSERT dbo.Animal SELECT 1, 'Spot', 0

    INSERT dbo.Animal SELECT 2, 'Fido', 0

    INSERT dbo.Animal SELECT 3, 'Woffer', 1 -- Woffer has gone to the farm

    INSERT dbo.Animal SELECT 4, 'King', 0

    INSERT dbo.Animal SELECT 5, 'Smokey', 0

    CREATE TABLE dbo.Food(

    FoodID int NOT NULL,

    FoodName varchar(20) NOT NULL,

    Deleted bit NOT NULL,

    CONSTRAINT [PK_Food] PRIMARY KEY CLUSTERED

    (

    FoodID ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

    ) ON [PRIMARY]

    GO

    INSERT Food SELECT 1, 'Pork', 0

    INSERT Food SELECT 2, 'Chicken', 0

    INSERT Food SELECT 3, 'Duck', 1 -- All the Ducks have flown South

    INSERT Food SELECT 4, 'Beef', 0

    INSERT Food SELECT 5, 'Turkey', 0

    CREATE TABLE dbo.AnimalFood(

    AnimalID int NOT NULL,

    FoodID int NOT NULL,

    Deleted bit NOT NULL,

    CONSTRAINT [PK_AnimalFood] PRIMARY KEY CLUSTERED

    (

    AnimalID ASC,

    FoodID ASC

    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]

    ) ON [PRIMARY]

    GO

    ALTER TABLE [dbo].[AnimalFood] WITH CHECK ADD CONSTRAINT [FK_AnimalFood_Animal] FOREIGN KEY([AnimalID])

    REFERENCES [dbo].[Animal] ([AnimalID])

    GO

    ALTER TABLE [dbo].[AnimalFood] CHECK CONSTRAINT [FK_AnimalFood_Animal]

    GO

    ALTER TABLE [dbo].[AnimalFood] WITH CHECK ADD CONSTRAINT [FK_AnimalFood_Food] FOREIGN KEY([FoodID])

    REFERENCES [dbo].[Food] ([FoodID])

    GO

    ALTER TABLE [dbo].[AnimalFood] CHECK CONSTRAINT [FK_AnimalFood_Food]

    GO

    INSERT AnimalFood SELECT 1, 2, 0 -- Spot likes Chicken

    INSERT AnimalFood SELECT 1, 3, 0 -- Spot likes Duck (when in season)

    INSERT AnimalFood SELECT 1, 5, 0 -- Spot likes Turkey

    INSERT AnimalFood SELECT 2, 1, 0 -- Fido likes Pork

    INSERT AnimalFood SELECT 2, 2, 1 -- Fido used to like Chicken but does not any more

    INSERT AnimalFood SELECT 2, 4, 0 -- Fido likes Beef

    INSERT AnimalFood SELECT 3, 1, 0 -- Woffer liked Pork

    INSERT AnimalFood SELECT 3, 2, 0 -- Woffer liked Chicken

    INSERT AnimalFood SELECT 3, 4, 0 -- Woffer liked Beef

    INSERT AnimalFood SELECT 3, 5, 0 -- Woffer liked Turkey

    INSERT AnimalFood SELECT 4, 2, 1 -- King used to like Chicken but now doesn't like anything

    INSERT AnimalFood SELECT 5, 2, 0 -- Smokey likes Chicken

    INSERT AnimalFood SELECT 5, 4, 0 -- Smokey likes Beef

    INSERT AnimalFood SELECT 5, 5, 1 -- Smokey used to like Turkey

    Expected results:

    1 returns 'Fido' (Woffer is excluded because deleted=1)

    2 returns 'Smokey, Spot' (not 'Fido, Smokey, Spot' edited my mistake)

    3 returns 'Spot'

    4 returns 'Fido, Smokey'

    5 returns 'Spot' (not 'Smokey, Spot' - another mistake)

  • I'm not sure I'm following your expected results.

    According to the data, type 2 (chicken), was liked by 5 animals, but 3 are filtered out:

    Woofer is deleted in Animal table.

    Fido and King are are deleted in AnimalFood for food type 2.

    So you expect Fido, Smokey, and Spot, yet only Smokey and Spot like chicken.

    Also, you say 3 should return Spot, yet 3 has been deleted from Food. Do you still need to see these?

    Finally, for FoodID 5, you are expecting Smokey and Spot. However, smokey has Deleted = 1 from AnimalFood. Why are we expecting him in the result set?

    As for the technical solution, if cursors are out, will While Loops work?

    For now, how about this?

    DECLARE @foodid TINYINT

    SET @foodid = 2

    DROP TABLE #temp

    SELECT

    af.animalid, a.animalname, a.DELETED 'animal_del', af.foodid, f.foodname, f.DELETED 'food_del', af.DELETED 'af_deleted'

    INTO #temp

    FROM animalfood af

    JOIN animal a ON a.animalid = af.animalid

    JOIN food f

    ON f.foodid = af.foodid

    WHERE af.foodid = @foodid

    AND a.DELETED = 0

    AND f.DELETED = 0

    AND af.DELETED = 0

    DECLARE @maxrows INT, @rows int

    SELECT @maxrows = @@rowcount, @rows = 1

    WHILE 1=1

    BEGIN

    DECLARE @int INT, @name VARCHAR(MAX), @namestring VARCHAR(max)

    SELECT TOP 1 @int = animalid,

    @name = animalname

    FROM #temp

    ORDER BY animalname

    IF @rows = 1

    SELECT @namestring = @name

    ELSE

    SELECT @namestring = @namestring + ',' + @name

    SET @rows = @rows + 1

    DELETE FROM #temp WHERE animalid = @int

    IF @rows > @maxrows

    BREAK

    END

    SELECT @namestring

  • Hi Thomas.

    Nice setup. Thanks.

    If you write the code to return the food ID and the list of animals that like the food (food ID and animal name make a unique row, I'll show you how to concatenate it without any form of RBAR at all.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Thanks Mister Sachmo (& I did make a mistake on the Chicken & Turkey examples).

  • Mister Sachmo,

    Also, yes I wanted to allow for #3 Duck, because it might come back in season.

    The while works, but I am interested in something with the best performance.

    Thanks again!

  • SELECT

    T.name,

    STUFF((

    SELECT

    ',' + name

    FROM

    sys.columns C

    WHERE C.object_id = T.object_id

    ORDER BY

    name

    FOR

    XML PATH('')

    ) , 1 , 1 , '') As Columns_

    FROM sys.tables T

    ORDER BY name

  • Jeff - I am very interested in an RBAR-less solution

    Declare @InFoodID int

    SET @InFoodID = 2

    SELECT

    AnimalFood.FoodID, Animal.AnimalName

    FROM

    AnimalFood INNER JOIN

    Animal ON AnimalFood.AnimalID = Animal.AnimalID

    WHERE

    (AnimalFood.FoodID = @InFoodID) AND

    (AnimalFood.Deleted = 0) AND

    (Animal.Deleted = 0)

    ORDER BY Animal.AnimalName

    This would yield:

    FoodID AnimalName

    2....... Smokey

    2....... Spot

    (I am not sure I need the FoodID in the results since the PK of AnimalFood is both AnimalID and FoodID)

    Thanks in advance!

  • @ninja's_RGR'us

    That is wonderful!

  • Ninja's_RGR'us (6/1/2011)


    SELECT

    T.name,

    STUFF((

    SELECT

    ',' + name

    FROM

    sys.columns C

    WHERE C.object_id = T.object_id

    ORDER BY

    name

    FOR

    XML PATH('')

    ) , 1 , 1 , '') As Columns_

    FROM sys.tables T

    ORDER BY name

    Thanks Ninja, When I ran your script, I got the 'column' names concat'd. When I realized "the column names are the contents of the column", I came up with this:

    DECLARE @InFoodID INT

    SET @InFoodID = 2

    SELECT STUFF((

    SELECT

    ', ' + Animal.AnimalName

    FROM

    AnimalFood INNER JOIN

    Animal ON AnimalFood.AnimalID = Animal.AnimalID

    WHERE

    (AnimalFood.FoodID = @InFoodID) AND

    (AnimalFood.Deleted = 0) AND

    (Animal.Deleted = 0)

    ORDER BY Animal.AnimalName

    FOR XML PATH('')

    ) , 1 , 2 , '') As Animals

    This yeilds: "Smokey, Spot" which is what I want!!!

    I see that FOR XML PATH does the concatination and STUFF removes the first comma.

    Thanks for your help!

  • Bingo! Now you won't need us anymore!

  • thomas.bassett (6/1/2011)


    Jeff - I am very interested in an RBAR-less solution

    Declare @InFoodID int

    SET @InFoodID = 2

    SELECT

    AnimalFood.FoodID, Animal.AnimalName

    FROM

    AnimalFood INNER JOIN

    Animal ON AnimalFood.AnimalID = Animal.AnimalID

    WHERE

    (AnimalFood.FoodID = @InFoodID) AND

    (AnimalFood.Deleted = 0) AND

    (Animal.Deleted = 0)

    ORDER BY Animal.AnimalName

    This would yield:

    FoodID AnimalName

    2....... Smokey

    2....... Spot

    (I am not sure I need the FoodID in the results since the PK of AnimalFood is both AnimalID and FoodID)

    Thanks in advance!

    No need. Remi beat me to it.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

Viewing 11 posts - 1 through 10 (of 10 total)

You must be logged in to reply to this topic. Login to reply