Sort the Alphanumeric column in SQL - sql

I want sort the below Alphanumeric Column in SQL Server.
Column A
------------------------------
SP-SS-2
SP-FAB-8
WD-1
WD-4A
WD-11
WD-10
WD-2
WD-20
I want sorted by ascending order Output is as follows:
Column A
-----------------------
SP-FAB-8
SP-SS-2
WD-1
WD-2
WD-4A
WD-10
WD-11
WD-20
Any tricks to make it sort properly?

SELECT ColumnA FROM #TblTableA
ORDER BY
LEFT(ColumnA,PATINDEX('%[0-9]%',ColumnA)-1),
CASE
WHEN ISNUMERIC(ColumnA) = 0
THEN CAST(LEFT(RIGHT(ColumnA,LEN(ColumnA)-CHARINDEX('-',ColumnA)),
patindex('%[^0-9]%', RIGHT(ColumnA,LEN(ColumnA)-CHARINDEX('-',ColumnA))+'.') - 1) AS INT)
ELSE ColumnA
END
Output:-
ColumnA
---------
SP-FAB-8
SP-SS-2
WD-1
WD-2
WD-4A
WD-10
WD-11
WD-20
Note:- You will get this type of output using Patindex.....
For More Info...Follow this below Link
https://dba.stackexchange.com/questions/117379/sort-a-varchar-type-column-alphanumeric-values

