Go Premium for a chance to win a PS4. Enter to Win

x
?
Solved

String concat order problem when grouping

Posted on 2009-05-15
6
Medium Priority
?
717 Views
Last Modified: 2012-05-07
Hi

I have written a CLR string concat function, the problem is when using it in my sql statement the order of the items that are concatenated seems to be dependant on the column itself.

How to be explicit on the order ?


My current sql

 SELECT cast(dbo.StringConcat(cast(description as varchar)) as varchar(max)) AS [description],
[vw_ECS_OM_JobNotes_Job].[reference] AS [reference] FROM [vw_ECS_OM_JobNotes_Job]  
GROUP BY  [vw_ECS_OM_JobNotes_Job].[reference]  

results in the actual description being alphabetically appended to the aggregate result, however i have another column called sequence that i need to be the order...

I tried ordering inside the view but this doesnt work either ?
0
Comment
Question by:crazyman
  • 4
  • 2
6 Comments
 
LVL 13

Author Comment

by:crazyman
ID: 24394543
I also tried this, which didnt work either...



 SELECT cast(dbo.StringConcat(cast(description as varchar)) as varchar(max)) AS [description],
[reference] AS [reference] FROM
(
select top 100 percent * from
[vw_ECS_OM_JobNotes_Job]
order by reference,sequence) as sub
GROUP BY  [reference]
0
 
LVL 16

Expert Comment

by:ToddBeaulieu
ID: 24394897
I've never done a CLR function, but my initial guess is that the result set, including the concatenation is all done in the first pass and then the results are sorted. By then, of course, it's too late. If that's the case, you may need to select your "driving data set" into a temp table (ordered) and then select off that, including the concat column.
0
 
LVL 13

Author Comment

by:crazyman
ID: 24395029
Ive debugged the clr function and it clearly adds the items in what seems to be a random order...

Ive attached my function, even when concatinsating on an id column it sometimes does it in a random order...

Any better contactinate aggregate functions around, ive seen some but they almost all require the function to have knowledge of the calling table, i need something generic...


using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.IO;
using System.Diagnostics;
namespace Peverel.SqlExtensions
{
    [SqlUserDefinedAggregate(
        //use CLR serialization to serialize the intermediate result. 
      Format.UserDefined,
        //Optimizer property: 
      IsInvariantToNulls = true,
        //Optimizer property: 
      IsInvariantToDuplicates = false,
        //Optimizer property: 
      IsInvariantToOrder = false,
        //Maximum size in bytes of persisted value: 
      MaxByteSize = 8000)]
    public class StringConcat : IBinarySerialize
    {
        /// <summary> 
        /// Variable holds intermediate result of the concatenation 
        /// </summary> 
        private StringBuilder intermediateResult;
        /// <summary> 
        /// Initialize the internal data structures 
        /// </summary> 
        public void Init()
        {
            intermediateResult = new StringBuilder();
        }
        /// <summary> 
        /// Accumulate the next value, nop if the value is null 
        /// </summary> 
        /// <param name="value"></param> 
        public void Accumulate(SqlString value)
        {
            
            if (value.IsNull)
            {
                return;
            }
            intermediateResult.Append(value.Value).Append(' ');
        }
        /// <summary> 
        /// Merge the partially computed aggregate with this aggregate. 
        /// </summary> 
        /// <param name="other"></param> 
        public void Merge(StringConcat other)
        {
            intermediateResult.Append(other.intermediateResult);
        }
        /// <summary> 
        /// Called at end of aggregation, to return results. 
        /// </summary> 
        /// <returns></returns> 
        public SqlString Terminate()
        {
            string output = string.Empty;
            //Delete the trailing comma, if any .
            if (intermediateResult != null && intermediateResult.Length > 0)
                output = intermediateResult.ToString(0, intermediateResult.Length - 1);
            return new SqlString(output);
        }
        public void Read(BinaryReader r)
        {
            intermediateResult = new StringBuilder(r.ReadString());
        }
        public void Write(BinaryWriter w)
        {
            w.Write(intermediateResult.ToString());
        }
    }
 
}

Open in new window

0
Get your Disaster Recovery as a Service basics

Disaster Recovery as a Service is one go-to solution that revolutionizes DR planning. Implementing DRaaS could be an efficient process, easily accessible to non-DR experts. Learn about monitoring, testing, executing failovers and failbacks to ensure a "healthy" DR environment.

 
LVL 16

Accepted Solution

by:
ToddBeaulieu earned 2000 total points
ID: 24395158
The IsInvariantToOrder immediately caught my eye. Sure enough, this is a known issue.

http://msdn.microsoft.com/en-us/library/microsoft.sqlserver.server.sqluserdefinedaggregateattribute.isinvarianttoorder.aspx

