Sorting varchar field with mixed alphanumeric data - sql-server

I searched and read a lot of answers on here, but can't find one that will answer my problem, (or help me to find the answer on my own).
We have a table which contains a varchar display field, who's data is entered by the customer.
When we display the results, our customer wants the results to be ordered "correctly".
A sample of what the data could like is as follows:
"AAA 2 1 AAA"
"AAA 10 1 AAA"
"AAA 10 2 BAA"
"AAA 101 1 AAA"
"BAA 101 2 BBB"
"BAA 101 10 BBB"
"BAA 2 2 AAA"
Sorting by this column ASC returns:
1: "AAA 10 1 AAA"
2: "AAA 10 2 BAA"
3: "AAA 101 1 AAA"
4: "AAA 2 1 AAA"
5: "BAA 101 10 BBB"
6: "BAA 101 2 BBB"
7: "BAA 2 2 AAA"
The customer would like row 4 to actually be the first row (as 2 comes before 10), and similarly row 7 to be between rows 4 and 5, as shown below:
1: "AAA 2 1 AAA"
2: "AAA 10 1 AAA"
3: "AAA 10 2 BAA"
4: "AAA 101 1 AAA"
5: "BAA 2 2 AAA"
6: "BAA 101 10 BBB"
7: "BAA 101 2 BBB"
Now, the real TRICKY bit is, there is no hard and fast rule to what the data will look like in this column; it is entirely down to the customer as to what they put in here (the data shown above is just arbitrary to demonstrate the problem).
Any Help?
EDIT:
learning that this is referred to as "natural sorting" has improved my search results massively
I'm going to give the accepted answer to this question a bash and will update accordingly:
Natural (human alpha-numeric) sort in Microsoft SQL 2005

First create this function
Create FUNCTION dbo.SplitAndJoin
(
#delimited nvarchar(max),
#delimiter nvarchar(100)
) RETURNS Nvarchar(Max)
AS
BEGIN
declare #res nvarchar(max)
declare #t TABLE
(
-- Id column can be commented out, not required for sql splitting string
id int identity(1,1), -- I use this column for numbering splitted parts
val nvarchar(max)
)
declare #xml xml
set #xml = N'<root><r>' + replace(#delimited,#delimiter,'</r><r>') + '</r></root>'
insert into #t(val)
select
r.value('.','varchar(max)') as item
from #xml.nodes('//root/r') as records(r)
SELECT #res = STUFF((SELECT ' ' + case when isnumeric(val) = 1 then RIGHT('00000000'+CAST(val AS VARCHAR(8)),8) else val end
FROM #t
FOR XML PATH('')), 1, 1, '')
RETURN #Res
END
GO
This function gets an space delimited string and split it to words then join them together again by space but if the word is number it adds 8 leading zeros
then you use this query
Select * from Test
order by dbo.SplitAndJoin(col1,' ')
Live result on SQL Fiddle

Without consistency, you only have brute force
Without rules, your brute force is limited
I've made some assumptions with this code: if it starts with 3 alpha characters, then a space, then a number (up to 3 digits), let's treat it differently.
There's nothing special about this - it is just string manipulation being brute forced in to giving you "something". Hopefully it illustrates how painful this is without having consistency and rules!
DECLARE #t table (
a varchar(50)
);
INSERT INTO #t (a)
VALUES ('AAA 2 1 AAA')
, ('AAA 10 1 AAA')
, ('AAA 10 2 BAA')
, ('AAA 101 1 AAA')
, ('BAA 101 2 BBB')
, ('BAA 101 10 BBB')
, ('BAA 2 2 AAA')
, ('Completely different')
;
; WITH step1 AS (
SELECT a
, CASE WHEN a LIKE '[A-Z][A-Z][A-Z] [0-9]%' THEN 1 ELSE 0 END As fits_pattern
, CharIndex(' ', a) As first_space
FROM #t
)
, step2 AS (
SELECT *
, CharIndex(' ', a, first_space + 1) As second_space
, CASE WHEN fits_pattern = 1 THEN Left(a, 3) ELSE 'ZZZ' END As first_part
, CASE WHEN fits_pattern = 1 THEN SubString(a, first_space + 1, 1000) ELSE 'ZZZ' END As rest_of_it
FROM step1
)
, step3 AS (
SELECT *
, CASE WHEN fits_pattern = 1 THEN SubString(rest_of_it, 1, second_space - first_space - 1) ELSE 'ZZZ' END As second_part
FROM step2
)
SELECT *
, Right('000' + second_part, 3) As second_part_formatted
FROM step3
ORDER
BY first_part
, second_part_formatted
, a
;
Relevant, sorted results:
a
---------------------
AAA 2 1 AAA
AAA 10 1 AAA
AAA 10 2 BAA
AAA 101 1 AAA
BAA 2 2 AAA
BAA 101 10 BBB
BAA 101 2 BBB
Completely different
This code can be vastly improved/shortened. I've just left it verbose in order to give you some clarity over the steps taken.