If we want to overcome this issue, we need to separate strings and numbers and then sort them. In the first step we need a function that helps to separate numbers from the strings. Therefore, we will create this function Query to get only numbers from a string
CREATE FUNCTION dbo.udf_GetNumeric
(#strAlphaNumeric VARCHAR(256))
RETURNS VARCHAR(256)
AS
BEGIN
DECLARE #intAlpha INT
SET #intAlpha = PATINDEX('%[^0-9]%', #strAlphaNumeric)
BEGIN
WHILE #intAlpha > 0
BEGIN
SET #strAlphaNumeric = STUFF(#strAlphaNumeric, #intAlpha, 1, '' )
SET #intAlpha = PATINDEX('%[^0-9]%', #strAlphaNumeric )
END
END
RETURN ISNULL(#strAlphaNumeric,0)
END
GO
In the second step we can execute the following query that will help to sort alphanumeric characters.
CREATE TABLE #Temp
(Val VARCHAR(100))
INSERT INTO #Temp
VALUES ('SP-SS-2') ,
('SP-FAB-8'),
('WD-1'),
('WD-4A'),
('WD-11'),
('WD-10'),
('WD-2'),
('WD-20')
select val,dbo.udf_GetNumeric(val) as textpart
,LEFT(val,PATINDEX('%[0-9]%',val)-1) as stringpart
from
#Temp
order by LEFT(val,PATINDEX('%[0-9]%',val)-1) ,convert(int,dbo.udf_GetNumeric(val))
+----------+----------+------------+
| val | textpart | stringpart |
+----------+----------+------------+
| SP-FAB-8 | 8 | SP-FAB- |
| SP-SS-2 | 2 | SP-SS- |
| WD-1 | 1 | WD- |
| WD-2 | 2 | WD- |
| WD-4A | 4 | WD- |
| WD-10 | 10 | WD- |
| WD-11 | 11 | WD- |
| WD-20 | 20 | WD- |
+----------+----------+------------+

Related

Substring output for different cases

I need output with following cases:
+-----------------+--------+
| STRING in table | OUTPUT |
+-----------------+--------+
| NONGL_NONGL | NONGL |
| GL252_GL252 | GL |
| GL400_GL400 | GL |
| NOS_NOS | NOS |
+-----------------+--------+
I tried to use SUBSTRING() but it is not giving me proper output.
So for the NONGL and NOS result you need:
SELECT LEFT([col_name], CHARINDEX('_', [col_name]) - 1)
And for the GL output, since the string is containing numbers you need PATINDEX():
SELECT LEFT([col_name], PATINDEX('%[0-9]%', [col_name]) - 1)
In the end just use a CASE WHEN
DECLARE #tbl Table (string VARCHAR(MAX) );
INSERT INTO #tbl VALUES
('NONGL_NONGL'),
('GL252_GL252'),
('GL400_GL400'),
('NOS_NOS');
SELECT
string
,CASE WHEN
string LIKE '%[0-9]%' then LEFT(string, PATINDEX('%[0-9]%', string) - 1)
ELSE LEFT(string, CHARINDEX('_', string) - 1)
END AS [Output]
from #tbl
This should do what you want:
select (case when col like 'NONGL%' then 'NONGL'
when col like 'GL%' then 'GL'
when col like 'NOS%' then 'NOS'
end) as new_col
Here is another way:
DECLARE #StrTable Table (Str VARCHAR(MAX) );
INSERT INTO #StrTable VALUES
('NONGL_NONGL'),
('GL252_GL252'),
('GL400_GL400'),
('NOS_NOS');
SELECT Str,
IIF(Str LIKE 'NONGL%','NONGL',IIF(Str LIKE 'GL%', 'GL', IIF(Str LIKE 'NOS%', 'NOS', Str))) AS Outputs
FROM #StrTable;
Result:
+-------------+---------+
| Str | Outputs |
+-------------+---------+
| NONGL_NONGL | NONGL |
| GL252_GL252 | GL |
| GL400_GL400 | GL |
| NOS_NOS | NOS |
+-------------+---------+

SQL Sort Numeric Strings After Split

I currently have char values in a table column which are in the format "IS-" and then 1 to 5 numbers, a possible period with either 2 numbers or a letter following the period.
Examples are, IS-1, IS-12, IS-123, IS-123.11, IS-123.a.
I need to split the string so that I grab only the number part, sort the strings ASC, and the bring the strings back together the way they were.
Explanation. I have the following set of values, IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a. As you can see, because IS-1 comes before IS-8 they are sorting out of numerical order.
Any idea where to begin? I was thinking of using CASE, but I'm not really sure how to proceed.
Thanks.
Do string functions in your ORDER BY to remove only the number. Something like this should work:
SELECT col
FROM table
ORDER BY CAST(CASE WHEN ISNUMERIC(SUBSTRING(col,4,20)) = 1
THEN SUBSTRING(col,4,20)
ELSE LEFT(SUBSTRING(col,4,20),CHARINDEX('.',SUBSTRING(col,4,20),0)-1)
END AS NUMERIC)
This will first remove the IS- and check if the rest of the string is a number. If it is, it will leave the decimal digits, otherwise it will remove the . and the following alpha characters.
This is assuming your intended ordering in the case of numeric decimal places would be:
IS-123.A
IS-123.1
IS-123.2
If you don't care about what's after the decimal/period, then simply:
ORDER BY CAST(LEFT(SUBSTRING(col,4,20),CHARINDEX('.',SUBSTRING(col,4,20),0)-1) AS NUMERIC)
If I understand you correctly, this might help you:
DECLARE #mockup TABLE(ID INT IDENTITY,YourExample VARCHAR(100));
INSERT INTO #mockup VALUES
('IS-1, IS-12, IS-123, IS-123.11, IS-123.a.')
,('IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a');
WITH Splitted AS
(
SELECT *
,CAST('<x>' + REPLACE(m.YourExample,',','</x><x>') + '</x>' AS XML) AS SplitAtComma
FROM #mockup AS m
)
,NumberExtracted AS
(
SELECT s.ID
,part.value('text()[1]','nvarchar(max)') AS OnePart
,CAST('<y>' + REPLACE(REPLACE(part.value('text()[1]','nvarchar(max)'),'.','-'),'-','</y><y>') + '</y>' AS XML).value('/y[2]/text()[1]','int') AS TheNumber
FROM Splitted AS s
CROSS APPLY s.SplitAtComma.nodes('/x') AS A(part)
)
SELECT *
FROM NumberExtracted
ORDER BY ID,TheNumber;
The first CTE uses a string-split via XML to get all values within the original string (btw: never store comma separated values!).
The second CTE will use the same approach to extract the number, typesafe as INT.
You can use this in an ORDER BY finally.
The result:
+----+-----------+-----------+
| ID | OnePart | TheNumber |
+----+-----------+-----------+
| 1 | IS-1 | 1 |
+----+-----------+-----------+
| 1 | IS-12 | 12 |
+----+-----------+-----------+
| 1 | IS-123 | 123 |
+----+-----------+-----------+
| 1 | IS-123.11 | 123 |
+----+-----------+-----------+
| 1 | IS-123.a. | 123 |
+----+-----------+-----------+
| 2 | IS-870.a | 870 |
+----+-----------+-----------+
| 2 | IS-871.a | 871 |
+----+-----------+-----------+
| 2 | IS-872.a | 872 |
+----+-----------+-----------+
| 2 | IS-1170 | 1170 |
+----+-----------+-----------+
| 2 | IS-1171 | 1171 |
+----+-----------+-----------+
| 2 | IS-1172 | 1172 |
+----+-----------+-----------+
| 2 | IS-1173 | 1173 |
+----+-----------+-----------+
| 2 | IS-1174 | 1174 |
+----+-----------+-----------+
IF OBJECT_ID(N'tempdb..##table1', N'U') IS NOT NULL
DROP TABLE ##table1;
create table ##table1(col1 varchar(20))
declare #query as nvarchar(max)
declare #var1 as varchar(max)='IS-1, IS-12, IS-123, IS-123.11, IS-123.a.,IS-1170, IS-1171, IS-1172, IS-1173, IS-1174, IS-870.a, IS-871.a, IS-872.a.'
set #var1=replace(#var1,',','''),(''')
set #var1='('''+#var1+''')'
set #var1=replace(#var1,' ','')
set #query='insert into ##table1 values'+#var1
EXEC sp_executesql #query
IF OBJECT_ID(N'tempdb..##table2', N'U') IS NOT NULL
DROP TABLE ##table2;
select * into ##table2 from ##table1 order by cast(replace(replace(replace(col1,'IS-',''),'.a.',''),'.a','') as float)
declare #results varchar(max)
select #results = coalesce(#results + ', ', '') + convert(varchar(12),col1) from ##table2
select #results
DROP TABLE ##table1
DROP TABLE ##table2

SQL Find String

I would like to know if finding a string can be done from another table. It's a bit complicated.
Here's the table: (tbl_dishes)
| dish | Type |
| egg, hotdog & bread | Breakfast |
From the table above, I want to get the individual descriptions of the column dish from another table
2nd Table (tbl_Foods)
| food | Description |
| egg | Fresh |
| hotdog | red |
| bread | toasted |
| steak | meat |
Let's say my query would look like this: (but it's wrong)
SELECT food, description FROM tbl_Foods
WHERE food Exists IN (SELECT dish FROM tbl_Dishes)
My desired results would be:
| food | Description |
| egg | Fresh |
| hotdog | red |
| bread | toasted |
It's like getting all matched word in the dish column. I don't know if it's possible. Please help.
Thank you.
SELECT food, description
FROM tbl_Foods
join tbl_Dishes
on tbl_Dishes.dish like ('%' + tbl_Foods.food +'%')
You will need to split the list
DECLARE #DelimString VARCHAR(100)
SET DelimString = SELECT REPLACE(REPLACE(dish,'&',','),' ', '') FROM tbl_Dishes
DECLARE #Dish TABLE (Dish VARCHAR(50)); INSERT INTO #Dish SELECT CAST(ParamValue AS VARCHAR) FROM MultiValueParams_String(#DelimString)
Use this function.
Create function [dbo].[MultiValueParams_String] (#ParamList varchar(4000))
returns #Values table (RoNum INT,ParamValue varchar(4000))
as
begin
declare #Delim char(1) = ',' -- comma is always the delimiter
declare #Chrind int = 1
declare #Piece nvarchar(50)
declare #RoNum int = 0
while #Chrind>0
begin
select #Chrind=charindex(#Delim,#ParamList)
if #Chrind>0
select #Piece=left(#ParamList,#chrind-1)
else
select #Piece=#ParamList
insert #values(RoNum,ParamValue) values (#RoNum,#Piece)
select #ParamList = right(#ParamList,len(#ParamList)-#chrind)
if len(#ParamList)=0 break
SELECT #RoNum = #RoNum + 1
end
return
end
SELECT food, description
FROM tbl_Foods f
INNER JOIN #Dish d ON f.food = d.dish
Something like this.

Query SQL Server with wildcard IN the database

I need to build a query where the criteria must match with wildcard in the database.
With an example it will be clearest.
I have a column with a field like this 963-4-AKS~M2RN21AXA150~~~0C1D1D~~XX.
The ~ char is a wildcard.
So the following criterias must match :
~63-4-AKS~M
963-4-AKS1M
963-4-AKS~M2RN21AXA150AAA
963-4-AKSAM2RN21AXA150AAA
963-4-AKSCM2RN21AXA150A060C1D1DSDXX
963-4-AKS~M2RN21AXA150~~~0C1D1D~~XX
I've tried so much things my head hurt :(
In the other way (with wildcard from the criteria) no problem, easy. But in this way I cannot find the key.
The problem is when I have a ~ in the field it doesn't match. So here only the first and last match with the following statement :
SELECT myField FROM myTable WHERE myField LIKE REPLACE('%' + myCriteria + '%', '~', '_');
It seems the patterns and the field are adjusted to the left.
If this is indeed the case, with my head bowed (full of sadness), here is a function.
create function is_a_match (#myField varchar(100),#myCriteria varchar(100))
returns bit
as
begin
declare #i int = 0
,#is_a_match bit = 1
,#len_myField int = len(#myField)
,#len_myCriteria int = len(#myCriteria)
,#myField_c char(1)
,#myCriteria_c char(1)
While 1=1
begin
set #i += 1
if #i > #len_myCriteria break
if #i > #len_myField
begin
set #is_a_match = 0
break
end
set #myField_c = substring(#myField ,#i,1)
set #myCriteria_c = substring(#myCriteria,#i,1)
if not (#myField_c = '~' or #myCriteria_c = '~' or #myField_c = #myCriteria_c)
begin
set #is_a_match = 0
break
end
end
return #is_a_match
end
GO
select myCriteria
,dbo.is_a_match (myField,myCriteria) as is_a_match
from (values ('~63-4-AKS~M' )
,('963-4-AKS1M' )
,('963-4-AKS~M2RN21AXA150AAA' )
,('963-4-AKSAM2RN21AXA150AAA' )
,('963-4-AKSCM2RN21AXA150A060C1D1DSDXX' )
,('963-4-AKS~M2RN21AXA150~~~0C1D1D~~XX' )
,('963-4-AKS~M2RN21AXA150~~~0C1X1D~~XX' )
,('963-4-AKS~M2RN21AXA150~~~0C1D1D~~XXYY')
) c (myCriteria)
,(values ('963-4-AKS~M2RN21AXA150~~~0C1D1D~~XX' )
) f (myField)
+---------------------------------------+------------+
| myCriteria | is_a_match |
+---------------------------------------+------------+
| ~63-4-AKS~M | 1 |
+---------------------------------------+------------+
| 963-4-AKS1M | 1 |
+---------------------------------------+------------+
| 963-4-AKS~M2RN21AXA150AAA | 1 |
+---------------------------------------+------------+
| 963-4-AKSAM2RN21AXA150AAA | 1 |
+---------------------------------------+------------+
| 963-4-AKSCM2RN21AXA150A060C1D1DSDXX | 1 |
+---------------------------------------+------------+
| 963-4-AKS~M2RN21AXA150~~~0C1D1D~~XX | 1 |
+---------------------------------------+------------+
| 963-4-AKS~M2RN21AXA150~~~0C1X1D~~XX | 0 |
+---------------------------------------+------------+
| 963-4-AKS~M2RN21AXA150~~~0C1D1D~~XXYY | 0 |
+---------------------------------------+------------+
You are mixing between the field and the patterns.
The field may not hold wildcards.
E.g.
This is not a match because of the 'A's
963-4-AKS~M2RN21AXA150~~~0C1D1D~~XX
963-4-AKSAM2RN21AXA150AAA
If you could tighten the constraints on the wildcard, you might have a fighting chance here. What I mean is that you generate the valid permutations if a wildcard is presented in the persisted data. Then query the permutations with your existing query.
But if every wildcard has 36 possible options, this becomes exponentially painful.

How to merge multiple rows into one row with filtering rules in SQL Server

I have a table like this:
+---------------+---------------+----------------+---------------------+
| MedicalCardId | DiagnosisType | DiagnosisOrder | Symptom |
+---------------+---------------+----------------+---------------------+
| 1 | Main | 1 | Lung Cancer |
| 1 | Secondary | 1 | High Blood Pressure |
| 1 | Secondary | 2 | Heart Attack |
| 1 | Secondary | 3 | Gastritis |
| 2 | Main | 1 | Diabetes |
| 2 | Secondary | 1 | Kidney Malfunction |
| 3 | Main | 1 | Flu |
+---------------+---------------+----------------+---------------------+
The DiagnosisOrder for each 'Main' DiagnosisType is 1, and for 'Secondary' DiagnosisType of the same MedicalCardId, it restarts to increase from 1.
I would like to merge multiple rows of the same MedicalCardId into a single row, and each Symptom becomes a new column depending on its DiagnosisType and DiagnosisOrder
The query result is expected to be like:
+---------------+-------------+---------------------+-------------------+-------------------+
| MedicalCardId | MainSymptom | SecondarySymptom1 | SecondarySymptom2 | SecondarySymptom3 |
+---------------+-------------+---------------------+-------------------+-------------------+
| 1 | Lung Cancer | High Blood Pressure | Heart Attack | Gastritis |
| 2 | Diabetes | Kidney Malfunction | | |
| 3 | Flu | | | |
+---------------+-------------+---------------------+-------------------+-------------------+
I've tried using PIVOT, but I'm unable to apply it to my practice.
You can try with conditional aggregation -
select MedicalCardId,
max(case when DiagnosisType='Main' then Symptom end) as MainSymptom,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=1 then Symptom end) as SecondarySymptom1,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=2 then Symptom end) as SecondarySymptom2,
max(case when DiagnosisType='Secondary' and DiagnosisOrder=3 then Symptom end) as SecondarySymptom3
from tablename
group by MedicalCardId
I believe you need to create a dynamic pivot table. The reason why you can’t use a normal pivot table query is because you don’t know how many Secondary Symptoms there are and therefore you don’t know how many columns to create. Below is a stored procedure that works. The first step is creating a VARCHAR (#Columns) variable that will be used to store the dynamic column names these will be [Main], [Secondary1], [Secondary2], [Secondary3] so on and so forth (I used a case statement to create the column names per your expected query result). The second step is creating another VARCHAR (#SQL) variable that will contain the pivot table SQL query. In this step you will use string concatenation to put this variable together.
Kris Wenzel has a great tutorial on dynamic pivot tables at essentialsql.com here is the link https://www.essentialsql.com/create-dynamic-pivot-table-sql-server/
Here is the stored procedure.
USE [TestDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Author,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
CREATE PROCEDURE [dbo].[GenerateData]
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
--GATHER PIVOT COLUMNS DYNAMICALLY
DECLARE #Columns as VARCHAR(MAX)
SELECT #Columns =
COALESCE(#Columns + ', ','') + QUOTENAME([Diagnosis])
FROM
(SELECT DISTINCT case when [DiagnosisOrder] = 1 and [DiagnosisType] = 'Main' then 'MainSymptom' else 'SecondarySymptom' + CAST([DiagnosisOrder] AS VARCHAR) end [Diagnosis] FROM [TestDB].[dbo].[test] ) AS B
ORDER BY B.[Diagnosis]
--CREATE SQL QUERY FOR PIVOT TABLE
DECLARE #SQL as VARCHAR(MAX)
SET #SQL = 'SELECT MedicalCardId, ' + #Columns + '
FROM
(
select [MedicalCardId]
,[Diagnosis]
,[Sympton]
from
(
SELECT [MedicalCardId]
,case when [DiagnosisOrder] = 1 and [DiagnosisType] = ''Main'' then ''MainSymptom'' else ''SecondarySymptom'' + CAST([DiagnosisOrder] AS VARCHAR) end [Diagnosis]
,[Sympton]
FROM [TestDB].[dbo].[test]
) A
) t
PIVOT(
MAX([Sympton])
FOR [Diagnosis] IN (' + #Columns + ')
) AS pivot_table order by [MedicalCardId]'
--EXECUTE SQL
EXEC(#SQL)
END
GO