Is there a method to use contain rather than equal in case statement?
For example, I am checking a database table has an entry
lactulose, Lasix (furosemide), oxazepam, propranolol, rabeprazole, sertraline,
Can I use
CASE When dbo.Table.Column = 'lactulose' Then 'BP Medication' ELSE '' END AS 'BP Medication'
This did not work.
CASE WHEN ', ' + dbo.Table.Column +',' LIKE '%, lactulose,%'
THEN 'BP Medication' ELSE '' END AS [BP Medication]
The leading ', ' and trailing ',' are added so that you can handle the match regardless of where it is in the string (first entry, last entry, or anywhere in between).
That said, why are you storing data you want to search on as a comma-separated string? This violates all kinds of forms and best practices. You should consider normalizing your schema.
In addition: don't use 'single quotes' as identifier delimiters; this syntax is deprecated. Use [square brackets] (preferred) or "double quotes" if you must. See "string literals as column aliases" here: http://msdn.microsoft.com/en-us/library/bb510662%28SQL.100%29.aspx
EDIT If you have multiple values, you can do this (you can't short-hand this with the other CASE syntax variant or by using something like IN()):
CASE
WHEN ', ' + dbo.Table.Column +',' LIKE '%, lactulose,%'
WHEN ', ' + dbo.Table.Column +',' LIKE '%, amlodipine,%'
THEN 'BP Medication' ELSE '' END AS [BP Medication]
If you have more values, it might be worthwhile to use a split function, e.g.
USE tempdb;
GO
CREATE FUNCTION dbo.SplitStrings(#List NVARCHAR(MAX))
RETURNS TABLE
AS
RETURN ( SELECT DISTINCT Item FROM
( SELECT Item = x.i.value('(./text())[1]', 'nvarchar(max)')
FROM ( SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(#List,',', '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
WHERE Item IS NOT NULL
);
GO
CREATE TABLE dbo.[Table](ID INT, [Column] VARCHAR(255));
GO
INSERT dbo.[Table] VALUES
(1,'lactulose, Lasix (furosemide), oxazepam, propranolol, rabeprazole, sertraline,'),
(2,'lactulite, Lasix (furosemide), lactulose, propranolol, rabeprazole, sertraline,'),
(3,'lactulite, Lasix (furosemide), oxazepam, propranolol, rabeprazole, sertraline,'),
(4,'lactulite, Lasix (furosemide), lactulose, amlodipine, rabeprazole, sertraline,');
SELECT t.ID
FROM dbo.[Table] AS t
INNER JOIN dbo.SplitStrings('lactulose,amlodipine') AS s
ON ', ' + t.[Column] + ',' LIKE '%, ' + s.Item + ',%'
GROUP BY t.ID;
GO
Results:
ID
----
1
2
4
Pseudo code, something like:
CASE
When CHARINDEX('lactulose', dbo.Table.Column) > 0 Then 'BP Medication'
ELSE ''
END AS 'Medication Type'
This does not care where the keyword is found in the list and avoids depending on formatting of spaces and commas.
Related
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.
I have these special characters " ||~|| " at the end of each value in column X. I need to remove these special characters.
Right now I am using this syntax, but it doesn't seem to accomplish the task for all rows.
set [Customer Num] = substring(ltrim(rtrim([Customer Num])),3,len([Customer Num]))
Try this options,
Declare #myStr varchar(50) = 'amol~'
--If want to remove char ~ of any position
Select REPLACE(#myStr,'~','')
Set #myStr = '~amol~'
Select REPLACE(#myStr,'~','')
Set #myStr = '~am~ol~'
Select REPLACE(#myStr,'~','')
--If want to remove character ~ at Last position & existance of char ~ is inconsistent
Set #myStr ='amol~'
Select Case When RIGHT(#myStr,1) = '~'
Then LEFT(#myStr,len(#myStr) - 1)
Else #myStr
End
If you are looking to replace ||~|| Then try this,
Declare #myStr varchar(50) = 'amol ||~|| '
--If want to remove string ||~| of any position
Select REPLACE(#myStr,'||~||','')
Set #myStr = '||~||amol||~||'
Select REPLACE(#myStr,'||~||','')
Set #myStr = '||~||am||~||ol||~||'
Select REPLACE(#myStr,'||~||','')
--If want to remove string ||~| at Last position & existance of char ||~| is inconsistent
Set #myStr ='amol||~||'
Select Case When RIGHT(#myStr,5) = '||~||'
Then LEFT(#myStr,len(#myStr) - 5)
Else #myStr
End
If you know for sure that your values end with the Special String, try
substring ( [Customer Num], 1, length([Customer Num]) - length(' ||~|| ') )
It's better, however, to safeguard against accidental Deletions:
substring (
[Customer Num]
, 1
, Case coalesce(substr( [Customer Num], length([Customer Num]) - length(' ||~|| '), '_' )
When ' ||~|| ' then length([Customer Num]) - length(' ||~|| ')
Else length([Customer Num])
End
)
If your rdbms Supports regular expressions, this simplifies to (using Oracle Syntax)
Regexp_replace ( [Customer Num], ' \|\|~\|\| $', '')
Assuming you have to remove last 3 characters of ColumnX
set [ColumnX] = substring(ltrim(rtrim([ColumnX])),0,len([ColumnX]) - 3)
This works
update [Table] set [Customer Num] = (substring(ltrim(rtrim([Customer Num])),0,len([Customer Num]) - 3))
where [Customer Num] like '%only text containing this string%'
I have to variables that contain comma-separated strings:
#v1 = 'hello, world, one, two'
#v2 = 'jump, down, yes, one'
I need a function that will return TRUE if there is at least one match. So in the above example, it would return TRUE since the value 'one' is in both strings.
Is this possible in SQL?
Use a split function (many examples here - CLR is going to be your best option in most cases back before SQL Server 2016 - now you should use STRING_SPLIT()).
Once you have a split function, the rest is quite easy. The model would be something like this:
DECLARE #v1 VARCHAR(MAX) = 'hello, world, one, two',
#v2 VARCHAR(MAX) = 'jump, down, yes, one';
SELECT CASE WHEN EXISTS
(
SELECT 1
FROM dbo.Split(#v1) AS a
INNER JOIN dbo.Split(#v2) AS b
ON a.Item = b.Item
)
THEN 1 ELSE 0 END;
You can even reduce this to only call the function once:
SELECT CASE WHEN EXISTS
(
SELECT 1 FROM dbo.Split(#v1)
WHERE ', ' + LTRIM(#v2) + ','
LIKE '%, ' + LTRIM(Item) + ',%'
) THEN 1 ELSE 0 END;
On 2016+:
SELECT CASE WHEN EXISTS
(
SELECT 1 FROM STRING_SPLIT(#v1, ',')
WHERE ', ' + LTRIM(#v2) + ','
LIKE '%, ' + LTRIM([Value]) + ',%'
) THEN 1 ELSE 0 END;
You can use CTEs to split your string into xml nodes, then insert the words into table variables. Joining the table variables will reveal any matches
DECLARE #v1 VARCHAR(200) = 'hello, world, one, two'
DECLARE #v2 VARCHAR(200) = 'jump, down, yes, one'
DECLARE #v1Words TABLE (word VARCHAR(100))
DECLARE #v2Words TABLE (word VARCHAR(100))
;WITH cteSplitV1 AS(
SELECT CAST('<word>' + REPLACE(#v1,', ','</word><word>') + '</word>' AS XML) AS words)
INSERT INTO #v1Words(word)
SELECT word.x.value('.','VARCHAR(100)') AS [word]
FROM cteSplitV1
CROSS APPLY words.nodes('/word') AS word(x)
;WITH cteSplitV2 AS(
SELECT CAST('<word>' + REPLACE(#v2,', ','</word><word>') + '</word>' AS XML) AS words)
INSERT INTO #v2Words(word)
SELECT word.x.value('.','VARCHAR(100)') AS [word]
FROM cteSplitV2
CROSS APPLY words.nodes('/word') AS word(x)
SELECT *
FROM #v1Words v1
JOIN #v2Words v2
ON v1.word = v2.word
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
I am creating a computed column across fields of which some are potentially null.
The problem is that if any of those fields is null, the entire computed column will be null. I understand from the Microsoft documentation that this is expected and can be turned off via the setting SET CONCAT_NULL_YIELDS_NULL. However, there I don't want to change this default behavior because I don't know its implications on other parts of SQL Server.
Is there a way for me to just check if a column is null and only append its contents within the computed column formula if its not null?
You can use ISNULL(....)
SET #Concatenated = ISNULL(#Column1, '') + ISNULL(#Column2, '')
If the value of the column/expression is indeed NULL, then the second value specified (here: empty string) will be used instead.
From SQL Server 2012 this is all much easier with the CONCAT function.
It treats NULL as empty string
DECLARE #Column1 VARCHAR(50) = 'Foo',
#Column2 VARCHAR(50) = NULL,
#Column3 VARCHAR(50) = 'Bar';
SELECT CONCAT(#Column1,#Column2,#Column3); /*Returns FooBar*/
Use COALESCE. Instead of your_column use COALESCE(your_column, ''). This will return the empty string instead of NULL.
You can also use CASE - my code below checks for both null values and empty strings, and adds a seperator only if there is a value to follow:
SELECT OrganisationName,
'Address' =
CASE WHEN Addr1 IS NULL OR Addr1 = '' THEN '' ELSE Addr1 END +
CASE WHEN Addr2 IS NULL OR Addr2 = '' THEN '' ELSE ', ' + Addr2 END +
CASE WHEN Addr3 IS NULL OR Addr3 = '' THEN '' ELSE ', ' + Addr3 END +
CASE WHEN County IS NULL OR County = '' THEN '' ELSE ', ' + County END
FROM Organisations
Use
SET CONCAT_NULL_YIELDS_NULL OFF
and concatenation of null values to a string will not result in null.
Please note that this is a deprecated option, avoid using.
See the documentation for more details.
I just wanted to contribute this should someone be looking for help with adding separators between the strings, depending on whether a field is NULL or not.
So in the example of creating a one line address from separate fields
Address1, Address2, Address3, City, PostCode
in my case, I have the following Calculated Column which seems to be working as I want it:
case
when [Address1] IS NOT NULL
then ((( [Address1] +
isnull(', '+[Address2],'')) +
isnull(', '+[Address3],'')) +
isnull(', '+[City] ,'')) +
isnull(', '+[PostCode],'')
end
Hope that helps someone!
ISNULL(ColumnName, '')
I had a lot of trouble with this too. Couldn't get it working using the case examples above, but this does the job for me:
Replace(rtrim(ltrim(ISNULL(Flat_no, '') +
' ' + ISNULL(House_no, '') +
' ' + ISNULL(Street, '') +
' ' + ISNULL(Town, '') +
' ' + ISNULL(City, ''))),' ',' ')
Replace corrects the double spaces caused by concatenating single spaces with nothing between them. r/ltrim gets rid of any spaces at the ends.
In Sql Server:
insert into Table_Name(PersonName,PersonEmail) values(NULL,'xyz#xyz.com')
PersonName is varchar(50), NULL is not a string, because we are not passing with in single codes, so it treat as NULL.
Code Behind:
string name = (txtName.Text=="")? NULL : "'"+ txtName.Text +"'";
string email = txtEmail.Text;
insert into Table_Name(PersonName,PersonEmail) values(name,'"+email+"')
This example will help you to handle various types while creating insert statements
select
'insert into doc(Id, CDate, Str, Code, Price, Tag )' +
'values(' +
'''' + convert(nvarchar(50), Id) + ''',' -- uniqueidentifier
+ '''' + LEFT(CONVERT(VARCHAR, CDate, 120), 10) + ''',' -- date
+ '''' + Str+ ''',' -- string
+ '''' + convert(nvarchar(50), Code) + ''',' -- int
+ convert(nvarchar(50), Price) + ',' -- decimal
+ '''' + ISNULL(Tag, '''''') + '''' + ')' -- nullable string
from doc
where CDate> '2019-01-01 00:00:00.000'