Multiple IIFs and CHARINDEXs searches to extract data from same column in SQL? - sql

This is MS SQL Server 2017.
This currently works. I just can't believe that this is the best way to perform these actions.
The Meeting table is populated from multiple services. It has a MeetingComment column that we used to encode some additional information from some of those sources but is unused by other sources. I want to extract some of the coded information, when used, into separate columns: Source and MeetingType.
MeetingComment
Source
MeetingType
{{{[ASTRA][CLASS][TMR:OFF]}}} ...
ASTRA
CLASS
{{{[ASTRA][CLASS][MERGED][TMR:OFF]}}} ...
ASTRA
CLASS
{{{[ASTRA][EVENT:Study Session][TMR:OFF]}}} ...
ASTRA
EVENT:Study Session
{{{[ASTRA][EVENT:Meeting][TMR:OFF]}}} ...
ASTRA
EVENT:Meeting
{{{[ASTRA][EVENT:Maintenance][TMR:ON]}}} ...
ASTRA
EVENT:Maintenance
UNK
UNK
Here is the SQL that I currently have that is working:
SELECT
Meeting.MeetingID,
Meeting.MeetingComment,
Meeting.Subject,
Rooms.RoomName,
IIF(
CHARINDEX(
'{{{[', Meeting.MeetingComment
) = 1,
SUBSTRING(
Meeting.MeetingComment,
5,
CHARINDEX(
']', Meeting.MeetingComment
)-5
),
'UNK'
) AS Source,
IIF(
CHARINDEX(
'{{{[', Meeting.MeetingComment
) = 1,
SUBSTRING(
Meeting.MeetingComment,
CHARINDEX(
'[',
Meeting.MeetingComment,
(
CHARINDEX(
'[', Meeting.MeetingComment,
1
)
) + 2
) + 1,
CHARINDEX(
']',
Meeting.MeetingComment,
(
CHARINDEX(
']', Meeting.MeetingComment,
1
)
)+ 2
)- CHARINDEX(
'[',
Meeting.MeetingComment,
(
CHARINDEX(
'[', Meeting.MeetingComment,
1
)
) + 2
) -1
),
'UNK'
) AS MeetingType,
Meeting.Recurrence,
Meeting.Location
But, as a programmer, it bugs me to have to use the same conditional test (the IIF statements) for both fields and to have to do the same CHARINDEX lookups multiple times. So before I move on, I just wanted to check to see if there is a better way to do this. Thanks in advance.

It appears from your example you are simply after the content of the first and second set of brackets.
Assuming the current version of SQL Server a more elegant solution would be a little parsing with json conbined with conditional aggregation:
select MeetingComment, Max([Source]) [Source], Max(MeetingType) MeetingType
from t
cross apply (
select
case when [key]='1' then j.[value] end [Source],
case when [key]='3' then j.[value] end MeetingType
from OpenJson(Concat('["', replace(Translate(MeetingComment,'[]',',,'), ',', '","'), '"]')) j
)s
group by MeetingComment;

Related

How to make a line break in a stuff function when using DISTINCT

