How to convert detail data into columns?

  • Hi!

    I am working on an application processing requests from families to get subvention for their kids in school. For this purpose I need a query which returns for each family data for their first ,second, .. up to fifth kid.

    The query should correctly handle twins (same birth date) and the order of the ID's does not always match the order of the birth dates.

    Now I am using the following query (I limited it to 3 kids here!):

    select R.ID, R.NAme,R.Address,

    K1.Name, K1.Birth_date, -- First child

    K2.Name, K2.Birth_date, -- Second child

    K3.Name, K3.Birth_date -- Third child

    from #REQUESTS R

    inner join #KIDS K1

    on R.ID = K1.REQ_ID

    and K1.ID = (SELECT TOP 1 ID FROM #KIDS

    WHERE (REQ_ID=R.ID)

    ORDER BY Birth_date)

    left outer join #KIDS K2

    on R.ID = K2.REQ_ID

    and K2.ID = (SELECT TOP 1 ID FROM #KIDS

    WHERE (REQ_ID = R.ID) AND (ID <> K1.ID)

    ORDER BY Birth_date)

    left outer join #KIDS K3

    on R.ID = K3.REQ_ID

    and K3.ID = (SELECT TOP 1 ID FROM #KIDS

    WHERE (REQ_ID = R.ID) AND (ID NOT IN(K1.ID, K2.ID))

    ORDER BY Birth_date)

    It works nice, but since I have yearly about 12000 requests and even more KID records, it is terrible slow. πŸ™

    I tried some other approaches, but without success. Any idea how to speed up this query??

    Below is my test data:

    Thanks,

    Herman

    IF OBJECT_ID('TempDB..#KIDS','U') IS NOT NULL

    DROP TABLE #REQUESTS

    IF OBJECT_ID('TempDB..#KIDS','U') IS NOT NULL

    DROP TABLE #KIDS

    CREATE TABLE #REQUESTS

    (ID int PRIMARY KEY CLUSTERED,

    Name varchar(30),

    Address varchar(30))

    CREATE TABLE #KIDS

    (ID int PRIMARY KEY CLUSTERED,

    REQ_ID int NOT NULL,

    Name varchar(34) NOT NULL,

    Birth_DAte datetime NOT NULL

    )

    SET DATEFORMAT YMD

    INSERT INTO #REQUESTS (ID, NAme, Address)

    SELECT 1,'Family 1','Main road 1' UNION ALL

    SELECT 2,'Family 2','Main road 2' UNION ALL

    SELECT 3,'Family 3','Main road 3' UNION ALL

    SELECT 4,'Family 4','Main road 4' UNION ALL

    SELECT 5,'Family 5','Main road 5'

    INSERT INTO #KIDS(ID, REQ_ID, NAme, Birth_DAte)

    SELECT 1,1,'Harry', '20010101' UNION ALL

    SELECT 2,1,'Betty', '20020101' UNION ALL

    SELECT 3,2,'Hank', '19991111' UNION ALL

    SELECT 4,3,'Laura', '20020101' UNION ALL --ID order does not match birth order

    SELECT 5,3,'Mike', '20010101' UNION ALL

    SELECT 6,3,'Erik', '20030101' UNION ALL

    SELECT 7,4,'Lina', '20030101' UNION ALL --Twins!

    SELECT 8,4,'Lana', '20030101' UNION ALL --Twins!

    SELECT 9,5,'Andrej', '20030101' UNION ALL

    SELECT 10,5,'Katy', '20030101'

  • Hi Herman

    Here's a few more kids to play with:

    INSERT INTO #KIDS(ID, REQ_ID, NAme, Birth_DAte)

    SELECT 1,1,'Harry', '20010101' UNION ALL

    SELECT 2,1,'Betty', '20020101' UNION ALL

    SELECT 3,2,'Hank', '19991111' UNION ALL

    SELECT 4,3,'Laura', '20020101' UNION ALL --ID order does not match birth order

    SELECT 5,3,'Mike', '20010101' UNION ALL

    SELECT 6,3,'Erik', '20030101' UNION ALL

    SELECT 7,4,'Lina', '20030101' UNION ALL --Twins!

    SELECT 8,4,'Lana', '20030101' UNION ALL --Twins!

    SELECT 9,4,'Jane', '20020101' UNION ALL

    SELECT 10,4,'Jude', '20021001' UNION ALL

    SELECT 11,4,'Shel', '20040101' UNION ALL

    SELECT 12,4,'Pooky', '20050101' UNION ALL

    SELECT 13,5,'Andrej', '20030101' UNION ALL

    SELECT 14,5,'Katy', '20030101'

    Here's a query similar to yours which may run a little faster:

    [font="Courier New"]SELECT R.ID, R.NAme,R.Address,

       K1.Name, K1.Birth_date, -- First child

       CASE WHEN K2.ID <> K1.ID THEN K2.Name ELSE NULL END AS Name, CASE WHEN K2.ID <> K1.ID THEN K2.Birth_date ELSE NULL END AS Birth_date, -- Second child

       CASE WHEN K3.ID <> K2.ID THEN K3.Name ELSE NULL END AS Name, CASE WHEN K3.ID <> K2.ID THEN K3.Birth_date ELSE NULL END AS Birth_date, -- Third child

       CASE WHEN K4.ID <> K3.ID THEN K4.Name ELSE NULL END AS Name, CASE WHEN K4.ID <> K3.ID THEN K4.Birth_date ELSE NULL END AS Birth_date, -- Fourth child

       CASE WHEN K5.ID <> K4.ID THEN K5.Name ELSE NULL END AS Name, CASE WHEN K5.ID <> K4.ID THEN K5.Birth_date ELSE NULL END AS Birth_date -- Fifth child

    FROM #REQUESTS R, #KIDS K1, #KIDS K2, #KIDS K3, #KIDS K4, #KIDS K5

    WHERE K1.ID = (SELECT TOP 1 ID FROM #KIDS WHERE REQ_ID = R.ID ORDER BY Birth_DAte, ID)

       AND K2.ID = (SELECT TOP 1 ID FROM (SELECT TOP 2 * FROM #KIDS WHERE REQ_ID = R.ID ORDER BY Birth_DAte, ID) d ORDER BY Birth_DAte DESC, ID DESC)

       AND K3.ID = (SELECT TOP 1 ID FROM (SELECT TOP 3 * FROM #KIDS WHERE REQ_ID = R.ID ORDER BY Birth_DAte, ID) d ORDER BY Birth_DAte DESC, ID DESC)

       AND K4.ID = (SELECT TOP 1 ID FROM (SELECT TOP 4 * FROM #KIDS WHERE REQ_ID = R.ID ORDER BY Birth_DAte, ID) d ORDER BY Birth_DAte DESC, ID DESC)

       AND K5.ID = (SELECT TOP 1 ID FROM (SELECT TOP 5 * FROM #KIDS WHERE REQ_ID = R.ID ORDER BY Birth_DAte, ID) d ORDER BY Birth_DAte DESC, ID DESC)

    [/font]

    Here's a completely different method which pre-numbers the kids by DOB, rather than fudging it with SELECT TOP n:

    [font="Courier New"]SELECT R.ID, R.NAme AS FamilyName, R.Address, K.Name, K.Birth_date, CAST(NULL AS INT) AS KidNo

    INTO #temp

    FROM #REQUESTS R INNER JOIN #KIDS K ON K.REQ_ID = R.ID ORDER BY R.ID, K.Birth_DAte, K.ID

    DECLARE @KidNo INT, @ID INT

    SET @KidNo = 0

    UPDATE #temp SET @KidNo = KidNo = CASE WHEN @ID = ID THEN @KidNo+1 ELSE 1 END, @ID = ID

    SELECT ID, FamilyName, Address,

    MAX(CASE KidNo WHEN 1 THEN (Name) ELSE NULL END) AS [Name], MAX(CASE KidNo WHEN 1 THEN (Birth_date) ELSE NULL END) AS [Birth_date],

    MAX(CASE KidNo WHEN 2 THEN (Name) ELSE NULL END) AS [Name], MAX(CASE KidNo WHEN 2 THEN (Birth_date) ELSE NULL END) AS [Birth_date],

    MAX(CASE KidNo WHEN 3 THEN (Name) ELSE NULL END) AS [Name], MAX(CASE KidNo WHEN 3 THEN (Birth_date) ELSE NULL END) AS [Birth_date],

    MAX(CASE KidNo WHEN 4 THEN (Name) ELSE NULL END) AS [Name], MAX(CASE KidNo WHEN 4 THEN (Birth_date) ELSE NULL END) AS [Birth_date],

    MAX(CASE KidNo WHEN 5 THEN (Name) ELSE NULL END) AS [Name], MAX(CASE KidNo WHEN 5 THEN (Birth_date) ELSE NULL END) AS [Birth_date]

    FROM #temp

    GROUP BY ID, FamilyName, Address[/font]

    Cheers

    ChrisM

    β€œ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

  • Here's a "dynamic" solution.

    Primitive, but effective

    It requests your data for all children with a total of 5

    but it includes twins, triplets, ...

    If you have 3 children and then are blessed with a triplet, this proc will

    deliver the data for all 6 children

    Correct ?

    btw: it is primitive and may need some extra optimization.

    /*

    http://www.sqlservercentral.com/Forums/Topic618464-65-1.aspx

    */

    IF OBJECT_ID('TempDB..#KIDS','U') IS NOT NULL

    DROP TABLE #REQUESTS

    IF OBJECT_ID('TempDB..#KIDS','U') IS NOT NULL

    DROP TABLE #KIDS

    IF OBJECT_ID('TempDB..#ReqKidsWRK','U') IS NOT NULL

    DROP TABLE #ReqKidsWRK

    IF OBJECT_ID('TempDB..#ListKidsWRK','U') IS NOT NULL

    DROP TABLE #ListKidsWRK

    Declare @DebugNY char(1)

    Set @DebugNY = 'N'

    CREATE TABLE #REQUESTS

    (ID int PRIMARY KEY CLUSTERED,

    Name varchar(30),

    Address varchar(30))

    CREATE TABLE #KIDS

    (ID int PRIMARY KEY CLUSTERED,

    REQ_ID int NOT NULL,

    Name varchar(34) NOT NULL,

    Birth_DAte datetime NOT NULL

    )

    -- SET DATEFORMAT YMD

    INSERT INTO #REQUESTS (ID, NAme, Address)

    SELECT 1,'Family 1','Main road 1' UNION ALL

    SELECT 2,'Family 2','Main road 2' UNION ALL

    SELECT 3,'Family 3','Main road 3' UNION ALL

    SELECT 4,'Family 4','Main road 4' UNION ALL

    SELECT 5,'Family 5','Main road 5' UNION ALL

    SELECT 6,'Family 6','Main road 6' UNION ALL

    SELECT 7,'Family 7','Main road 7'

    INSERT INTO #KIDS(ID, REQ_ID, NAme, Birth_DAte)

    SELECT 1,1,'Harry', '20010101' UNION ALL

    SELECT 2,1,'Betty', '20020101' UNION ALL

    SELECT 3,2,'Hank', '19991111' UNION ALL

    SELECT 4,3,'Laura', '20020101' UNION ALL --ID order does not match birth order

    SELECT 5,3,'Mike', '20010101' UNION ALL

    SELECT 6,3,'Erik', '20030101' UNION ALL

    SELECT 7,4,'Lina', '20030101' UNION ALL --Twins!

    SELECT 8,4,'Lana', '20030101' UNION ALL --Twins!

    SELECT 9,5,'Andrej', '20030101' UNION ALL

    SELECT 10,5,'Katy', '20030101' UNION ALL

    SELECT 11,6,'No1', '20010101' UNION ALL

    SELECT 12,6,'No2', '20020101' UNION ALL

    SELECT 13,6,'No3', '20030101' UNION ALL

    SELECT 14,6,'No4_1', '20040101' UNION ALL

    SELECT 15,6,'No4_2', '20040101' UNION ALL

    SELECT 16,6,'No4_3', '20040101' UNION ALL

    SELECT 17,7,'No1', '20010101' UNION ALL

    SELECT 18,7,'No2', '20020101' UNION ALL

    SELECT 19,7,'No3', '20030101' UNION ALL

    SELECT 20,7,'No4', '20040101' UNION ALL

    SELECT 21,7,'No5', '20050101' UNION ALL

    SELECT 22,7,'No6', '20060101'

    If @DebugNY = 'Y'

    begin

    select R.ID, R.NAme,R.Address,

    K1.Name, K1.Birth_date, -- First child

    K2.Name, K2.Birth_date, -- Second child

    K3.Name, K3.Birth_date -- Third child

    from #REQUESTS R

    inner join #KIDS K1

    on R.ID = K1.REQ_ID

    and K1.ID = (SELECT TOP 1 ID FROM #KIDS

    WHERE (REQ_ID=R.ID)

    ORDER BY Birth_date)

    left outer join #KIDS K2

    on R.ID = K2.REQ_ID

    and K2.ID = (SELECT TOP 1 ID FROM #KIDS

    WHERE (REQ_ID = R.ID) AND (ID <> K1.ID)

    ORDER BY Birth_date)

    left outer join #KIDS K3

    on R.ID = K3.REQ_ID

    and K3.ID = (SELECT TOP 1 ID FROM #KIDS

    WHERE (REQ_ID = R.ID) AND (ID NOT IN(K1.ID, K2.ID))

    ORDER BY Birth_date)

    end

    Select K.REQ_ID

    , K.Birth_DAte

    , count(distinct K.ID) as DatePersonsCount

    , count(distinct K1.ID) as RunningPersonsCount

    , case when count(distinct K1.ID) >= 5 then 1 else 0 end as ReEvaluate

    into #ReqKidsWRK

    from #KIDS K

    inner join #KIDS K1

    on K1.REQ_ID =K.REQ_ID

    and K1.Birth_DAte <= K.Birth_DAte

    Group by K.Req_Id, K.Birth_Date

    Order by K.Req_Id, K.Birth_Date

    /* drop Childeren where childcount > 5 and birthdate > the referencedate */

    Delete W

    from #ReqKidsWRK W

    Inner join (Select REQ_ID

    , min( Birth_Date ) as Ref_Birth_Date

    from #ReqKidsWRK

    where ReEvaluate = 1

    group by REQ_ID ) R

    on W.REQ_ID = R.REQ_ID

    and W.Birth_Date > R.Ref_Birth_Date

    Create table #ListKidsWRK

    (ID int ,

    REQ_ID int NOT NULL,

    Name varchar(34) NOT NULL,

    Birth_DAte datetime NOT NULL,

    Seq_No int not null default 0

    );

    Insert into #ListKidsWRK (ID, REQ_ID, Name, Birth_DAte )

    Select distinct K.*

    from #Kids K

    Where exists ( select *

    from #ReqKidsWRK W

    where W.REQ_ID = K.REQ_ID

    and W.Birth_Date = K.Birth_Date ) ;

    Create unique clustered index x_ListKidsWRK on #ListKidsWRK (REQ_ID, Birth_DAte, ID) ;

    Declare @SQLSeqNo varchar(4000)

    Select @SQLSeqNo =

    'DECLARE @PrevGrpCnt INT

    SET @PrevGrpCnt = 0

    DECLARE @PrevReqId INT

    SET @PrevReqId = 0

    UPDATE #ListKidsWRK

    SET @PrevGrpCnt = Seq_No = CASE

    WHEN REQ_ID = @PrevReqId

    THEN @PrevGrpCnt + 1

    ELSE 1

    END,

    @PrevReqId = REQ_ID

    FROM #ListKidsWRK WITH (INDEX(x_ListKidsWRK),TABLOCKX)

    '

    Exec (@SQLSeqNo)

    If @DebugNY = 'Y'

    begin

    Select *

    from #ListKidsWRK

    Order by REQ_ID, Seq_No

    end

    /* Build dynamic sql for all kids */

    Declare @SQLSel varchar(1000)

    Declare @SQLSelKid varchar(1000)

    Declare @SQLSeljoin varchar(1000)

    Declare @SQLSelGroup varchar(1000)

    Declare @SQLSelorderby varchar(1000)

    Select @SQLSel = 'select R.ID, R.NAme,R.Address'

    , @SQLSelKid = ''

    , @SQLSeljoin = ' from #REQUESTS R inner join #ListKidsWRK K on R.ID = K.REQ_ID '

    , @SQLSelGroup = ' group by R.ID, R.NAme,R.Address'

    , @SQLSelorderby = ' Order by R.ID ;' ;

    Select @SQLSelKid = @SQLSelKid + ', min( case when K.Seq_No = ' + cast(T.N as varchar(15)) + ' then K.Name else null end) as Name, min( case when K.Seq_No = ' + cast(T.N as varchar(15)) + ' then K.Birth_date else null end) as Birth_date'

    from (Select max (Seq_No) as Max_Seq_No

    from #ListKidsWRK ) W

    inner join tempdb.dbo.Tally T

    on T.N <= W.Max_Seq_No

    If @DebugNY = 'Y'

    begin

    Print @SQLSel

    Print @SQLSelKid

    Print @SQLSeljoin

    Print @SQLSelGroup

    Print @SQLSelorderby

    end

    Exec ( @SQLSel + @SQLSelKid + @SQLSeljoin + @SQLSelGroup + @SQLSelorderby )

    Johan

    Learn to play, play to learn !

    Dont drive faster than your guardian angel can fly ...
    but keeping both feet on the ground wont get you anywhere :w00t:

    - How to post Performance Problems
    - How to post data/code to get the best help[/url]

    - How to prevent a sore throat after hours of presenting ppt

    press F1 for solution, press shift+F1 for urgent solution πŸ˜€

    Need a bit of Powershell? How about this

    Who am I ? Sometimes this is me but most of the time this is me

  • Wow,:w00t:

    Thanks Chris. Your query is much faster then mine: 3 seconds compared to more than a minute on my database.

    I had been thinking about your second suggestion as well, but it requires me to change the application. Anyway, with the response time of your query there is no need!

    Thanks very much

    Herman

Viewing 4 posts - 1 through 3 (of 3 total)

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