How to remove spaces between comma or numbers in T-SQL? - sql-server

SELECT REPLACE('10,6 7 7,900 11,027,900', ' ', '')
SELECT REPLACE('10,2 27,900 10,6 7 7,900 11,027,900', ' ', '')
Bad Result:
10,677,90011,027,900
10,227,90010,677,90011,027,900
Good Result:
10,677,900 11,027,900
10,227,900 10,677,900 11,027,900

This is an odd requirement. Before this goes downhill, I suggest you normalize your table properly. Anyway, if you're stuck with what you have for now, here is a way to solve your problem.
First, you need a string splitter, to split strings by comma. I use DelimitedSplit8K, written by Jeff Moden and improved by the members of SQL Server Central community.
After splitting the string, check if the value of each item after the space is removed has a length of 3. If yes, concatenate the new string (space removed). Else, concatenate the original item.
WITH Tbl(OriginalString) AS(
SELECT '10,6 7 7,900 11,027,900' UNION ALL
SELECT '10,2 27,900 10,6 7 7,900 11,027,900'
),
TblSplitted(originalString, ItemNumber, Item) AS (
SELECT *
FROM Tbl t
CROSS APPLY dbo.DelimitedSplit8K(t.OriginalString, ',')
)
SELECT *
FROM Tbl t
CROSS APPLY(
SELECT STUFF((
SELECT ',' +
CASE
WHEN LEN(REPLACE(s.Item, ' ', '')) = 3 THEN REPLACE(s.Item, ' ', '')
ELSE s.Item
END
FROM TblSplitted s
WHERE s.originalString = t.OriginalString
ORDER BY s.ItemNumber
FOR XML PATH('')
), 1, 1, '')
) x(NewString);

Related

Combine string_split column results in table SQL

