Home | About | Resources | Contact | Partners
MUST Logo

Layout for printing

Download Free MUST Trial Today



"MUST allows our business to use Access to flexibly RAD our systems and then easily move production systems to SQL Server"
Karen Spencer, Partner and Business Manager,
Gravitas IT solutions

"FANTASTIC! What was going to take me hours hand (re)coding Stored Procs just worked..."
David Peach, I.T. Management & Support Services,
Qbt Consulting

"It has saved me a lot of time"
Julian Baker,
The Access Man



Packing up Text Fields using a
Common Table Expression in SQL Server
(11th September 2011)

One of the shortcomings of SQL is that you can’t easily perform a SUM over a text field, packing up the values, and there are many situations when we want to concatenate the multiple text values into a string.

In this article we will look at how to achieve this using a Common Table Expression (CTE), we will use the Products table in Northwind, and set the challenge to concatenate together all the Product Names in each product category.

CTE expressions consist of two parts, the part that gets the parent records, and the part that gets the child records, and the child records can recursively use the expression to get more dependent records.

  ;WITH CTE_Products AS (
    -- This defines the parent record
  UNION ALL
    -- This defines the child record
    -- and can refer back to the CTE_Products object
    -- to generate recursion
  )

What you may not immediately spot is that CTE expressions are very useful in situations where you may not have a hierarchy of data, but simply used as a clever technique for assembling records. In our example we are going to use a temporary table, if you have a field which gathers and orders records sequentially then you can avoid the need to create a temporary table.

To better understand how to use this technique, we will start with an example that packs up a set of product names for a single category, and then we will show a more generalised example.

Packing up text fields

We are starting with a table that contains a list of product names, each assigned to a category.



The result we want to obtain for Category = 1, is shown below:-



We start by creating a temporary table which will hold the list of product names for category =1, ordered by the sequential key AnId.

  CREATE TABLE #TempResults(
    AnID INT IDENTITY(1,1),
    ProductName VARCHAR(40)
  )

  INSERT INTO #TempResults(ProductName)
  SELECT ProductName
  FROM Products
  WHERE CategoryID = 1
  ORDER BY ProductName

If we start by just executing the SQL which gets the parent record, then that is as follows:-

  SELECT TOP 1 p.AnID,
  CONVERT(VARCHAR(1024),ProductName) As ProductName
  FROM
    #TempResults p

This returns the following single row:-



If we were to execute the following CTE Expression:-

  ;WITH CTE_Products AS (
    -- This defines the parent record
    SELECT TOP 1 p.AnID,
    CONVERT(VARCHAR(1024),ProductName) As ProductName
    FROM
      #TempResults p
    UNION ALL
    -- This defines the child record
    SELECT c.AnID, c.ProductName
    FROM
      #TempResults c
  )

  SELECT * FROM CTE_Products

Then we would see a union between the parent and all the child related records:-



But we want to process the child records one at a time concatenating the product names, so we need the following expression (notice that the child part is recursive and makes a reference to CTE_Products):-

  ;WITH CTE_Products AS (
      -- This defines the parent record
      SELECT TOP 1 p.AnID,
      CONVERT(VARCHAR(1024),ProductName) As ProductName
      FROM
        #TempResults p
    UNION ALL
      -- This defines the child record
      SELECT c.AnID,
      CONVERT(VARCHAR(1024),p.ProductName + CHAR(13) + CHAR(10)
      + c.ProductName) As ProductName
      FROM
        #TempResults c
          INNER JOIN CTE_Products p
          ON c.AnID = p.AnID+1
  )

  SELECT * FROM CTE_Products

In the above code we used the TOP function to return the first record.

This results in the following records:-



By altering the select to the following:-

  SELECT TOP 1 *
  FROM CTE_Products
  ORDER BY AnID DESC

We can return the one desired record:-



Returning multiple results

The next example shows how to generalise the above to return a record for each available category, which returns the following result:-



To do this we use the following:-

  IF OBJECT_ID('tempdb..#TempResults') IS NOT NULL DROP TABLE #TempResults

  CREATE TABLE #TempResults(
    AnID INT IDENTITY(1,1),
    ProductName VARCHAR(40),
    CategoryID INT
  )

  INSERT INTO #TempResults(ProductName, CategoryID)
  SELECT ProductName, CategoryID
  FROM Products
  ORDER BY CategoryID, ProductName

  ;WITH CTE_Products AS (
      -- This defines the parent record
      SELECT p.AnID, p.CategoryID,
      CONVERT(VARCHAR(1024),ProductName) As ProductName
      FROM
        #TempResults p
          INNER JOIN
            (SELECT MIN(p.AnID) As MinAnID, p.CategoryID
            FROM
              #TempResults p
            GROUP BY p.CategoryID ) As FirstInEachGroup
            ON p.AnID = FirstInEachGroup.MinAnID
  UNION ALL
      -- This defines the child record
      SELECT c.AnID, c.CategoryID,
      CONVERT(VARCHAR(1024),p.ProductName + CHAR(13) + CHAR(10)
      + c.ProductName) As ProductName
      FROM
        #TempResults c
          INNER JOIN CTE_Products p
            ON c.AnID = p.AnID+1
            AND c.categoryID = p.categoryID
  )

  SELECT CategoryID , ProductName, AnID
  FROM CTE_Products cte
  WHERE AnID = (SELECT Max(t.AnID) AS MaxAnID
            FROM #TempResults t
            WHERE t.CategoryID = cte.categoryID)

Simplifying the process

If your table already has a sequential key, the you do not need to create a temporary table, but can develop the CTE against your physical table, this also means that you can create a function to return the packed text fields.

Download Code

The sample TSQL can be downloaded from here.

MUST+SQL allows you to upsize into multiple schemas splitting up an application into manageable chunks or bringing together multiple databases and simplifying security. Click below to improve your development productivity.



Top of Page