2

Suppose I declare a list of integer as following in SQL.

DECLARE integerlist varchar(max) = '45, 3, 23, 100'

How could i make an efficient query to output as follow (in which the number is sort in ascending order)

integerlist = '3, 23, 45, 100'
2
  • 2
    Can you explain why you need to re-order this string in T-SQL? Commented Jan 15, 2016 at 1:08
  • 1
    You have to split the string first to rows, then do the ordering, then concatenate them back again. Still, this should be done in the application side. Commented Jan 15, 2016 at 1:16

2 Answers 2

4

I suggest conversion of the list simply by constructing a sql query of this list and execute this query with required order by, then reconstruct string back!

DECLARE @list varchar(max) = '45, 3, 23, 100', @sql nvarchar(max)
DECLARE @A AS TABLE (A int) 
set @sql='SELECT ' + REPLACE(@list, ',', ' A UNION ALL SELECT ') + ' A ORDER BY A'
INSERT into @A EXECUTE  sp_executesql @sql
SET @list=null
SELECT @list = COALESCE(@list + ', ', '') + cast(A as varchar) FROM @A
SELECT @list

Demo: http://sqlfiddle.com/#!3/9eecb7db59d16c80417c72d1/4892

Update: Using UNION ALL instead of UNION will boost optimization about 3 times for large numbers

Sign up to request clarification or add additional context in comments.

2 Comments

I like You improved the already accepted answer. Your approach is best suitable for smaller data, I added some measures if You are interested.
I already tested for 1000 rows of data in my laptop of windows 10, sql server 2014 it took about 250 ms
1

SQL server is not the best place to do this. Seriously. Being that said, one approach is xml:

DECLARE @list varchar(max) = '45, 3, 23, 100';
DECLARE @myDoc xml = '<a>' + replace(@list, ', ','</a><a>') + '</a>';

SET @list = (SELECT left(name, len(name)-1) FROM
(SELECT T.X.value('.[1]', 'varchar(max)') +', '
FROM @myDoc.nodes('/a') as T(X)
ORDER BY T.X.value('.[1]', 'int') FOR XML PATH('')) as xmlPath(name));

SELECT @list;

EDIT

To show how bad it is to do it on sql server, I made a bechmark

Numbers |UNION ALL |XML       |C#
-------------------------------------
1       |5 ms      |7 ms      |214 ms
10      |5 ms      |6 ms      |7 ms
100     |8 ms      |8 ms      |2 ms
1000    |57 ms     |23 ms     |4 ms
10000   |2,4 sec   |172 ms    |12 ms
100000  |13 min    |1,8 sec   |86 ms
1000000 |N/A       |18 sec    |877 ms
10000000|N/A       |3,2 min   |11 sec

I made it quite unfair for C# - included read from database and write to disc to the time, elapsed first run, single threaded processing. The value for one number is off probably because of JIT, but aside from this the winner is clear. My configuration is SQL server 2014 on i5-3570K.

Here is full code

--------------------------------------------
-- Just table of numbers 1, 2, 3, 4, 5,...
--------------------------------------------
CREATE TABLE Tally (Number int not null);  
;WITH
  Pass0 as (select 1 as C union all select 1), --2 rows
  Pass1 as (select 1 as C from Pass0 as A, Pass0 as B),--4 rows
  Pass2 as (select 1 as C from Pass1 as A, Pass1 as B),--16 rows
  Pass3 as (select 1 as C from Pass2 as A, Pass2 as B),--256 rows
  Pass4 as (select 1 as C from Pass3 as A, Pass3 as B),--65536 rows
  Pass5 as (select 1 as C from Pass3 as A, Pass4 as B),--16M rows
  T as (select row_number() over(order by C) as Number from Pass5)
INSERT Tally (Number)
    SELECT Number
        FROM T
        WHERE Number <= 10000000;
ALTER TABLE Tally ADD CONSTRAINT PK_Tally PRIMARY KEY CLUSTERED (Number);

--------------------------------------------
-- Each varchar constains string of random numbers smaller than 1000000
-- separated by ", ". Count of random numbers is txt_num.
--------------------------------------------
CREATE TABLE [dbo].[Texts](
    [txt_num] [int] NOT NULL,
    [txt_text] [varchar](max) NOT NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]

--------------------------------------------
-- Filling of the table
--------------------------------------------
INSERT INTO Texts (txt_num, txt_text)
SELECT txt_num, txt_text
FROM 
(SELECT txt_num, (SELECT left(name, len(name)-1) as txt_text FROM
    (SELECT convert(varchar(max), convert(int, 1000000.0*RAND(convert(varbinary, newid()))))+', ' 
    FROM Tally WHERE Number<=txt_num FOR XML PATH('')) as xmlPath(name)) txt_text 
FROM (VALUES (1), (10), (100), (1000), (10000), (100000), (1000000), (10000000)) AS Tmp(txt_num))Tmp3

--------------------------------------------
-- UNION ALL function
--------------------------------------------
DECLARE @list varchar(max);
SELECT @list = (SELECT txt_text FROM Texts WHERE txt_num=1);--10,100,1000,...
DECLARE @sql nvarchar(max);

DECLARE @A AS TABLE (A int) 
SET @sql='SELECT ' + REPLACE(@list, ',', ' A UNION ALL SELECT ') + ' A ORDER BY A'
INSERT into @A EXECUTE  sp_executesql @sql
SET @list=null
SELECT @list = COALESCE(@list + ', ', '') + cast(A as varchar) FROM @A
SELECT @list

--------------------------------------------
-- XML function
--------------------------------------------
DECLARE @list varchar(max);
SELECT @list = (SELECT txt_text FROM Texts WHERE txt_num=1);--10,100,1000,...
DECLARE @myDoc xml = '<a>' + replace(@list, ', ','</a><a>') + '</a>';

SET @list = (SELECT left(name, len(name)-1) FROM
(SELECT T.X.value('.[1]', 'varchar(max)') +', '
FROM @myDoc.nodes('/a') as T(X)
ORDER BY T.X.value('.[1]', 'int') FOR XML PATH('')) as xmlPath(name));

SELECT @list;

--------------------------------------------
-- C# code
--------------------------------------------
static void Main(string[] args)
{
    using (var dc = new DataClasses1DataContext())
    {
        for (int i = 1; i <= 10000000; i *= 10)
        {
            var timer = System.Diagnostics.Stopwatch.StartNew();

            string input = dc.Texts
                .Where(w => w.txt_num == i)
                .Select(s => s.txt_text)
                .Single();

            string output = string.Join(", ", input
                .Split(", ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
                .Select(s => int.Parse(s))
                .OrderBy(o => o)
                );

            System.IO.StreamWriter sw = new System.IO.StreamWriter("cs" + i);
            sw.WriteLine(output);
            sw.Close();

            Console.WriteLine(i + " - " + timer.ElapsedMilliseconds + " miliseconds");
        }
        Console.ReadKey();
    }
}

2 Comments

It takes 674 ms in my machine to order 1000 rows. although your query is still faster... thank you for the optimization :)
@Antonion I made optimization to use UNION ALL instead of UNION now works about 3 times faster and take about 250 ms for 1000 values. thank you to let me think about benchmark and optimization for this case.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.