I'm trying to create a stored procedure for updating a table in a batch. I want to take parameters in as a nvarchar and call string_split on them.
#ParamList1 NVARCHAR(max) = '1,2,3,4,5'
#ParamList2 NVARCHAR(max) = 'a,b,c,d,e'
I want to get a temporary table like
Param1 Param2
1 a
2 b
3 c
...
How would I do this?
Unfortunately, string_split() does not guarantee ordering or provide a position argument (Microsoft are you listening?).
So, the safest method is a recursive CTE (or perhaps another approach using XML):
with cte as (
select convert(nvarchar(max), NULL) as x1, convert(nvarchar(max), NULL) as x2, #paramlist1 as rest1, #paramlist2 as rest2, 1 as lev
union all
select convert(nvarchar(max), left(rest1, charindex(',', rest1 + ',') - 1)),
convert(nvarchar(max), left(rest2, charindex(',', rest2 + ',') - 1)),
stuff(rest1, 1, charindex(',', rest1 + ','), ''),
stuff(rest2, 1, charindex(',', rest2 + ','), ''),
lev + 1
from cte
where rest1 <> '' and rest2 <> ''
)
select *
from cte
where x1 is not null;
Here is a db<>fiddle.
You've got an answer already, which is working fine, but this should be faster and easier:
You did not specify your SQL-Server's version, but - talking about STRING_SPLIT() - I assume it's at least v2016. If this is correct, you can use OPENJSON. Your list of numbers needs nothing more than brackets to be a JSON-array ([1,2,3]), while an array of words/letters can be transformed with some easy string operations (["a","b","c"]).
Following the docs, OPENJSON returns the elements position in [key], while the element itself is returned in [value]. You can simply JOIN these sets:
DECLARE #ParamList1 NVARCHAR(max) = '1,2,3,4,5';
DECLARE #ParamList2 NVARCHAR(max) = 'a,b,c,d,e';
SELECT p1.[key] AS FragmentNr
,p1.[value] AS P1
,p2.[value] AS P2
FROM OPENJSON(CONCAT('[',#ParamList1 + ']')) p1
INNER JOIN OPENJSON(CONCAT('["',REPLACE(#ParamList2,',','","'),'"]')) p2 ON p1.[key]=p2.[key] ;
In this answer you will find some details (UPDATE section 1 and 2).

T-SQL: How to replace spaces in a string except if they are after a specific character

The situation is as follows:
We have action logs in our database triggered by user events, that saves the events in varchar but in xml format. In some cases the name of the attributes contains spaces like this one:
<UNITDETAILUPDATE NEWUNIT TYPE="DUW 30 01" OLDFAULT_CIRC="HWS" NEWFAULT_CIRC="HWS" OLDOUTGOING R-STATE="R3C" />
I would like to eliminate the spaces from the names of the attributes before parsing to xml(because this way it is not possible of course :))
As you can see there are multiple occurences in the string. A great solution would be something like only replacing the spaces where there is no " character before them, but I have no idea how to achieve this.
Any ideas?
Thank you :)
For a high-performing set-based solution you can grab a copy of ngrams8k and do this:
DECLARE #string varchar(1000) = '<UNITDETAILUPDATE NEWUNIT TYPE="DUW 30 01" OLDFAULT_CIRC="HWS" NEWFAULT_CIRC="HWS" OLDOUTGOING R-STATE="R3C" />';
select newString =
(
select
case when token = ' ' and position > space1 and isQuoted = 0 and p.c <> '"'
then '' else token end
from
(
select ng.*, sum(case when token = '"' then 1 else 0 end) over (order by position)%2
from dbo.ngrams8k(#string, 1) ng
) x(position, token, isQuoted)
cross join (values (charindex(' ', #string))) v(space1)
cross apply (values (substring(#string, position-1,1))) p(c)
order by position
for xml path(''), type
).value('(text())[1]', 'varchar(8000)');
Results
<UNITDETAILUPDATE NEWUNITTYPE="DUW 30 01" OLDFAULT_CIRC="HWS" NEWFAULT_CIRC="HWS" OLDOUTGOINGR-STATE="R3C" />
If you have a SQL Server 2017 you can use string_agg like with ngrams8k like this:
select newString = string_agg(
case when token = ' ' and position > space1 and isQuoted = 0
and substring(#string, position-1,1) <> '"' then '' else token end,'')
from
(
select ng.*, sum(case when token = '"' then 1 else 0 end) over (order by position)%2
from dbo.ngrams8k(#string, 1) ng
) x(position, token, isQuoted)
cross join (values (charindex(' ', #string))) v(space1)
cross apply (values (substring(#string, position-1,1))) p(c);
You could search for good spaces and save them with a placeholder
Declare #var varchar(100) = '<UNITDETAILUPDATE NEWUNIT TYPE="DUW 30 01" OLDFAULT_CIRC="HWS" NEWFAULT_CIRC="HWS" OLDOUTGOING R-STATE="R3C" />'
Select #var = replace(#var,'" ','"|")
Then remove the spaces
Select #var = replace(#var,' ','_')
Then put the good spaces back
Select #var = replace(replace(#var,'|',' '),'UNITDETAILUPDATE_','UNITDETAILUPDATE ')
This could be combined into one ugly replace so that it could be selected across a table. You would probably need to placehold the spaces inside the quotations. Regex is not supported in SQL but sometimes it could be used with 'like'
This "Xml" is awfully bad...
The following approach won't be fast. If you need this more often, you might use another language or tool.
This solutions uses a recursive CTE, which is a hidden RBAR, to build ab the string again, charachter by character, checking for "within quotes":
DECLARE #BadXml NVARCHAR(MAX)='<UNITDETAILUPDATE NEWUNIT TYPE="DUW 30 01" OLDFAULT_CIRC="HWS" NEWFAULT_CIRC="HWS" OLDOUTGOING R-STATE="R3C" />';
WITH recCTE
AS
(
SELECT LTRIM(RTRIM(REPLACE(#BadXml,'" ','"$'))) AS TheString
,1 AS CurrentPos
,CAST('<' AS NVARCHAR(MAX)) AS BuildNew
,-1 AS IsFirstBlank
,-1 AS QuotOpen
UNION ALL
SELECT r.TheString
,r.CurrentPos+1
,r.BuildNew + CASE WHEN chr=' ' AND r.IsFirstBlank=1 AND r.QuotOpen=-1 THEN '_' ELSE chr END
,CASE WHEN r.IsFirstBlank=-1 AND chr=' ' THEN 1 ELSE r.IsFirstBlank END
,CASE WHEN chr='"' THEN r.QuotOpen * (-1) ELSE r.QuotOpen END
FROM recCTE AS r
CROSS APPLY(SELECT SUBSTRING(r.TheString,r.CurrentPos+1,1)) AS A(chr)
WHERE r.CurrentPos<LEN(r.TheString)
)
SELECT TOP 1 IsFirstBlank,QuotOpen, CAST(REPLACE(BuildNew,'"$','" ') AS XML) AS TheXml
FROM recCTE
ORDER BY LEN(BuildNew) DESC
OPTION (MAXRECURSION 1000)
The result
IsFirstBlank QuotOpen TheXml
1 -1 <UNITDETAILUPDATE NEWUNIT_TYPE="DUW 30 01" OLDFAULT_CIRC="HWS" NEWFAULT_CIRC="HWS" OLDOUTGOING_R-STATE="R3C" />
Take away the CAST to xml, the TOP 1 and the ORDER BY to see how it works.

SQL: How to CONCAT value

How can I return the values of MainEmail in the query below, delimited by commas and still count MDCselect?
declare #MainHospital varchar(50)='hospital 1'
select distinct mainhospital , f.Item, count(*) Count
from SurveyPicList s
cross apply splitstrings(s.MDCselect,':') as f
WHERE MainHospital = #MainHospital
GROUP BY MainHospital, f.Item
ORDER BY Count DESC
To be clear the above returns this: http://i.imgur.com/F1oPU6P.jpg
So there were 3 separate entries/people that selected "02-Eye". I want to list out their emails(MainEmail) comma delimited. Please let me know if I am unclear.
Assuming from your use of CROSS APPLY that you are using SQL Server, and that it is at least version 2005, you can use XML to do the concatenation as follows:
declare #MainHospital varchar(50)='hospital 1';
select mainhospital , f.Item, count(*) Count
,Stuff(
(select distinct ', ' + m.MainEmail
from SurveyPicList m
where m.MainHospital = #MainHospital
and ':' + m.MDCselect + ':' like '%:' + f.Item + ':%'
FOR XML PATH ('')),
1, 2, '') as Emails
from SurveyPicList s
cross apply splitstrings(s.MDCselect,':') as f
WHERE MainHospital = #MainHospital
GROUP BY MainHospital, f.Item
ORDER BY Count DESC
From the name I am assuming that splitstrings splits its first argument into items separated by its second argument. Hence I used like to check for f.Item in m.MDCselect in the WHERE condition of the subselect. Actually, what this WHERE condition is doing is collecting all the rows from another instance of the same table that match one record in the final grouped output.

SQL: Concatenate column values in a single row into a string separated by comma

Let's say I have a table like this in SQL Server:
Id City Province Country
1 Vancouver British Columbia Canada
2 New York null null
3 null Adama null
4 null null France
5 Winnepeg Manitoba null
6 null Quebec Canada
7 Seattle null USA
How can I get a query result so that the location is a concatenation of the City, Province, and Country separated by ", ", with nulls omitted. I'd like to ensure that there aren't any trailing comma, preceding commas, or empty strings. For example:
Id Location
1 Vancouver, British Columbia, Canada
2 New York
3 Adama
4 France
5 Winnepeg, Manitoba
6 Quebec, Canada
7 Seattle, USA
I think this takes care of all of the issues I spotted in other answers. No need to test the length of the output or check if the leading character is a comma, no worry about concatenating non-string types, no significant increase in complexity when other columns (e.g. Postal Code) are inevitably added...
DECLARE #x TABLE(Id INT, City VARCHAR(32), Province VARCHAR(32), Country VARCHAR(32));
INSERT #x(Id, City, Province, Country) VALUES
(1,'Vancouver','British Columbia','Canada'),
(2,'New York' , null , null ),
(3, null ,'Adama' , null ),
(4, null , null ,'France'),
(5,'Winnepeg' ,'Manitoba' , null ),
(6, null ,'Quebec' ,'Canada'),
(7,'Seattle' , null ,'USA' );
SELECT Id, Location = STUFF(
COALESCE(', ' + RTRIM(City), '')
+ COALESCE(', ' + RTRIM(Province), '')
+ COALESCE(', ' + RTRIM(Country), '')
, 1, 2, '')
FROM #x;
SQL Server 2012 added a new T-SQL function called CONCAT, but it is not useful here, since you still have to optionally include commas between discovered values, and there is no facility to do that - it just munges values together with no option for a separator. This avoids having to worry about non-string types, but doesn't allow you to handle nulls vs. non-nulls very elegantly.
select Id ,
Coalesce( City + ',' +Province + ',' + Country,
City+ ',' + Province,
Province + ',' + Country,
City+ ',' + Country,
City,
Province,
Country
) as location
from table
This is a hard problem, because the commas have to go in-between:
select id, coalesce(city+', ', '')+coalesce(province+', ', '')+coalesce(country, '')
from t
seems like it should work, but we can get an extraneous comma at the end, such as when country is NULL. So, it needs to be a bit more complicated:
select id,
(case when right(val, 2) = ', ' then left(val, len(val) - 1)
else val
end) as val
from (select id, coalesce(city+', ', '')+coalesce(province+', ', '')+coalesce(country, '') as val
from t
) t
Without a lot of intermediate logic, I think the simplest way is to add a comma to each element, and then remove any extraneous comma at the end.
Use the '+' operator.
Understand that null values don't work with the '+' operator (so for example: 'Winnepeg' + null = null), so be sure to use the ISNULL() or COALESCE() functions to replace nulls with an empty string, e.g.: ISNULL('Winnepeg','') + ISNULL(null,'').
Also, if it is even remotely possible that one of your collumns could be interpreted as a number, then be sure to use the CAST() function as well, in order to avoid error returns, e.g.: CAST('Winnepeg' as varchar(100)).
Most of the examples so far neglect one or more pieces of this. Also -- some of the examples use subqueries or do a length check, which you really ought not to do -- just not necessary -- though your optimizer might save you anyway if you do.
Good Luck
ugly but it will work for MS SQL:
select
id,
case
when right(rtrim(coalesce(city + ', ','') + coalesce(province + ', ','') + coalesce(country,'')),1)=',' then left(rtrim(coalesce(city + ', ','') + coalesce(province + ', ','') + coalesce(country,'')),LEN(rtrim(coalesce(city + ', ','') + coalesce(province + ', ','') + coalesce(country,'')))-1)
else rtrim(coalesce(city + ', ','') + coalesce(province + ', ','') + coalesce(country,''))
end
from
table
I know it's an old question, but should someone should stumble upon this today, SQL Server 2017 and later has the STRING_AGG function, with the WITHIN GROUP option :
with level1 as
(select id,city as varcharColumn,1 as columnRanking from mytable
union
select id,province,2 from mytable
union
select id,country,3 from mytable)
select STRING_AGG(varcharColumn,', ')
within group(order by columnRanking)
from level1
group by id
Should empty strings exist aside of nulls, they should be excluded with some WHERE clause in level1.
Here is an option:
SELECT (CASE WHEN City IS NULL THEN '' ELSE City + ', ' END) +
(CASE WHEN Province IS NULL THEN '' ELSE Province + ', ' END) +
(CASE WHEN Country IS NULL THEN '' ELSE Country END) AS LOCATION
FROM MYTABLE

Not able to remove first comma in string SQL Server

The code works fine except for
SELECT DISTINCT isnull(#FEATURE + ', ', ',') + Feature
FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] as rf2 where (rf2.roomid = rf.roomid)
for xml path('')),
which is giving me problems. I do not know how to get rid of the first comma. I have tried substring but it removes all the commas(I guess since it's in a select statement). I even tried
COALESCE(#FEATURE + ', ', '') + FEATURE but I end up with the same result, either no commas or one extra comma at the end or at the very beginning.
DECLARE #FEATURE VARCHAR(500)
-- INSERT INTO BookableRooms IF NOT EXISTS ROOM NUMBER
INSERT INTO dbo.BookableRooms_test (FK_Building_code,
Room_num,Other_room_name,
Capacity_from,
Capacity_to,
Accessiblity,AV_book_separate,Flooring_type,Features,
Food_drink_allowed, Alcohol_allowed,Internet,
[Owner],FK_BookingContact_ID,
LCD_projector,Computer,FK_SpaceManager_code,Last_updated_date,Last_updated_by,SpecialFlag)
SELECT DISTINCT CASE
WHEN rf.[Building Reference] is null or rf.[Building Reference] = '' THEN 'HH'
ELSE rf.[Building Reference]
END,
[ROOMID],[Description],
(SELECT MIN(rf2.CAPACITY) FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] AS rf2 WHERE rf2.ROOMID = rf.ROOMID),
(SELECT MAX(rf2.CAPACITY) FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] AS rf2 WHERE rf2.ROOMID = rf.ROOMID),
dbo.iszero([Wheelchair Accessible]), dbo.iszero(rf.[Separate AV]),dbo.isflat(rf.[Flat Floor]),
(
SELECT DISTINCT isnull(#FEATURE + ', ', ',') + Feature
FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] as rf2 where (rf2.roomid = rf.roomid)
for xml path('')),
dbo.iszero(rf.[Food and Drink]),dbo.iszero(rf.[Alcohol]),dbo.iszero(rf.[Room Internet]),
'HH',5,
dbo.iszero(rf.[Digital Projector]),dbo.iszero(rf.[Computer]),'HH',GetDate(),'System',dbo.iszero(rf.[Restricted])
FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] as rf where NOT EXISTS(
SELECT 1 FROM dbo.BookableRooms_test b WHERE
b.Room_num = rf.ROOMID);
Use Stuff but on the outer side of subquery:
stuff ((SELECT DISTINCT ',' + Feature
FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] as rf2
WHERE (rf2.roomid = rf.roomid)
for xml path('')), 1, 1, '')
Use the STUFF function to remove the leading comma.
SELECT DISTINCT STUFF(',' + Feature
FROM [128.100.194.219, 1435].[EMSData].[dbo].[vOSLRoomFeatures] as rf2 where (rf2.roomid = rf.roomid)
for xml path('')),1,1,''),
I am not sure what you are trying to achieve. If my guess was wrong, it would good if you told us what results you expect in which situation.
Try this
CASE WHEN #FEATURE IS NULL THEN NULL ELSE #FEATURE + ', ' END

Resources