Strangely, technet has this article, which seems to be exactly what you're trying to do:

http://technet.microsoft.com/en-us/library/ms131056.aspx

Check this thread out. There's more about how aggregates don't support ordering and also a proposed solution. Maybe it will help.

http://www.eggheadcafe.com/conversation.aspx?messageid=32427691&threadid=32427691
0
 
LVL 13

Author Comment

by:crazyman
ID: 24396635
Hmmm

I had an idea, if i can sort the results in my clr object then i can control the output, however you cant pass the sort column data in as another param, so i thought if you appended it with a delimiter at the start of the data and used it to sort the result.

Anyway it is a bit of a bodge job but it works great...

Sql looks like

 SELECT cast(dbo.StringConcat(cast(sequence as varchar) + '|' + cast(description as varchar)) as varchar(max)) AS [description],
[vw_ECS_OM_JobNotes_Job].[reference] AS [reference] FROM [vw_ECS_OM_JobNotes_Job]  
GROUP BY  [vw_ECS_OM_JobNotes_Job].[reference]  


Code attached..
using System;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.IO;
using System.Diagnostics;
namespace Peverel.SqlExtensions
{
    [SqlUserDefinedAggregate(
        //use CLR serialization to serialize the intermediate result. 
      Format.UserDefined,
        //Optimizer property: 
      IsInvariantToNulls = true,
        //Optimizer property: 
      IsInvariantToDuplicates = false,
        //Optimizer property: 
      IsInvariantToOrder = false,
        //Maximum size in bytes of persisted value: 
      MaxByteSize = 8000)]
    public class StringConcat : IBinarySerialize
    {
        private class AggregateData
        {
            public string Value;
            public string RawValue;
            public int Order = 0;
            private AggregateData() { }
            
            public static AggregateData Parse(string value)
            {
                int order = 0;
                string[] parts = value.Split('|');
                AggregateData ad = new AggregateData();
                ad.Value = value;
                ad.RawValue = value;
                if (parts.Length > 1 && int.TryParse(parts[0],out order))
                {
                    ad.Order = order;
                    ad.Value = value.Substring(order.ToString().Length+1);
                }
                return ad;
            }
        }
        private List<AggregateData> values;
 
        public void Init()
        {
            this.values = new List<AggregateData>();
        }
 
        public void Accumulate(SqlString value)
        {
            this.values.Add(AggregateData.Parse(value.Value));
        }
 
        public void Merge(StringConcat value)
        {
            this.values.AddRange(value.values);
        }
 
        public SqlString Terminate()
        {
            this.values.Sort(new Comparison<AggregateData>(SortValue));
            StringBuilder sb = new StringBuilder();
            foreach (AggregateData ad in this.values)
            {
                //For debug you can see the order val
                sb.Append(String.Format("{0}|{1}",ad.Order,ad.Value));
                sb.Append(ad.Value);
            }
            return new SqlString(sb.ToString());
        }
        private int SortValue(AggregateData a, AggregateData b)
        {
            return a.Order.CompareTo(b.Order);
        }
        public void Read(BinaryReader r)
        {
            int itemCount = r.ReadInt32();
            this.values = new List<AggregateData>(itemCount);
            for (int i = 0; i <= itemCount - 1; i++)
            {
                this.values.Add(AggregateData.Parse(r.ReadString()));
            }
        }
 
        public void Write(BinaryWriter w)
        {
            w.Write(this.values.Count);
            foreach (AggregateData ad in this.values)
            {
                w.Write(ad.RawValue);
            }
        }
 
    }
 
}

Open in new window

0
 
LVL 13

Author Comment

by:crazyman
ID: 24396668
oops just incase anyone else uses this solution comment out the line
sb.Append(String.Format("{0}|{1}",ad.Order,ad.Value));


0

Featured Post

Free Tool: Site Down Detector

Helpful to verify reports of your own downtime, or to double check a downed website you are trying to access.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Recently we ran in to an issue while running some SQL jobs where we were trying to process the cubes.  We got an error saying failure stating 'NT SERVICE\SQLSERVERAGENT does not have access to Analysis Services. So this is a way to automate that wit…
In real business world data are crucial and sometimes data are shared among different information systems. Hence, an agreeable file transfer protocol need to be established.
Familiarize people with the process of retrieving data from SQL Server using an Access pass-thru query. Microsoft Access is a very powerful client/server development tool. One of the ways that you can retrieve data from a SQL Server is by using a pa…
This video shows, step by step, how to configure Oracle Heterogeneous Services via the Generic Gateway Agent in order to make a connection from an Oracle session and access a remote SQL Server database table.

886 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question