0

For example:

  • Pillars 101 in an apartment
  • Zuzu Durga International Hotel
  • Wyndham Garden Fresh Meadows

Need to sort the above as,

  • 101 an apartment in Pillars
  • Durga Hotel International Zuzu
  • Fresh Garden Meadows Wyndham
Shnugo
  • 66,100
  • 9
  • 53
  • 114
Abynez
  • 3
  • 1
  • 4
  • 6
    This is not a typical requirement in most database work. You never told us which DB you are using, but most likely you would need a UDF for this. The atomic unit of work in a table is usually the cell itself. – Tim Biegeleisen Jan 05 '18 at 10:57
  • 1
    This is incredibly difficult to achieve. For example, there are cases where place name is not simply a single word (eg: New York). So you can't simply substring it. Do you have a list/table with place names? – HoneyBadger Jan 05 '18 at 11:03
  • Am using SQL server 2016 and I need to sort the values in each cell of a particular column. Example as defined above. Is there any predefined function to sort alpha numeric words? – Abynez Jan 05 '18 at 11:06
  • @HoneyBadger YEah I do have it. Its' okay if it sort the city name in the content (eg: San Mateo - Mateo San) – Abynez Jan 05 '18 at 11:08
  • @Abynez There are no words in a *field*. It's just a string value with text. Some of the characters may be `,` or ` ` or ``. That doesn't mean they have any special meaning or ordering. You have a fundamental design bug - breaking 1st Normal Form to store multiple items in a single field. Fix the bug. – Panagiotis Kanavos Jan 05 '18 at 11:50
  • @Abynez on the other hand, assuming the contents have no special meaning and thus can't/shouldn't appear as fields or records, you could use SQL Server's JSON support and store the data as a JSON array. Then use the JSON functions to retrieve individual values. For existing data you could use `REPLACE` to replace spaces with `','` and wrap the result in `['`, ']`, converting it to a JSON array – Panagiotis Kanavos Jan 05 '18 at 11:55

3 Answers3

6

Try this:

DECLARE @tbl TABLE(YourString VARCHAR(100));
INSERT INTO @tbl VALUES
 ('Pillars 101 in an apartment')
,('Zuzu Durga International Hotel')
,('Wyndham Garden Fresh Meadows');

