Is it possible to convert while selecting in SQL? - sql

I have a simple SQL query but the input parameter is a string of multiple values. I'm trying to get this to work but maybe my syntax is off or it's not possible like this?
SELECT *
FROM Table
WHERE CatID IN
(SELECT CONVERT(TINYINT,value) FROM STRING_SPLIT(#Cat,'+'))
where #Cat = '13+14+15' and CatID is of type tinyint. I've also tried using CONVERT(TINYINT,*) without luck.
Previously I was using the following code but was hoping to switch it around because of other complications.
SELECT *
FROM Table
WHERE CatID IN
(CONVERT(NVARCHAR, CatID) IN (SELECT * FROM STRING_SPLIT(#Cat,'+'))
If there's another way to do this I'm open to suggestions, maybe someway to split directly into integers? Thanks!

It would be nice if you could specify what to call the argument for string_split() using an alias in the FROM clause:
SELECT t.*
FROM Table t
WHERE t.CatID IN (SELECT CONVERT(TINYINT, val)
FROM STRING_SPLIT(#Cat, '+') ss(val)
);
But the grammar doesn't seem to allow that. Your subquery solution seems like the better solution, although I would wrap that in a CTE.
You can split directly into integers, using a recursive CTE:
with cte as (
select convert(tinyint, left(#cat + '+', charindex('+', #cat) - 1)) as val,
substring(#cat, charindex('+', #cat + '+') + 1, len(#cat)) as rest
union all
select convert(tinyint, left(rest + '+', charindex('+', rest) - 1)),
substring(rest, charindex('+', rest + '+') + 1, len(rest))
from cte
)
select t.*
from table t
where t.catid in (select val from cte);
Well, I'm not sure if this is "direct", but it doesn't require a UDF.

Related

SQL - Remove Duplicate value between two columns

I'm looking a simple way to remove an unwanted Duplicate value.
The Dupe is part of a reference to another column, and not within the column itself, but the column I want to remove the dupe value from is multi-delimited with other values.
Here is an example table:
ID,Thing
Dog,Cat;Dog;Bird
Snake,Horse;Fish;Snake
Car,Car;Bus;Bike
As you can see Dog,Snake,Car are the values I need to remove from the Thing column.
Output:
ID,Thing
Dog,Cat;Bird
Snake,Horse;Fish
Car,Bus;Bike
Is there a way to match within a multidelimited field and pull out the exact match?
I'm using SQL Server MGMT studio. Thanks.
WITH CTE AS
(
SELECT ID, Thing, ROW_NUMBER() OVER (PARTITION BY Thing) AS rn
)
DELETE
FROM CTE
WHERE rn > 1
I believe this will do it. Test first by running just the CTE part of the query so you can see what rn is.
Your question and sample data is not very clear. I think what you want is to remove anything from the second column that is in the first column, in which case you can try using replace
select Id,
replace(replace(thing,id,''),';;',';')
from table
Storing multi-value elements in a column is never a good idea and is a conflict of interest with the relational data model; it pretty much always causes problems at some point.
What you can do is concatenate a leading and a trailing ; to the value of Thing and then replace the value of ID with an empty string.
Then remove the leading and trailing ;.
If your version of SQL Server is 2017+, you can use the function TRIM():
SELECT Id,
TRIM(';' FROM REPLACE(';' + Thing + ';', ';' + ID + ';', ';')) Thing
from tablename;
For previous versions use SUBSTRING():
SELECT Id,
SUBSTRING(
REPLACE(';' + Thing + ';', ';' + ID + ';', ';'),
2,
LEN(REPLACE(';' + Thing + ';', ';' + ID + ';', ';')) - 2
) Thing
from tablename;
If you want to update the table:
UPDATE tablename
SET Thing = TRIM(';' FROM REPLACE(';' + Thing + ';', ';' + ID + ';', ';'));
or:
UPDATE tablename
SET Thing = SUBSTRING(
REPLACE(';' + Thing + ';', ';' + ID + ';', ';'),
2,
LEN(REPLACE(';' + Thing + ';', ';' + ID + ';', ';')) - 2
);
See the demo.
I don't really understand what "multi-delimited" means with respect to a string. In your context it seems to suggest that you might have different types of delimiters. It definitely does mean that you have a really poor data model. If you want to remove the id from the things column, then my first suggestion is to fix the delimiters.
In SQL Server, you could use:
select t.*,
(select string_agg(s.value, ';')
from string_split(replace(t.things, ',', ';'), ';') s
where s.value <> t.id
) as new_things
from t;
If the delimited have some intrinsic meaning (did I mentioned that you should fix the data model?), then you can use a more brute force approach. Here is one method:
select t.*,
(case when things = id then ''
when things like concat(id, '[,;]%')
then stuff(things, 1, len(id) + 1, '')
when things like concat('%[,;]', id)
then left(things, len(things) - len(id) - 1)
when things like concat('%[,;]', id, '[,;]%')
then stuff(things, patindex(concat('%[,;]', id, '[,;]%'), things), len(id) + 1, '')
else things
end)
from t;
Here is a db<>fiddle.
Your Question is a good one. I used simple case statement to get the answer. It is CHARINDEX that helped to find the location of the value in Id column and then identified the position of the value in id and according to the position, string was replaced by required values.
--Preparing the table
SELECT *
INTO t
FROM (VALUES
('Dog', 'Cat;Dog;Bird'),
('Snake', 'Horse;Fish;Snake'),
('Car', 'Car;Bus;Bike')
) v(id, things)
--Query
SELECT id
,CASE WHEN CHARINDEX(reverse(id), reverse(things), 1) = 1 THEN REPLACE(things,';'+id ,'')
WHEN CHARINDEX(id, things, 1) < LEN(things) AND CHARINDEX(id, things, 1) > 1 THEN REPLACE(things, id +';' ,'')
WHEN CHARINDEX(id, things, 1) = 1 THEN REPLACE(things, id +';' ,'')
ELSE 'End'
END as [things]
FROM t

Get the Capital letters from a string

I have this requirement to extract the capital letters from a column in SQL Server.
EX: ABC_DEF_ghi
I only want to extract ABC_DEF.
Sometimes the string could be like ABC_DEF_GHI_jkl, so in this case it will be ABC_DEF_GHI
Any suggestions would be helpful.
Thanks in advance.
As Tim Biegeleisen mentioned, this isn't easy in SQL Server, as it doesn't support regular expressions. As such you have to be some what inventive.
As we don't know what version of SQL Server you are using (though I did ask) I am assuming you are using the latest version of SQL Server, and have access to both STRING_AGG and TRIM. If not, you'll need to use the old FOR XML PATH and STUFF method for string aggregation, and LTRIM and RTRIM with nested REPLACEs for TRIM.
Anyway, what I do here is collate the value to a binary collation that is both case sensitive and also orders the letters in Uppercase and then Lowercase (though a collation that does Lowercase and then Uppercase would be fine too, it's just important it's not alphabetically and then case). So in an order like ABC...Zabc...z rather than like AaBb...Zz. I then use a Tally to split the collated string into it's individual characters.
I then use STRING_AGG with a CASE expression to only retain the Underscore characters (which you appear to want as well) and just the uppercase letters. Finally I use TRIM to remove any leading and trailing underscores; without this the value returned would be 'ABC_DEF_GHI_'.
I also assume you are doing this against a table, rather than a scalar value, which gives this:
DECLARE #SomeString varchar(100) = 'ABC_DEF_GHI_jkl';
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT MAX(LEN(V.SomeString)) FROM (VALUES(#SomeString))V(SomeString)) --This would be your table
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2) --100 rows, add more cross joins for more rows
SELECT TRIM('_' FROM STRING_AGG(CASE WHEN SS.C LIKE '[A-Z_]' THEN SS.C END,'') WITHIN GROUP (ORDER BY T.I)) AS NewString
FROM (VALUES(#SomeString))V(SomeString) --This would be your table
CROSS APPLY (VALUES(V.SomeString COLLATE Latin1_General_BIN))C(SomeString) --Collate to a collation that is both case sensitive and orders Uppercase first
JOIN Tally T ON LEN(C.SomeString) >= T.I
CROSS APPLY (VALUES(SUBSTRING(C.SomeString,T.I,1)))SS(C) --Get each character
GROUP BY V.SomeString;
db<>fiddle
Of course, a "simpler" solution might be to find and implement a Regex CLR function and just use that. 🙃
Turns out the OP is using 2014... This means the above needs some significant refactorying. I am afraid I don't explain how FOR XML PATH or REPLACE work here (as I put the effort into the original solution), however, a search will yield you the details.:
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT TOP (SELECT MAX(LEN(V.SomeString)) FROM (VALUES(#SomeString))V(SomeString)) --This would be your table
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS I
FROM N N1, N N2) --100 rows, add more cross joins for more rows
SELECT REPLACE(LTRIM(RTRIM(REPLACE((SELECT CASE WHEN SS.C LIKE '[A-Z_]' THEN SS.C END
FROM (VALUES(V.SomeString COLLATE Latin1_General_BIN))C(SomeString) --Collate to a collation that is both case sensitive and orders Uppercase first
JOIN Tally T ON LEN(C.SomeString) >= T.I
CROSS APPLY (VALUES(SUBSTRING(C.SomeString,T.I,1)))SS(C) --Get each character
ORDER BY T.I
FOR XML PATH(''),TYPE).value('(./text())[1]','varchar(100)'),'_',' '))),' ','_') AS NewString
FROM (VALUES(#SomeString))V(SomeString) --This would be your table
GROUP BY V.SomeString;
For SQL 2017 and upper :
DECLARE #SomeString varchar(100) = 'ABC_DEF_GHI_jkl';
WITH T0 AS
(
SELECT 1 AS INDICE,
SUBSTRING(#SomeString, 1, 1) AS RAW_LETTER,
SUBSTRING(UPPER(#SomeString), 1, 1) AS UP_LETTER
UNION ALL
SELECT INDICE + 1,
SUBSTRING(#SomeString, INDICE + 1, 1) AS RAW_LETTER,
SUBSTRING(UPPER(#SomeString), INDICE + 1, 1)
FROM T0
WHERE INDICE < LEN(#SomeString)
)
SELECT STRING_AGG(RAW_LETTER, '') WITHIN GROUP (ORDER BY INDICE)
FROM T0
WHERE RAW_LETTER COLLATE Latin1_General_BIN = UP_LETTER;
For SQL Server previous than 2017 :
WITH T0 AS
(
SELECT 1 AS INDICE,
SUBSTRING(#SomeString, 1, 1) AS RAW_LETTER,
SUBSTRING(UPPER(#SomeString), 1, 1) AS UP_LETTER
UNION ALL
SELECT INDICE + 1,
SUBSTRING(#SomeString, INDICE + 1, 1) AS RAW_LETTER,
SUBSTRING(UPPER(#SomeString), INDICE + 1, 1)
FROM T0
WHERE INDICE < LEN(#SomeString)
)
SELECT STUFF((SELECT '' + RAW_LETTER
FROM T0
WHERE RAW_LETTER COLLATE Latin1_General_BIN = UP_LETTER
ORDER BY INDICE
FOR XML PATH('')), 1, 0, '');

How do I select a substring from two different patindex?

I have many different types of string, but they all follow the two same patterns:
ABC123-S-XYZ789
ABC123-P-XYZ789
QUESTION 1:
I know how I can extract the first part: ABC123
But how do I extract the second part??? XYZ789
QUESTION 2:
I can't tell beforehand if the string follows the -S- pattern or the -P- pattern, it can be different each time. Anyone who know how I can solve this?
Thanks! / Sophie
You can try following code:
SELECT CASE WHEN #a LIKE '%-S-%' THEN right(#a, CHARINDEX('-S-', #a)-1)
WHEN #a LIKE '%-P-%' THEN right(#a, CHARINDEX('-P-', #a)-1)
ELSE NULL END AS 'ColName'
FROM tablename
Is this what you need?
DECLARE #Input VARCHAR(100) = 'ABC123-S-XYZ789'
SELECT
FirstPart = SUBSTRING(
#Input,
1,
CHARINDEX('-', #Input) - 1),
SecondPart = SUBSTRING(
#Input,
LEN(#Input) - CHARINDEX('-', REVERSE(#Input)) + 2,
100),
Pattern = CASE
WHEN #Input LIKE '%-S-%' THEN 'S'
WHEN #Input LIKE '%-P-%' THEN 'P' END
You can use parsename() if the string has always this kind of parts such as ABC123-S-XYZ789
select col, parsename(replace(col, '-', '.'), 1)
However, the parsename() requires the SQL Server+12 if not then you can use reverse()
select col, reverse(left(reverse(col), charindex('-', reverse(col))-1))
If you're using SQL Server 2016 or newer, you can use STRING_SPLIT
CREATE TABLE #temp (string VARCHAR(100));
INSERT #temp VALUES ('ABC123-S-XYZ789'),('ABC123-P-XYZ789');
SELECT *, ROW_NUMBER() OVER (PARTITION BY string ORDER BY string)
FROM #temp t
CROSS APPLY STRING_SPLIT(t.string, '-');
I can't tell beforehand if the string folllows the -S- pattern or the -P- pattern
You can then use a CTE to get a specific part of the string:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY string ORDER BY string) rn
FROM #temp t
CROSS APPLY STRING_SPLIT(t.string, '-')
)
SELECT * FROM cte WHERE rn = 2

Select text from between two characters in string

I have data in database an example of data below
folder/subfolder/file/doc
folder/subfolder/doc
how do I get the 1st instance of characters from between the '/'
I want to extract 'folder/subfolder'
I have tried the following but not what I need. this gets 'folder/'
LEFT([Cat], CHARINDEX('/', [Cat]) ) as 'doc_cat',
and the below gets the last part
RIGHT([Cat], CHARINDEX('/', [Cat]) ) as 'doc_cat2',
I want to get the 1st part of and second part of string
Here is one method:
select left(doc_cat_1, charindex('/', doc_cat_1) - 1)
from t cross apply
(select stuff(cat, 1, charindex('/', cat), '') as doc_cat_1
) v1;
The string handling capabilities of SQL Server are pretty lousy. Apply at least makes it easier to handle intermediate results.
You can use LEFT and CHARINDEX
LEFT([Cat],charindex('/',[Cat],charindex('/',[Cat])+1)-1) AS 'doc_cat'
One more way to accomplish using XML -
declare #s table(patterns nvarchar(100))
insert into #s
values ('folder/subfolder/file/doc'), ('folder/subfolder/doc'),('folder/subfolder')
select cast(concat('<x>', REPLACE(patterns, '/', '</x><x>'), '</x>') as xml).value('/x[1]','varchar(100)') + '/'
+ cast(concat('<x>', REPLACE(patterns, '/', '</x><x>'), '</x>') as xml).value('/x[2]','varchar(100)')
from #s
If you're on SQL 2016 or newer, you could use STRING_SPLIT()
WITH cte AS (
SELECT cat, value, ROW_NUMBER() OVER (PARTITION BY cat ORDER BY cat) rn
FROM someTable CROSS APPLY
STRING_SPLIT(cat,'/')
)
SELECT cat, value FROM cte WHERE rn = 2;
The advantage here is that rn could be any number you need.
Fiddle here.

Extracting pipe delimted field into rows

I have a tbl with a field with values that are pipe delimited, and I need them extracted as rows.
Sample data
select distinct [PROV_KEY],
[NTWK_CDS]
FROM [SPOCK].[US\AC39169].[WellPointExtract_ERR]
where [PROV_KEY] = '447358B0A8E1C0F1B7AEB1ED07EC2F25'
--results
PROV_KEY NTWK_CDS
447358B0A8E1C0F1B7AEB1ED07EC2F25 |GA_HMO|GA_OPN|GA_PPO|GA_BD|GA_MCPPO|GA_HDPPO|
And I would like:
PROV_KEY NTWK_CDS
447358B0A8E1C0F1B7AEB1ED07EC2F25 GA_HMO
447358B0A8E1C0F1B7AEB1ED07EC2F25 GA_OPN
447358B0A8E1C0F1B7AEB1ED07EC2F25 GA_PPO
I tried the following but I'm only getting the first set of values:
select distinct [PROV_KEY],
substring([NTWK_CDS], 1,
CHARINDEX('|',[NTWK_CDS], CHARINDEX('|',[NTWK_CDS])+1))
FROM [SPOCK].[US\AC39169].[WellPointExtract_ERR]
where [PROV_KEY] = '447358B0A8E1C0F1B7AEB1ED07EC2F25'
This is a standard string splitting problem and there are many solutions out there. However most still feel like a workaround, as SQL Server does not have a split function build in.
You can start your research here: http://www.sommarskog.se/arrays-in-sql.html
The crucial operation you need to perform is a split. There are lots of solutions to this problem (see here for some), and people favor different ones depending on both situation and personal preference. Once you've done the split, though, you can JOIN or APPLY against the results to get the desired output.
I personally prefer using a SQLCLR function for this purpose since the performance is generally much better; but the number of options out there is staggering.
You can use splitting function
CREATE FUNCTION dbo.SplitStrings_CTE(#List nvarchar (1000), #Delimiter nvarchar(1 ))
RETURNS #returns TABLE(val nvarchar(100), [level] int, PRIMARY KEY CLUSTERED([level]))
AS
BEGIN
;WITH cte AS
(
SELECT SUBSTRING(#List, 0, CHARINDEX(#Delimiter , #List)) AS val ,
CAST(STUFF(#List + #Delimiter, 1, CHARINDEX(#Delimiter, #List),'') AS nvarchar (1000)) AS stval,
1 AS [level]
UNION ALL
SELECT SUBSTRING(stval, 0, CHARINDEX(#Delimiter, stval)),
CAST(STUFF(stval, 1 , CHARINDEX(#Delimiter ,stval), '') AS nvarchar(1000)),
[level] + 1
FROM cte
WHERE stval != ''
)
INSERT #returns
SELECT REPLACE(val ,' ' ,'') AS val, [level]
FROM cte
RETURN
END
Hence, your SELECT statement will be
SELECT *
FROM dbo.test82 t CROSS APPLY dbo.SplitStrings_CTE(t.NTWK_CDS, '|') o
WHERE o.val != ''
Demo on SQLFiddle