I have a stuff function that concatenates multiple records and I put a line break after every second record and its works fine with this query:
STUFF((
SELECT CASE WHEN ROW_NUMBER() OVER (order by new_name) % 2 = 1 THEN CHAR(10) ELSE ',' END + new_name
FROM new_subcatagories
FOR XML PATH('')), 1, 1, '')
and the result is
Auditory,Kinesthetic vestibular
Multitasking,Planning & organization
Proprioception,Tactile
Vestibular tactile,Visual
But I want now to make this with a other column that I need to DISTINCT and I can't get it work my query is:
STUFF((
SELECT distinct (CASE WHEN ROW_NUMBER() OVER (order by new_maincatgoriesname) % 2 = 1 THEN CHAR(10) ELSE ',' END
+ new_maincatgoriesname)
FOR XML PATH('')), 1, 1, '')
and I get the result is in multiple not expected ways for example
Executive Function
Sensory Discrimination
Sensory modulation ,Multitasking,Sensory Discrimination,Sensory modulation
or other not expected ways, and I want the result to be
Executive Function,Sensory Discrimination
Sensory modulation,Multitasking
If someone can help my it will be really appreciated.
DISTINCT applies to the entire row so having an extra column populated with unneeded data (such as ROW_NUMBER()) would give invalid results.
To fix it you need to add another query nesting level.
DECLARE #Blah TABLE( new_maincatgoriesname VARCHAR( 200 ))
INSERT INTO #Blah
VALUES( 'Executive Function' ), ( 'Sensory Discrimination' ), ( 'Multitasking' ),
( 'Sensory Discrimination' ), ( 'Executive Function' ), ( 'Sensory modulation' )
SELECT
STUFF( CAST((
-- Step 2: manipulate result of Step 1
SELECT (CASE WHEN ROW_NUMBER() OVER (order by new_maincatgoriesname) % 2 = 1 THEN CHAR(10) ELSE ',' END + new_maincatgoriesname )
FROM
-- Step 1: Get distinct values
( SELECT DISTINCT new_maincatgoriesname
FROM #Blah ) AS MainQuery
FOR XML PATH('') ) AS VARCHAR( 2000 )), 1, 1, '' )
Output:
Executive Function,Multitasking
Sensory Discrimination,Sensory modulation

Find Substring in SQL

I have to find substring as follows.
Data as below
aaaa.bbb.ccc.dddd.eee.fff.ggg
qq.eeddde.rrr.t.hh.jj.jj.hh.hh
ee.r.t.y.u.i.ii.
I want output as-
bbb
eeeddde
r
challenge I am facing is all have (.) as separator so sub-string is tough to work.
SELECT SUBSTRING(string,CHARINDEX('.',string)+1,
(((LEN(string))-CHARINDEX('.', REVERSE(string)))-CHARINDEX('.',string))) AS Result
FROM [table]
bbb
eeeddde
r
looking substring between first and secound (.)
then it might be between second and third (.)
Here is one method:
select left(v.str1, charindex('.', v.str1 + '.') - 1)
from t cross apply
(values (stuff(t.string, 1, charindex('.', t.string + '.'), '')
) v(str1)
I assume (CHARINDEX) this is ms sql server.
CROSS APPLY is handy for intermediate calculations.
SELECT t.pos, t1.pos,
SUBSTRING(string, t.pos + 1, t1.pos - t.pos -1) AS Result
FROM [table]
CROSS APPLY ( VALUES(CHARINDEX('.',string)) ) t(pos)
CROSS APPLY ( VALUES(CHARINDEX('.',string, t.pos+1))) t1(pos)
Just another option is to use a little XML
Example
Declare #YourTable table (ID int,SomeColumn varchar(max))
Insert Into #YourTable values
(1,'aaaa.bbb.ccc.dddd.eee.fff.ggg')
,(2,'qq.eeddde.rrr.t.hh.jj.jj.hh.hh')
,(3,'ee.r.t.y.u.i.ii.')
Select ID
,SomeValue = convert(xml,'<x>' + replace(SomeColumn,'.','</x><x>')+'</x>').value('/x[2]','varchar(100)')
From #YourTable
Returns
ID SomeValue
1 bbb
2 eeddde
3 r
You can use left(), replace() and charindex() functions together :
select replace(
replace(
left(str,charindex('.',str,charindex('.',str)+1)),
left(str,charindex('.',str)),
''
),
'.'
,''
) as "Output"
from t;
Demo

Split alpha and numeric using sql -followup Q

This is actually a follow-up question to a topic mentioned here:
split alpha and numeric using sql
Facing similar problems, I adjusted #knkarthick24 (a chance to say tnx!) query to my needs almost perfectly (I have “2,500” not “2 500”) as follow:
SELECT [Quantity]
,substring(replace(subsrtunit, ',', ''), PATINDEX('%[0-9.]%', replace(subsrtunit, ',', '')) + 1, len(subsrtunit)) AS unit
,LEFT(replace(subsrtnumeric, ',', ''), PATINDEX('%[^0-9.]%', replace(subsrtnumeric, ',', '') + 't') - 1) AS num
FROM (
SELECT [Quantity]
,subsrtunit = SUBSTRING([Quantity], posofchar, LEN([Quantity]))
,subsrtnumeric = SUBSTRING([Quantity], posofnumber, LEN([Quantity]))
FROM (
SELECT [Quantity]
,posofchar = PATINDEX('%[^0-9.]%', replace([Quantity], ',', ''))
,posofnumber = PATINDEX('%[0-9.]%', replace([Quantity], ',', ''))
FROM [OPI].[dbo].[MRRInvoices]
WHERE DocumentNum IS NOT NULL
) d
) t
the only thing left is handling negative values.
Right now the results I'm getting for a negative value in Quantity field (-1.00 GB) is (.00 GB) in the unit field and (1.00) for num field.
Also, does anyone know how to "translate" it to derived column in SSIS?
Can "Findstring" in SSIS replace PATINDEX?
Thank you all in advance.
!1[img]

how to covert this string in 10 different nodes in SQL Server 2012?

My String is =
[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]
How to convert it into 10 different nodes/values? My current attempt is like this
select CAST('<A>'+REPLACE(REPLACE( REPLACE(REPLACE('[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]', '[', ''), ']', ''),',',''),' ','</A><A>')+'</A>' AS XML) AS Data
Answer=
<A>10</A><A>17</A><A>315</A><A>410</A><A>114</A><A>1</A>
I want it in 10 nodes/values instead of above. How i should do it in sql server 2012?
This is too long for comments
select REPLACE(REPLACE(REPLACE(#data, '],[', ''), '[', ''), ']', '')
Result :
10, 17, 315, 410, 114, 1
EDIT :
Is seems to you are looking for values only
select LTRIM(REPLACE(REPLACE(a.value('.', 'VARCHAR(30)'), '[', ''), ']', '')) [Data] from
(
select CAST('<A>'+REPLACE('[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]', ',', '</A><A>')+'</A>' AS xml) AS Data
)a cross apply Data.nodes ('/A') as split(a)
Result :
Data
10
1
7
3
15
4
10
1
14
1
Already provided answers seem to work well, but I thought about a more versatile one (may work in more complex scenarios) using regular expressions:
Install sql-server-regex (e.g. for Sql Server 2014)
Use a "split" method
select Match from dbo.RegexSplit(#data, '\D') where Match <> ''
Performance testing
I noticed that using CLR functions is much faster than REPLACE as indicated below:
Using RegexSplit (about 20s for 1M elements)
declare #baseMsg varchar(max) = '[10, 1],[7, 3],[15, 4],[10, 1],[14, 1],'
declare #data varchar(max) = replicate(#baseMsg, 1000000)
select Match from dbo.RegexSplit(#data, '\D') where Match <> ''
Using REPLACE (about 15s for 2K elements)
declare #baseMsg varchar(max) = '[10, 1],[7, 3],[15, 4],[10, 1],[14, 1],'
declare #data varchar(max) = replicate(#baseMsg, 200)
select LTRIM(REPLACE(REPLACE(a.value('.', 'VARCHAR(30)'), '[', ''), ']', '')) [Data] from
(
select CAST('<A>'+REPLACE(#data, ',', '</A><A>')+'</A>' AS xml) AS Data
)a cross apply Data.nodes ('/A') as split(a)
So, we are talking about a difference of three orders of magnitude.
Of course, the solution should be chosen based on string length ,security permissions (maybe the SQLCLR is not allowed or the external library must be analyzed before it is allowed to run within SQL Server).
I found the answer, SQL should be as below :
select CAST('<A>'+REPLACE(REPLACE( REPLACE(
REPLACE('[10, 1],[7, 3],[15, 4],[10, 1],[14, 1]', '[', ''),
']', ' '),
',',''),
' ','</A><A>') +'</A>' AS XML) AS Data

SQL SERVER select string from right after a certain character

I have a bit of problem regarding sql select statement.
I have a column value that look like this
2>4>5 or
28>30>52 or
300>410>500 or
2>4>5>8
My question is, how can i get the value from RIGHT after the >
character, so the select statement from the value above will return
4
30
410
5
Thanks in advance
If you need second value from right, then try:
SELECT SUBSTRING_INDEX( SUBSTRING_INDEX(your_column, '>', -2), '>', 1);
EDIT
One solution for sql server:
DECLARE #str varchar(max);
set #str = '2>4>5>8';
SELECT reverse( substring(
substring( reverse(#str), charindex( '>', reverse(#str) )+1, len(#str) ), 0,
charindex( '>', substring( reverse(#str), charindex( '>', reverse(#str) )+1, len(#str) ) )
) );
This is similar to extracting the n-th element from a delimited string. The only difference is that in this case we want the n-th-to-last element. The change can be achieved with a double use of reverse. Assuming the table is MyTable and the field is MyColumn, here's one way:
SELECT
Reverse(
CAST('<x>' + REPLACE(Reverse(MyColumn),'>','</x><x>') + '</x>' AS XML).value('/x[2]', --x[2] because it's the second element in the reversed string
'varchar(5)' --Use something long enough to catch any number which might occur here
))
FROM
MyTable
With credit to #Shnugo for his efforts here: Using T-SQL, return nth delimited element from a string
You can't cast as an int where I've put varchar(5)since at that stage the strings are still reversed. If you need to convert to an integer, do that by wrapping a convert/cast on the outside.
;WITH cte1(Value)
AS
(
SELECT '2>4>5' Union all
SELECT '28>30>52' Union all
SELECT '300>410>500' Union all
SELECT '2>4>5>8'
)
SELECT
SUBSTRING(
(
REVERSE(SUBSTRING(((REVERSE((SUBSTRING(Value, RIGHT(CHARINDEX('>', Value), Len(Value)) + 1, Len(Value)))))),
CHARINDEX('>',((REVERSE((SUBSTRING(Value, RIGHT(CHARINDEX('>', Value), Len(Value)) + 1, Len(Value)))))))+1,LEN(Value)))
),CHARINDEX('>',(
REVERSE(SUBSTRING(((REVERSE((SUBSTRING(Value, RIGHT(CHARINDEX('>', Value), Len(Value)) + 1, Len(Value)))))),
CHARINDEX('>',((REVERSE((SUBSTRING(Value, RIGHT(CHARINDEX('>', Value), Len(Value)) + 1, Len(Value)))))))+1,LEN(Value)))
))+1,LEN(Value))
AS ExpectedValue
FROM cte1