SELECT CAST('<x>' + REPLACE((SELECT YourString AS [*] FOR XML PATH('')),' ','</x><x>') + '</x>' AS XML).query
('
    for $x in /x
    order by $x
    return
    concat($x/text()[1], " ")   
').value('.','varchar(max)')
FROM @tbl; 

The code will frist transfer your text in an XML like <x>Pillars</x><x>101</x> ....
Then a FLWOR XQuery is used to return the text parts sorted.
The last call to .value() will return the sorted fragments as text again.

The result

101  Pillars  an  apartment  in 
Durga  Hotel  International  Zuzu 
Fresh  Garden  Meadows  Wyndham 

Final statement

This code is kind of an exercise. Your design is really bad and should be changed...

Shnugo
  • 66,100
  • 9
  • 53
  • 114
2

So there's nothing that you can do natively. If you want to sort the values just as a return value, i.e. not update the database itself, you can transform the results with either a stored procedure or perhaps a view.

So let's construct an answer.

Let's just assume you want to do it visually, for a single row. If you have SQL 2016 you can use STRING_SPLIT but SQL Fiddle doesn't, so I used a common UDF fnSplitString

http://sqlfiddle.com/#!6/7194d/2

SELECT value FROM fnSplitString('Pillars 101 in an apartment', ' ') WHERE RTRIM(value) <> '';

That gives me each word, split out. What about ordering it?

SELECT value FROM fnSplitString('Pillars 101 in an apartment', ' ') WHERE RTRIM(value) <> '' ORDER BY value;

And if I want to do it for each row in the DB table I have? http://sqlfiddle.com/#!6/7194d/8

SELECT split.value FROM [Data] d CROSS APPLY dbo.fnSplitString(IsNull(d.Value,''), ' ') AS split WHERE RTRIM(split.value) <> '' ORDER BY value;

That's sort of helpful, except now all my words are jumbled. Let's go back to our original query and identify each row. Each row probably has an Identity column on it. If so, you've got your grouping there. If not, you can use ROW_NUMBER, such as:

SELECT ROW_NUMBER() OVER(ORDER BY d.Value) AS [Identity] -- here, use identity instead of row_number , d.Value FROM [Data] d

If we then use this query as a subquery in our select, we get:

http://sqlfiddle.com/#!6/7194d/21

SELECT d.[Identity], split.value FROM ( SELECT ROW_NUMBER() OVER(ORDER BY d.Value) AS [Identity] -- here, use identity instead of row_number , d.Value FROM [Data] d ) d CROSS APPLY dbo.fnSplitString(IsNull(d.Value,''), ' ') AS split WHERE RTRIM(split.value) <> '' ORDER BY d.[Identity], value;

This query now sorts all rows within each identity. But now you need to reconstruct those individual words back into a single string, right? For that, you can use STUFF. In my example I use a CTE because of SQL Fiddle limitations but you could use a temp table, too.

WITH tempData AS ( SELECT d.[Identity], split.value FROM ( SELECT ROW_NUMBER() OVER(ORDER BY d.Value) AS [Identity] -- here, use identity instead of row_number , d.Value FROM [Data] d ) d CROSS APPLY dbo.fnSplitString(IsNull(d.Value,''), ' ') AS split WHERE RTRIM(split.value) <> '' ) SELECT grp.[Identity] , STUFF((SELECT N' ' + [Value] FROM tempData WHERE [Identity] = grp.[Identity] ORDER BY Value FOR XML PATH(N'')) , 1, 1, N'') FROM (SELECT DISTINCT [Identity] FROM tempData) AS grp

Here's the end result fiddle: http://sqlfiddle.com/#!6/7194d/27

As expressed in comments already, this is not a common case for SQL. It's an unnecessary burden on the server. I would recommend pulling data out of SQL and sorting it through your programming language of choice; or making sure it's sorted as you insert it into the DB. I went through the exercise because I had a few minutes to kill :)

Eli Gassert
  • 9,745
  • 3
  • 30
  • 39
1

Already +1 on Shnugo's solution. I actually watch for his posts.

Just another option use a parse UDF in concert with a Cross Apply.

Example

Select B.*
 From  YourTable A
 Cross Apply ( Select Sorted=Stuff((Select ' ' +RetVal From [dbo].[tvf-Str-Parse](A.SomeCol,' ') Order By RetVal For XML Path ('')),1,1,'')  )B

Returns

Sorted
101 an apartment in Pillars
Durga Hotel International Zuzu
Fresh Garden Meadows Wyndham

The UDF if Interested

CREATE FUNCTION [dbo].[tvf-Str-Parse] (@String varchar(max),@Delimiter varchar(10))
Returns Table 
As
Return (  
    Select RetSeq = Row_Number() over (Order By (Select null))
          ,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
    From  (Select x = Cast('<x>' + replace((Select replace(@String,@Delimiter,'§§Split§§') as [*] For XML Path('')),'§§Split§§','</x><x>')+'</x>' as xml).query('.')) as A 
    Cross Apply x.nodes('x') AS B(i)
);
--Thanks Shnugo for making this XML safe
--Select * from [dbo].[tvf-Str-Parse]('Dog,Cat,House,Car',',')
John Cappelletti
  • 79,615
  • 7
  • 44
  • 66
  • Hi, thx for your support and just a tiny hint: It's fine to have the parser *XML safe*, but without `,TYPE).value('.','nvarchar(max)')` Your output would not be re-escaped :-D You're moving quickly by the way! – Shnugo Jan 05 '18 at 14:23
  • @Shnugo Tremendous eye! I'll make the update in a few. – John Cappelletti Jan 05 '18 at 15:28