-3

I have a data set that looks something like this:

   Gender | Age | Name
    Male  | 30  | Bill
  Female  | 27  | Jenny
  Female  | 27  | Debby 
   Male   | 44  | Frank

And I'm trying to display this as specially formatted HTML code:

    <ul>
      <li>Male
        <ul>
          <li>30
            <ul>
              <li>Bill</li>
            </ul>
          </li>
          <li>44
            <ul>
              <li>Frank</li>
            </ul>
          </li>
        </ul>  
      </li>
    </ul>

    <ul>
      <li>Female
        <ul>
          <li>27
            <ul>
              <li>Jenny</li>
              <li>Debby</li>
            </ul>
          </li>
        </ul>  
      </li>
    </ul>

I tried using FOR XML but that didn't give the results I was looking for. It didn't remove the multiple Gender and Age fields returned. As you can see in this HTML it is compounding it all and only giving duplicates at the end node.

Aaron Bertrand provided an excellent method here Return Select Statement as formatted HTML that seems to work perfectly in SQL 2008, but I was looking for something that worked in 2005 as well, besides the little stuff, like += operators and setting default DECLARE values, just doesn't display in 2005.

How would something like this be achieved in SQL Server 2005?

Community
  • 1
  • 1
Control Freak
  • 12,965
  • 30
  • 94
  • 145
  • Why not just make edits to the solution that would make it 2005 compatible? Something like `SELECT @html += 'text'` can easily be rewritten as `SELECT @html = @html + 'text'`. – Khan Apr 24 '12 at 15:14
  • @Jeff he says he's done that but that it doesn't print out any results. I believe there is some information we're not being told, and it may not be the OP's fault, it's just tough to guide him on what the issue might be. I've posted a solution below that has the necessary adjustments and that I've validated works on SQL Server 2005. – Aaron Bertrand Apr 24 '12 at 15:16
  • @AaronBertrand The way I read it is that the OP just didn't know how to translate it to 2005. – Khan Apr 24 '12 at 15:19
  • @Jeff sorry, you'd have to read the exhaustive comment trail on the other question (linked in both the question and answer here) to see the background. The original question was for 2008 - so I used 2008 syntax. The OP converted it to 2005, and eliminated the syntax errors, but now claims it doesn't do anything. – Aaron Bertrand Apr 24 '12 at 15:22
  • @AaronBertrand Aha, I see. Well I applaud your solution with a +1 because it does look really handy. :) – Khan Apr 24 '12 at 15:24
  • So now the answer DOES work on SQL Server 2005? Care to share with everyone what YOU were doing wrong? – Aaron Bertrand Apr 24 '12 at 15:33
  • I guess if its different the only thing was `Declare @html = NVARCHAR(MAX); SET @html = N'';`, I was doing `DECLARE @html varchar(MAX); SET @html = '';` – Control Freak Apr 24 '12 at 15:52
  • Because building the CTE using a select statement wouldn't be off-topic than the purpose of the question.. ? – Control Freak Apr 24 '12 at 16:04
  • If the data you pumped into the CTE would demonstrate a discrepancy not shown by the data you originally included, yes, it should have been added. Why do you think it wouldn't be relevant? – Aaron Bertrand Apr 24 '12 at 16:06
  • possible duplicate of [Return Select Statement as formatted HTML](http://stackoverflow.com/questions/10289896/return-select-statement-as-formatted-html) – Shoban Apr 25 '12 at 08:27

3 Answers3

5
declare @T table
(
  Gender varchar(10),
  Age int,
  Name varchar(10)
)

insert into @T values ('Male',   30,   'Bill')
insert into @T values ('Female', 27,   'Jenny')
insert into @T values ('Female', 27,   'Debby') 
insert into @T values ('Male',   44,   'Frank')

select Gender as 'li',
       (select T2.Age as 'li',
               (select T3.Name as 'li'
                from @T as T3
                where T2.Age = T3.Age and
                      T1.Gender = T3.Gender
                for xml path(''), root('ul'), type) as 'li'
        from @T as T2
        where T1.Gender = T2.Gender
        group by T2.Age
        for xml path(''), root('ul'), type) as 'li'
from @T as T1
group by Gender
for xml path('ul')
Mikael Eriksson
  • 136,425
  • 22
  • 210
  • 281
  • 2
    +1. Much cleaner than mine. And look at all the drama you've avoided. :-) – Aaron Bertrand Apr 24 '12 at 15:36
  • @AaronBertrand I saw the number of comments. Did not care to read them :) – Mikael Eriksson Apr 24 '12 at 15:38
  • @ZeeTee I don't doubt it, but just curious, what kind of performance comparisons did you actually perform? Also aren't you going to complain to Mikael that he listed Female first, which is the opposite of your desired output? – Aaron Bertrand Apr 24 '12 at 16:10
  • @AaronBertrand - :) I left that one out on purpose. It is easily fixed but I think that if the order is important for the root it might be for the other levels as well so... without a clearer description of the expected order I choose to do nothing, – Mikael Eriksson Apr 24 '12 at 16:16
4

Here is the code for SQL Server 2005, which only required minor adjustments from the answer I gave yesterday which was requested for SQL Server 2008 specifically:

DECLARE @x TABLE(Gender VARCHAR(6), Age INT, Name VARCHAR(32));