Related

STRING_SPLIT with Row Number

Here is what I am trying to produce:
Row_Num Person Value Row_Number
1 Leo Math 1
1 Leo Science 2
1 Leo History 3
1 Leo Math,Science,History 4
2 Robert Gym 2
2 Robert Math 3
2 Robert History 4
2 Robert Gym,Math,History 1
3 David Art 1
3 David Science 2
3 David English 3
3 David History 4
3 David Computer Science 5
3 David Art,Science,English,History,Computer Science 6
This is the code I am using:
with part_1 as
(
select
1 as [Row_Num],
'Leo' as [Person],
'Math,Science,History' as [Subjects]
---
union
---
select
'2',
'Robert',
'Gym,Math,History'
---
union
---
select
'3',
'David',
'Art,Science,English,History,Computer Science'
---
)
----------------------------------------------------------------------
select
[Row_Num],
[Person],
[Subjects]
into
#part1
from
part_1;
go
--------------------------------------------------------------------------------
with p_2 as(
select
[Row_Num],
[Person],
--[Subjects],
[value]
from
#part1
cross apply
STRING_SPLIT([Subjects],',')
union all
select
[Row_Num],
[Person],
[Subjects]
from
#part1
)
select
[Row_Num]
,[Person]
,[Value]
,row_number()
over(Partition by Row_Num order by (select 1)) as [Row_Number]
from
p_2
order by
[Row_Num]
,[Row_Number]
Here is what I am producing:
Row_Num Person Value Row_Number
1 Leo Math 1
1 Leo Science 2
1 Leo History 3
1 Leo Math,Science,History 4
2 Robert Gym,Math,History 1
2 Robert Gym 2
2 Robert Math 3
2 Robert History 4
3 David Art 1
3 David Science 2
3 David English 3
3 David History 4
3 David Computer Science 5
3 David Art,Science,English,History,Computer Science 6
It looks good, until you look at Robert. All of the subjects are on the first row, instead of the bottom.
Any suggestions?
STRING_SPLIT is documented to not "care" about ordinal position:
The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string. You can override the final sort order by using an ORDER BY clause on the SELECT statement (ORDER BY value).
If the ordinal position of the data is important, don't use STRING_SPLIT. Personally, I recommend using delimitedsplit8k_LEAD, which includes a itemnumber column.
But idealy, the real solution is to stop storing delimited data in your database. Create 2 further tables, one with a list of the subjects, and another that creates a relationship between the student and subject.
Note that SQL Server 2022 brings a new parameter to STRING_SPLIT called ordinal which, when 1 is passed to it, will cause STRING_SPLIT to return an additional column (called ordinal) with the ordinal position of the value within the string; so you could add that column to your ORDER BY to ensure the ordering in maintained.
Of course, this doesn't change the fact that you should not be storing delimited data to start with, and should still be aiming to fix your design.
Here's an easy solution.
DECLARE #x VARCHAR(1000) = 'a,b,c,d,e,f,g';
DECLARE #t TABLE
(
[Index] INT PRIMARY KEY IDENTITY(1, 1)
, [Value] VARCHAR(50)
)
INSERT INTO #t (VALUE)
SELECT [Value]
FROM string_split(#x, ',')
SELECT * FROM #t
Wrap it like this:
CREATE FUNCTION SPLIT_STRING2
(
#x VARCHAR(5000)
, #y VARCHAR(5000)
) RETURNS #t TABLE
(
[Index] INT PRIMARY KEY IDENTITY(1, 1)
, [Value] VARCHAR(50)
)
AS
BEGIN
INSERT INTO #t (VALUE)
SELECT [Value]
FROM string_split(#x, #y)
RETURN
END
Here is a recursive CTE method to parse the Subject. There's one anchor and two recursive queries. The first recursive query parses the Subjects. The second recursive query adds the summary. I added the special case of one subject. The summary and parsed subjects records are the same. (This means there was only one subjects in Subjects.) It is filtered out in this example.
This only works because a recursive CTE has only the records from the prior iteration. If looking for set N+1 and referring back to the CTE, the CTE has records from set N, not sets 1 through N. (Reminds me of the math proof - prove n = 1 is true, then prove if n true then n+1 true. Then it's true for all n > 0.)
DECLARE #delimiter char(1) = ',';
WITH part_1 as (
-- Subjects has 0 or more "tokens" seperated by a comma
SELECT *
FROM (values
(1, 'Leo', 'Math,Science,History'),
('2', 'Robert', 'Gym,Math,History'),
('3', 'David', 'Art,Science,English,History,Computer Science'),
('4', 'Lazy', 'Art')
) t ([Row_Num],[Person],[Subjects])
), part_2 as (
-- Anchor on the first token. Every token has delimiter before an after, even if we have to pretend it exists
SELECT Row_Num, Person, Subjects,
LEN(Subjects) + 1 as [index_max], -- the last index possible is a "pretend" index just after the end of the string
1 as N, -- this is the first token
0 as [index_before], -- for the first token, pretend the delimiter exists just before the first character at index 0
CASE WHEN CHARINDEX(#delimiter, Subjects) > 0 THEN CHARINDEX(#delimiter, Subjects) -- delimiter after exists
ELSE LEN(Subjects) + 1 -- pretend the delimiter exists just after the end of the string at index len + 1
END as [index_after],
CAST(1 as bit) as [is_token] -- needed to stop the 2nd recursion
FROM part_1
-- Recursive part that checks records for token N to add records for token N + 1 if it exists
UNION ALL
SELECT Row_Num, Person, Subjects,
index_max,
N + 1,
index_after, -- the delimiter before is just the prior token's delimiter after.
CASE WHEN CHARINDEX(#delimiter, Subjects, index_after + 1) > 0 THEN CHARINDEX(#delimiter, Subjects, index_after + 1) -- delimiter after exists
ELSE index_max -- pretend the delimiter exists just after the end of the string at index len + 1
END,
CAST(1 as bit) as [is_token] -- needed to stop the 2nd recursion
FROM part_2 -- a recursive CTE has only the prior result for token N, not accumulated result of tokens 1 to N
WHERE index_after > 0 AND index_after < index_max
UNION ALL
-- Another recursive part that checks if the prior token is the last. If the last, add the record with full string that was just parsed.
SELECT Row_Num, Person, Subjects,
index_max,
N + 1, -- this is not a token
0, -- the entire originsal string is desired
index_max, -- the entire originsal string is desired
CAST(0 as bit) as [is_token] -- not a token - stops this recursion
FROM part_2 -- this has only the prior result for N, not accumulated result of 1 to N
WHERE index_after = index_max -- the prior token was the last
AND is_token = 1 -- it was a token - stops this recursion
AND N > 1 -- add this to remove the added record it it's identical - 1 token
)
SELECT Row_Num, Person, TRIM(SUBSTRING(Subjects, index_before + 1, index_after - index_before - 1)) as [token], N,
index_max, index_before, index_after, is_token
FROM part_2
ORDER BY Row_Num, N -- Row_Num, is_token DESC, N is not required
Row_Num Person token N index_max index_before index_after is_token
----------- ------ -------------------------------------------- ----------- ----------- ------------ ----------- --------
1 Leo Math 1 21 0 5 1
1 Leo Science 2 21 5 13 1
1 Leo History 3 21 13 21 1
1 Leo Math,Science,History 4 21 0 21 0
2 Robert Gym 1 17 0 4 1
2 Robert Math 2 17 4 9 1
2 Robert History 3 17 9 17 1
2 Robert Gym,Math,History 4 17 0 17 0
3 David Art 1 45 0 4 1
3 David Science 2 45 4 12 1
3 David English 3 45 12 20 1
3 David History 4 45 20 28 1
3 David Computer Science 5 45 28 45 1
3 David Art,Science,English,History,Computer Science 6 45 0 45 0
4 Lazy Art 1 4 0 4 1

PATINDEX with SOUNDEX

Want to search the string using PATINDEX and SOUNDEX.
I have the following table with some sample data to search the given string using PATINDEX and SOUNDEX.
create table tbl_pat_soundex
(
col_str varchar(max)
);
insert into tbl_pat_soundex values('Smith A Steve');
insert into tbl_pat_soundex values('Steve A Smyth');
insert into tbl_pat_soundex values('A Smeeth Stive');
insert into tbl_pat_soundex values('Steve Smith A');
insert into tbl_pat_soundex values('Smit Steve A');
String to search:- 'Smith A Steve'
SELECT col_str,PATINDEX('%Smith%',col_str) [Smith],PATINDEX('%A%',col_str) [A],PATINDEX('%Steve%',col_str) [Steve]
FROM tbl_pat_soundex
Getting Output:
col_str Smith A Steve
---------------------------------
Smith A Steve 1 7 9
Steve A Smyth 0 7 1
A Smeeth Stive 0 1 0
Steve Smith A 7 13 1
Smit Steve A 0 12 6
Expected Output:
col_str Smith A Steve
---------------------------------
Smith A Steve 1 7 9
Steve A Smyth 9 7 1
A Smeeth Stive 3 1 10
Steve Smith A 7 13 1
Smit Steve A 1 12 6
Tried:
SELECT col_str,
PATINDEX('%'+soundex('Smith')+'%',soundex(col_str)) [Smith],
PATINDEX('%'+soundex('A')+'%',soundex(col_str)) [A],
PATINDEX('%'+soundex('Steve')+'%',soundex(col_str)) [Steve]
FROM tbl_pat_soundex
But getting unexpected result:
col_str Smith A Steve
---------------------------------
Smith A Steve 1 0 0
Steve A Smyth 0 0 1
A Smeeth Stive 0 1 0
Steve Smith A 0 0 1
Smit Steve A 1 0 0
Note: I have 100 Millions of records in the table to search for.
Here's one option, not sure how it would perform with 100 million records considering all that you need to do. You'll have to test that out.
At a high level how I understand this is you basically need
Search all words in a string based on the words of another string
Returning the character starting position in the original string where that word equals or sounds like the search word.
You can use DIFFERENCE() for the comparison:
DIFFERENCE compares two different SOUNDEX values, and returns an
integer value. This value measures the degree that the SOUNDEX values
match, on a scale of 0 to 4. A value of 0 indicates weak or no
similarity between the SOUNDEX values; 4 indicates strongly similar,
or even identically matching, SOUNDEX values.
You'll need to split the string based on the space ' ' and since you're 2008 you'd have to roll your own function.
I used the XML function from here, https://sqlperformance.com/2012/07/t-sql-queries/split-strings, for my examples, you'll obviously need to adjust if you have your own or want to use something different:
CREATE FUNCTION dbo.SplitStrings_XML
(
#List NVARCHAR(MAX),
#Delimiter NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
RETURN
(
SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
FROM
(
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.')
) AS a CROSS APPLY x.nodes('i') AS y(i)
);
GO
I switched and use table variables to show the example, I would suggest not doing that with the amount of data you have and create and use physical tables.
Option 1 - Not dynamic:
DECLARE #tbl_pat_soundex TABLE
(
[col_str] VARCHAR(MAX)
);
INSERT INTO #tbl_pat_soundex
VALUES ( 'Smith A Steve' )
,( 'Steve A Smyth' )
,( 'A Smeeth Stive' )
,( 'Steve Smith A' )
,( 'Smit Steve A' )
SELECT DISTINCT [aa].[col_str]
, MAX([aa].[Smith]) OVER ( PARTITION BY [aa].[col_str] ) AS [Smith]
, MAX([aa].[A]) OVER ( PARTITION BY [aa].[col_str] ) AS [A]
, MAX([aa].[Steve]) OVER ( PARTITION BY [aa].[col_str] ) AS [Steve]
FROM (
SELECT [a].[col_str]
, CASE WHEN DIFFERENCE([b].[item], 'Smith') = 4 THEN
CHARINDEX([b].[item], [a].[col_str])
ELSE 0
END AS [Smith]
, CASE WHEN DIFFERENCE([b].[item], 'A') = 4 THEN
CHARINDEX([b].[item], [a].[col_str])
ELSE 0
END AS [A]
, CASE WHEN DIFFERENCE([b].[item], 'Steve') = 4 THEN
CHARINDEX([b].[item], [a].[col_str])
ELSE 0
END AS [Steve]
FROM #tbl_pat_soundex [a]
CROSS APPLY [dbo].[SplitStrings_XML]([a].[col_str], ' ') [b]
) AS [aa];
Using the function we split the string into individual words
Then we use a case statement to check the DIFFERENCE value
If that DIFFERENCE value equals 4 we then return the CHARINDEX value of the original word against string.
If doesn't equal we return 0
Then from there it's a matter of getting the max value of each based on the original string:
, MAX([aa].[Smith]) OVER ( PARTITION BY [aa].[col_str] ) AS [Smith]
, MAX([aa].[A]) OVER ( PARTITION BY [aa].[col_str] ) AS [A]
, MAX([aa].[Steve]) OVER ( PARTITION BY [aa].[col_str] ) AS [Steve]
To get you your final results:
Option 2 - Dynamic with a pivot:
We'll declare the string we want to search, split that out and search for those individuals words in the original string and then pivot the results.
--This example is using global temp tables as it's showing how
--to build a dynamic pivot
IF OBJECT_ID('tempdb..##tbl_pat_soundex') IS NOT NULL
DROP TABLE [##tbl_pat_soundex];
IF OBJECT_ID('tempdb..##tbl_col_str_SearchString') IS NOT NULL
DROP TABLE [##tbl_col_str_SearchString];
CREATE TABLE [##tbl_pat_soundex]
(
[col_str] VARCHAR(MAX)
);
INSERT INTO [##tbl_pat_soundex]
VALUES ( 'Smith A Steve' )
, ( 'Steve A Smyth' )
, ( 'A Smeeth Stive' )
, ( 'Steve Smith A' )
, ( 'Smit Steve A' );
--What are you searching for?
DECLARE #SearchString NVARCHAR(200);
SET #SearchString = N'Smith A Steve';
--We build a table we load with every combination of the words from the string and the words from the SearchString for easier comparison.
CREATE TABLE [##tbl_col_str_SearchString]
(
[col_str] NVARCHAR(MAX)
, [col_str_value] NVARCHAR(MAX)
, [SearchValue] NVARCHAR(200)
);
--Load that table for comparison
--split our original string into individual words
--also split our search string into individual words and give me all combinations.
INSERT INTO [##tbl_col_str_SearchString] (
[col_str]
, [col_str_value]
, [SearchValue]
)
SELECT DISTINCT [a].[col_str]
, [b].[item]
, [c].[item]
FROM [##tbl_pat_soundex] [a]
CROSS APPLY [dbo].[SplitStrings_XML]([a].[col_str], ' ') [b]
CROSS APPLY [dbo].[SplitStrings_XML](#SearchString, ' ') [c]
ORDER BY [a].[col_str];
--Then we can easily compare each word and search word for those that match or sound alike using DIFFERNCE()
SELECT [col_str], [col_str_value], [SearchValue], CASE WHEN DIFFERENCE([col_str_value], [SearchValue]) = 4 THEN CHARINDEX([col_str_value], [col_str]) ELSE 0 END AS [Match] FROM ##tbl_col_str_SearchString
--Then we can pivot on it
--and we will need to make it dynamic since we are not sure what what #SearchString could be.
DECLARE #PivotSQL NVARCHAR(MAX);
DECLARE #pivotColumn NVARCHAR(MAX);
SET #pivotColumn = N'[' + REPLACE(#SearchString, ' ', '],[') + N']';
SET #PivotSQL = N'SELECT * FROM (
SELECT [col_str], [SearchValue], CASE WHEN DIFFERENCE([col_str_value], [SearchValue]) = 4 THEN CHARINDEX([col_str_value], [col_str]) ELSE 0 END AS [Match] FROM ##tbl_col_str_SearchString
) aa
PIVOT (MAX([Match]) FOR [SearchValue] IN (' + #pivotColumn
+ N')) AS MaxMatch
ORDER BY [MaxMatch].[col_str]
';
--Giving us the final results.
EXEC sp_executesql #PivotSQL

How to avoid duplicate values in joining two or three tables?

I have this two tables and I want to join their two ID's.
Household Info
1
2
3
Household Members
1
1
1
2
3
3
3
3
3
The values is repeating over and over again, as you have noticed on my screenshot. The output I want is, I want a query of this:
Household Info.HID Household Members.HID
1 1
1
1
2 2
3 3
3
3
3
3
Since in the Table Household Info there are only 3 HID while the table Household Members there are three:1, one:2, and five:3
Hope you can help me on this one :3
EDITED: I am using Microsoft Access as RDBMS
For an RDBMS which supports CTE...
DECLARE #Household TABLE
( Household VARCHAR(10))
;
INSERT INTO #Household
( Household )
VALUES
(1),
(2),
(3)
;
declare #HouseholdMembers TABLE
( HouseholdMembers VARCHAR(10))
;
INSERT INTO #HouseholdMembers
( HouseholdMembers )
VALUES
(1),
(1),
(1),
(2),
(3),
(3),
(3),
(3),
(3)
;
Select
CASE WHEN RN = 1 THEN Household ELSE '' END Household,
HouseholdMembers
from (
select h.Household,
hm.HouseholdMembers,
ROW_NUMBER()OVER(PARTITION BY hm.HouseholdMembers ORDER BY h.Household)RN from #Household h
LEFT JOIN #HouseholdMembers hm
ON hm.HouseholdMembers = h.Household)T
You didn't mention what are you using as RDBMS.
I think that you can use pivot for your case:
http://www.codeproject.com/Tips/500811/Simple-Way-To-Use-Pivot-In-SQL-Query
or to use grouping:
select c2, c3
, sum( case when no_of_days <= 7 then 1 else 0 end) as dlt8
, sum( case when no_of_days between 8 and 14 then 1 else 0 end) as d8to14
, sum( case when no_of_days between 15 and 21 then 1 else 0 end) as d15to21
, sum( case when no_of_days between 22 and 27 then 1 else 0 end) as d22to27
from mytable
group by c2, c3
order by c2, c3;
Here you can find similar answer to your question:
Dynamic alternative to pivot with CASE and GROUP BY
Edit 1
If you need something like this:
SubjectID StudentName
---------- -------------
1 Mary
1 John
1 Sam
2 Alaina
2 Edward
Result I expected was:
SubjectID StudentName
---------- -------------
1 Mary, John, Sam
2 Alaina, Edward
you can check this example:
Concatenate many rows into a single text string?
Edit 2
And the last option that I can remember is this one. It's for MySQL but you can reuse the logic:
MySQL JOIN - Return NULL for duplicate results in left table

How can I unpivot and then pivot my table so the columns become rows and one column becomes a row?

How can I accomplish this with unpivot and pivot.
I've seen this question asked before and has a solution with case statement and union all
In SQL, how can I count the number of values in a column and then pivot it so the column becomes the row?
and here PIVOT/UNPIVOT multiple rows and columns but I have 20 rows and 24 columns and the query would become very long and I suspect inefficient. Does anyone know how I can do this with unpivot and pivot or is case and union all the only viable option?
Hour A B C D E ... Z
-----------------------------------------
0 4 2 3 0 6 2
1 3 5 7 1 8 7
2 2 6 1 1 4 3
3 2 2 0 3 0 2
4 3 9 6 2 2 8
...
23 6 5 2 3 8 6
Field 0 1 2 3 ...23
-------- -- -- -
A 2 0 2 2 4
B 7 2 8 1 6
....
Z 6 7 7 3 8
This is what I've tried in terms of pivot but I didn't get far:
select B,[0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23] from CTE2
pivot(
sum(A)
for hour in ([0],[1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20],[21],[22],[23])) as pvt;
Just to clarify, the numbers in the table are just random numbers I've put to simulate data, they aren't transposed as they should be.
Well, I know you say you've solved it so this probably isn't necessary and you can feel free to use whatever answer you currently have, but here's an example of how you could approach this problem in general.
IF OBJECT_ID('tmpTable', 'U') IS NOT NULL DROP TABLE tmpTable;
CREATE TABLE tmpTable (i INT, a INT, b INT, c INT, d INT);
INSERT tmpTable VALUES (1,69,69,10,1)
, (2,5,0,2,3)
, (3,5,5,5,5)
, (4,1,2,3,4);
DECLARE #applycols NVARCHAR(MAX);
SELECT #applycols = STUFF(
(SELECT ',(' + QUOTENAME(COLUMN_NAME) + ', ''' + COLUMN_NAME + ''')'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'tmpTable'
AND COLUMN_NAME <> 'i'
FOR XML PATH ('')),1,1,'');
DECLARE #aggcols NVARCHAR(MAX) = '';
SELECT #aggcols += ', MAX(CASE WHEN i = ' + CAST(i AS NVARCHAR(255)) + ' THEN piv.colval END) ' + QUOTENAME(CAST(i AS NVARCHAR(255)))
FROM tmpTable;
DECLARE #SQL NVARCHAR(MAX) = 'SELECT piv.col' + #aggcols + '
FROM tmpTable
CROSS APPLY (VALUES ' + #applycols + ') piv(colval, col)
GROUP BY piv.col;';
EXEC(#SQL);
DROP TABLE tmpTable;
Essentially, it's using dynamic SQL to determine all the columns/values and then using a simple CROSS APPLY / MAX(CASE... to get all the values.

count the number of spaces in values in sql server [duplicate]

This question already has answers here:
How do you count the number of occurrences of a certain substring in a SQL varchar?
(23 answers)
Closed 8 years ago.
I need the number of spaces in column values in sql server.
Ex:
column1
------------
aaa bbbb - 1 space
aaa bbb ccc - 2 space
aaa bbb ccc ddd - 3 space
I need the count of spaces like this.
thanks.
SELECT LEN(column1)-LEN(REPLACE(column1, ' ', '')) FROM YourTableName
This will give a different and more accurate result than the other answers, it is also counting spaces in the end of the words, it becomes clear when tested on these examples:
DECLARE #a table(column1 varchar(20))
INSERT #a values('b c ')
INSERT #a values('b c')
INSERT #a values(' b c ')
SELECT
LEN(column1 + ';')-LEN(REPLACE(column1,' ','')) - 1 accurate,
LEN(column1)-LEN(REPLACE(column1,' ', '')) [inaccurate] -- other answers
FROM #a
Result:
accurate inaccurate
2 1
1 1
10 4
Try this one -
DECLARE #t TABLE (txt VARCHAR(50))
INSERT INTO #t (txt)
VALUES
('aaa bbbb')
, ('aaa bbb ccc')
, ('aaa bbb ccc ddd')
SELECT txt, LEN(txt) - LEN(REPLACE(txt, ' ', ''))
FROM #t
this is a code for that
select len('aaa bbb') - len(replace('aaa bbb ccc', ' ', '')) from
**tablename**
output
1
select len('aaa bbb ccc') - len(replace('aaa bbb ccc', ' ', '')) from
**tablename**
ouput
2
Tablename acan be anything table that can be in your database

Resources