INSERT @x SELECT 'Male',   30, 'Bill'  UNION ALL SELECT 'Female', 27, 'Jenny'
UNION ALL SELECT 'Female', 27, 'Debby' UNION ALL SELECT 'Male',   44, 'Frank';

DECLARE @html NVARCHAR(MAX);
SET @html = N'';

;WITH x AS ( SELECT x.Age, x.Gender, x.Name,
    dr = DENSE_RANK() OVER (PARTITION BY x.Gender ORDER BY x.Age),
    gn = ROW_NUMBER() OVER (PARTITION BY x.Gender ORDER BY x.Age),
    rn = ROW_NUMBER() OVER (ORDER BY x.Gender DESC, x.Age)
  FROM @x AS x ) SELECT @html = @html + 
    CHAR(13) + CHAR(10) + CASE WHEN c1.gn = 1 THEN 
        CASE WHEN c1.rn > 1 THEN '</li></ul></li></ul>' ELSE '' END + '<ul><li>' 
        + c1.Gender ELSE '' END + CHAR(13) + CHAR(10) + CHAR(9) 
        + CASE WHEN c1.gn = 1 OR c1.Age <> c3.Age THEN 
        CASE WHEN c1.gn > 1 THEN '</li>' ELSE '<ul>' END + '<li>' 
        + CONVERT(VARCHAR(32), c1.Age) ELSE '' END + CHAR(13) + CHAR(10) + CHAR(9) 
        + CHAR(9) + CASE WHEN (c1.gn = 1 OR c1.Age <> c3.Age) THEN '<ul>' ELSE '' END 
        + '<li>' + c1.Name + '</li>' + CASE WHEN c1.Age <> c2.Age OR c1.dr <> c2.dr 
        THEN '</ul>' ELSE '' END
FROM x AS c1 
LEFT OUTER JOIN x AS c2
ON c1.rn = c2.rn - 1
LEFT OUTER JOIN x AS c3
ON c1.rn = c3.rn + 1
ORDER BY c1.Gender DESC, c1.Age;

SELECT @html = @html + '</ul></li></ul></li></ul>';

PRINT @html; -- note you will need to deal with this 
             -- in another way if the string is large

And here is proof that it works:

enter image description here

Community
  • 1
  • 1
Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
  • Yes, this does work in '05, but next question is.... What did you mean when you wrote this `@html -- note you will need to deal with this in another way if the string is large` – Control Freak Apr 24 '12 at 15:23
  • btw. I'm building the CTE with 30 rows. – Control Freak Apr 24 '12 at 15:28
  • I don't see any unclosed `` issues. The output I've produced isn't formatted exactly like your example above in terms of white space / indent / carriage returns, but in terms of the HTML it matches 100% tag for tag. Please point out where the output above is missing any `` tags compared to your sample in the question. – Aaron Bertrand Apr 24 '12 at 15:28
  • PS the comment about the HTML is just indicating that the `PRINT` command will only show *n* characters in Management Studio (and the value of *n* depends on your settings). So if the HTML string is large, it may *look* like it is getting cut off, but it isn't, and will work fine if you extract it from an application other than SSMS. – Aaron Bertrand Apr 24 '12 at 15:31
  • Why are you mad? What am i supposed to do, post 100's of lines of HTML code in StackOverflow as the example result? – Control Freak Apr 24 '12 at 15:53
  • I was analyzing the html code, it didn't show it all in the SQL result so i can really compare, but I do appreciate your efforts for helping my from yesterday on this and wasn't trying to cause any `DRAMA`, sorry. :) – Control Freak Apr 24 '12 at 16:02
  • In the last code, but in this code i just copied from your answer, not sure how much is different, but the other code yesterday wasn't working in '05, this is.. but i can't see all the result of the @html var because its cutting off after a certain # of char's, makes it hard to see if the html is valid. – Control Freak Apr 24 '12 at 16:08
0
SET ANSI_WARNINGS ON;

DECLARE @Test TABLE
(
    Gender  VARCHAR(10) NOT NULL,
    Age     INT NOT NULL,
    Name    VARCHAR(50) NOT NULL
);

INSERT  @Test (Gender, Age, Name)
SELECT  'Male'  ,30 ,'Bill'
UNION ALL
SELECT  'Female',27 ,'Jenny'
UNION ALL
SELECT  'Female',27 ,'Debby'
UNION ALL
SELECT  'Male'  ,44 ,'Frank';

DECLARE @x XML;
SET     @x = 
(
    SELECT  *
    FROM    @Test t
    FOR XML RAW, ROOT
);
SELECT @x AS [Source];
SELECT @x.query('
    for $g in distinct-values(//root/row/@Gender) (: or for $g in ("Male", "Female") :)
    return
    <ul>
        <li>
            {data($g)}
            <ul>
            {
                for $a in distinct-values(//root/row[@Gender=$g]/@Age)
                return <li>{data($a)}
                <ul>
                {
                    for $n in //root/row
                    where $n/@Gender=$g and $n/@Age = $a
                        return <li>{data($n/@Name)}</li>
                }</ul></li>
            }
            </ul> 
        </li>
    </ul>
') AS Result;

Refences: SQL Server XQuery

Bogdan Sahlean
  • 19,233
  • 3
  • 42
  • 57