I have a custom function to split a string:
CREATE FUNCTION [dbo].[SplitString]
(
#str NVARCHAR(MAX),
#separator CHAR(1)
)
RETURNS TABLE
AS
return (
with tokens(p, a, b) AS (
select
cast(1 as bigint),
cast(1 as bigint),
charindex(#separator, #str)
union all
select
p + 1,
b + 1,
charindex(#separator, #str, b + 1)
from tokens
where b > 0
)
select
p-1 ItemIndex,
substring(
#str,
a,
case when b > 0 then b-a ELSE LEN(#str) end)
AS Item
from tokens
);
(credit for that code goes to someone else on Stack Overflow who I can't remember)
This works fine in another script I use to do the same thing.
Ultimately this makes up part of an ETL, with the aim of taking this:
ID | Title
1 | Mrs K
Into This:
ClientContactRef | Title | Forename
1 | Mrs | K
My code looks like this:
DECLARE #Title varchar(max)
DECLARE #ThirdPartyRef int
DECLARE cur CURSOR FOR
SELECT Title, ID FROM [Abacus].[dbo].[ThirdParty]
WHERE Title IS NOT NULL
OPEN cur
WHILE ##FETCH_STATUS = 0
BEGIN
FETCH NEXT FROM cur INTO #Title, #ThirdPartyRef
UPDATE Migrate.ClientContact
SET Title = LEFT(Item,60)
FROM SplitString(#Title,CHAR(32))
WHERE ItemIndex = 0
AND Migrate.ClientContact.ClientContactRef = #ThirdPartyRef
UPDATE Migrate.ClientContact
SET Forename = LEFT(Item,60)
FROM SplitString(#Title,CHAR(32))
WHERE ItemIndex = 1
AND Migrate.ClientContact.ClientContactRef = #ThirdPartyRef
END
CLOSE Cur
Deallocate Cur
GO
As mentioned I use this elsewhere to split out an address into 5 components (which I also need to do here) but I get the same problem - namely that there doesn't seem to be any output from split string inside the cursor, so all my target fields end up as null.
The splitstring function works outside of the cursor on a single row as expected, but once inside the cursor nothing appears to be being returned. I attempted to put a temp table inside the cursor to catch the output of splitstring, and there wasn't any.
Thanks in advance
Simon
This should achieve what you are trying to do, in a single update statement and without a cursor.
UPDATE MCC SET
MCC.Title = CASE WHEN SST.ItemIndex = 0 THEN LEFT(SST.Item,60) ELSE MCC.Title END
,MCC.ForeName = CASE WHEN SST.ItemIndex = 1 THEN LEFT(SST.Item,60) ELSE MCC.ForeName END
FROM Migrate.ClientContact AS MCC
JOIN Abacus.dbo.ThirdParty AS ATP
ON MCC.ClientContactRef = ATP.ID
CROSS APPLY SplitString(ATP.Title,CHAR(32)) AS SST
WHERE SST.ItemIndex IN (0,1)
;
Related
Let's start with the basics. Here's the simplified structure of the data coming into the report:
ID | Tags
1 |A|
2 |A|B|
3 |B|
4 |A|C|D|
5 |B|D|
6 |D|A|C| --I added this row to show that tags could be in any order
I have a parameter on the report where users can choose one or more tags from a list (A,B,C,D)
Here's the output I'd like to see on the report. It'll be exported into Excel so I'll be using that to describe the desired output.
Sample report output: (Tag parameter selection: A and D)
Worksheet 1 = displays all records => [1,2,3,4,5,6]
Worksheet 2 = displays records that match all tags selected (must have tags for both A AND D!) => [4,6]
Worksheet 3 = displays records that have tag A => [1,2,4,6]
Worksheet 4 = displays records that have tag D => [4,5,6]
**Note: Worksheets 3 and up will show each of the tags selected in a separate worksheet, there could be 1 to N sheets.
Currently in the report I have 3 tables ready to go:
Table 1: Just displays the full query (nice and easy!) and has a PageName="All records"
Table 2: Need to filter full query down to match Worksheet 2 above and will have a PageName="Filtered records" This is problem #1! Looking for ideas on a filter query!
Table 3: Need to group the full query by Tag, but also only displays groups where the tag is in the list of tags selected in the parameter. This is problem #2! Can't just take the filter from Table 2 and then group because records would be missing (such as number 5 for tag D)
Any and all help would be greatly appreciated!!
Additional notes:
Tag delimiter could be changed (I chose | because the data has commas)
Regardless of delimiter, tags can only come back in one column (delimited list) due to aggregation in other columns
There are several questions asked here. I'll deal with the one that will make you queries simple first.
I'm not sure if I've met your criteria as I didn't understand some of what you said but anyway, it might point you in the right direction.
Create a split function in your database if you don't already have one
If you don't have one, you can use this one I created years ago. It's not perfect but does the job for me.
CREATE FUNCTION [fnSplit](#sText varchar(8000), #sDelim varchar(20) = ' ')
RETURNS #retArray TABLE (idx smallint Primary Key, value varchar(8000))
AS
BEGIN
DECLARE #idx smallint,
#value varchar(8000),
#bcontinue bit,
#iStrike smallint,
#iDelimlength tinyint
IF #sDelim = 'Space'
BEGIN
SET #sDelim = ' '
END
SET #idx = 0
SET #sText = LTrim(RTrim(#sText))
SET #iDelimlength = DATALENGTH(#sDelim)
SET #bcontinue = 1
IF NOT ((#iDelimlength = 0) or (#sDelim = 'Empty'))
BEGIN
WHILE #bcontinue = 1
BEGIN
--If you can find the delimiter in the text, retrieve the first element and
--insert it with its index into the return table.
IF CHARINDEX(#sDelim, #sText)>0
BEGIN
SET #value = SUBSTRING(#sText,1, CHARINDEX(#sDelim,#sText)-1)
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
--Trim the element and its delimiter from the front of the string.
--Increment the index and loop.
SET #iStrike = DATALENGTH(#value) + #iDelimlength
SET #idx = #idx + 1
SET #sText = LTrim(Right(#sText,DATALENGTH(#sText) - #iStrike))
END
ELSE
BEGIN
--If you can't find the delimiter in the text, #sText is the last value in
--#retArray.
SET #value = #sText
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
--Exit the WHILE loop.
SET #bcontinue = 0
END
END
END
ELSE
BEGIN
WHILE #bcontinue=1
BEGIN
--If the delimiter is an empty string, check for remaining text
--instead of a delimiter. Insert the first character into the
--retArray table. Trim the character from the front of the string.
--Increment the index and loop.
IF DATALENGTH(#sText)>1
BEGIN
SET #value = SUBSTRING(#sText,1,1)
BEGIN
INSERT #retArray (idx, value)
VALUES (#idx, #value)
END
SET #idx = #idx+1
SET #sText = SUBSTRING(#sText,2,DATALENGTH(#sText)-1)
END
ELSE
BEGIN
--One character remains.
--Insert the character, and exit the WHILE loop.
INSERT #retArray (idx, value)
VALUES (#idx, #sText)
SET #bcontinue = 0
END
END
END
RETURN
END
This function just splits your delimited strings into it's components as a table.
We can then use CROSS APPLY to give us a result set that should be easier to work with. As an example I recreated your sample data then used CROSS APPLY like this...
DECLARE #t table(ID int, Tags varchar(100))
INSERT INTO #t VALUES
(1,'|A|'),
(2,'|A|B|'),
(3,'|B|'),
(4,'|A|C|D|'),
(5,'|B|D|'),
(6,'|D|A|C|')
SELECT * FROM #t t
CROSS APPLY fnSplit(Tags,'|') f
WHERE f.Value != ''
This gives us this output
ID Tags idx value
1 |A| 1 A
2 |A|B| 1 A
2 |A|B| 2 B
3 |B| 1 B
4 |A|C|D| 1 A
4 |A|C|D| 2 C
4 |A|C|D| 3 D
5 |B|D| 1 B
5 |B|D| 2 D
6 |D|A|C| 1 D
6 |D|A|C| 2 A
6 |D|A|C| 3 C
To get all records just do
SELECT DISTINCT t.* FROM #t t
CROSS APPLY fnSplit(Tags,'|') f
WHERE f.Value != ''
To get the filtered records, assuming you have a parameter called #pTags then change the dataset statement to something like
SELECT DISTINCT t.ID, f.Value FROM #t t
CROSS APPLY fnSplit(Tags,'|') f
WHERE f.Value != ''
and f.Value IN (#pTags)
As long as this is directly in your dataset query and the parameter is multi-value then this should filter correctly, use DISTINCT if required.
I've a task to print substring from a string based on the occurence of slash '/' position in a string .Below i'm trying to show you one sample example how i want to get .
Declare #My_string = 'abc/def gh /ijk l/m/no p/qr
From the above string i want to print the substring value based on the position of slash occourence.
For Example: Sometimes i was asked to print the substring after the 2nd occourence of slah then i've to display ijk l and Sometimes i was asked to print the substring after the 3rd occourence then m should display if it is 1st then def gh vice versa .There is no specific occourence position it may vary based on need .How can i achieve it .Any help will be very thankful
Note: I want to achieve without using function .i know that it can be done by using split function .Is that possible to get without using it
One way to do it without using a function is to use a recursive cte.
This is not the way I would recommend splitting a string but since you insist on not using a function it's a reasonable alternative.
If you change your mind about splitting string function, you should read Aaron Bertrand's Split strings the right way – or the next best way and choose a split string function that would be easy to modify to return the item number as well.
Sample data:
DECLARE #My_string varchar(100) = 'abc/def gh /ijk l/m/no p/qr',
#SlashIndex int = 3
The CTE:
;WITH CTE AS
(
SELECT LEFT(#My_String, CHARINDEX('/', #My_String)-1) As Value,
0 As SlashIndex,
RIGHT(#My_String, LEN(#My_String) - CHARINDEX('/', #My_String)) As String
WHERE CHARINDEX('/', #My_String) > 0
OR LEN (#My_String) > 0
UNION ALL
SELECT LEFT(String, CASE WHEN CHARINDEX('/', String) > 0 THEN CHARINDEX('/', String) ELSE LEN(String) +1 END -1),
SlashIndex + 1,
RIGHT(String, LEN(String) - CASE WHEN CHARINDEX('/', String) > 0 THEN CHARINDEX('/', String) ELSE LEN(String) END)
FROM CTE
WHERE CHARINDEX('/', String) > 0
OR LEN(String) > 0
)
The Query:
SELECT Value
FROM CTE
WHERE SlashIndex = #SlashIndex
Result: m
You can see a live demo on rextester.
A simple way to get the 3rd position via XML
Example
Declare #My_string varchar(max)= 'abc/def gh /ijk l/m/no p/qr'
Select Pos3 = convert(xml,'<x>'+replace(#My_String,'/','</x><x>')+'</x>').value('/x[3]','varchar(100)')
Returns
Pos3
ijk l
created below function which will take 2 input first - your position and second will be your string and return the required output -
create function dbo.GetSubsting ( #StatPoint int , #Input_string varchar(1000) )
returns varchar(1000)
as
begin
Declare #len int , #idx int =1 , #section int = 1 , #output varchar(1000) = ''
DECLARE #tab table ( idx int identity(1,1 ), Val char(1) , section int )
select #len = len(#Input_string)
while #idx <= #len
begin
insert into #tab
select substring( #Input_string , #idx ,1) , #section
if substring( #Input_string , #idx ,1) = '/'
set #section = #section + 1
set #idx = #idx + 1
end
select #output = #output + Val from #tab where section = #StatPoint
select #output = replace(#output , '/' , '')
RETURN #output
end
go
select dbo.GetSubsting(3, 'abc/def gh /ijk l/m/no p/qr')
--OUTPUT
-- ijk l
I am creating a store procedure and in which am I stuck in a problem. I want to query two columns based on condition. If parameter is numeric then query to one column and if it is nonnumeric then query to other column. Following is the procedure.
$
declare #result AS varchar(50)
DECLARE #peopleId AS varchar(50)
if('232332' NOT LIKE '%[^0-9]%')
BEGIN
SET #result='Numeric'
PRINT #result
END
ELSE
BEGIN
set #result='nonNumeric'
print #result
END
select isnull(class.grade,'') as grade,ISNULL(class.room,'') as room,student.prefix as prefix,student.student_id as student_id,(person.first_name+' '+person.last_name) as name,
person.dob as dob,person.people_id as people_id,quit_on,
case when student.student_status='30' then
N'พักการเรียน'
when student.student_status='31' then
N'น.ร.ไปเรียนโครงการฯ'
else ''
end
as quit_reason from school_student student
inner join people_person person on student.person_id=person.id
left join school_classroom_students classStudent on classStudent.student_id=student.id
left join school_classroom class on class.id =classStudent.classroom_id
where student.student_status in('30','31') and student.system_status = 'DC' and student.school_id=#schoolId
AND case
WHEN
#result='nonNumeric' then-- this should execure
person.people_id=#peopleId
else---- this should work
person.first_name+' '+ person.last_name LIKE '%'+#peopleId+'%'
Please help me out on this
Why would use use a separate variable? You can do:
WHEN (person.people_id = try_convert(int, #peopleId) or
try_convert(int, #peopleId) is null and
person.first_name + ' ' + person.last_name LIKE '%' + #peopleId + '%'
)
I question why you are passing a value that is used for both a string and numeric comparison. If I were using a variable, I would do:
declare #personid int;
declare #personname varchar(255);
if #peopleid not like '%[^0-9]%'
set #personname = #peopleid;
else
set #personid = convert(int, #peopleid);
where (personid = #personid or
person.first_name + ' ' + person.last_name LIKE '%' + #personname + '%'
)
The code just seems easier to follow.
Since SQL Server doesn't treat results of CASE expressions as booleans, you have to add an extra comparison. The way to do that is like this:
WHERE 1 = CASE WHEN x THEN 1 WHEN y THEN 0 ELSE 1 END
Conditions which result in rows being included in the result must evaluate to 1, and conditions which don't, must evaluate to something other than 1 (like 0). So the whole CASE expression returns either 0 or 1, and that is compared to 1.
In your code, it would look like this:
AND 1 = case
WHEN
#result='nonNumeric' then case when person.person_id = #peopleId then 1 else 0 end
else when person.first_name+' '+person.last_name LIKE '%'+#peopleId+'%' then 1 else 0 end
end
I added the END.
Just do like that
DECLARE #IsNumeric INT = NULL
DECLARE #IsNotNumeric INT = NULL
DECLARE #peopleId Varchar(50)
SET #peopleId = '123'
IF ISNUMERIC(#peopleId) = 1
BEGIN
SET #IsNumeric = 1
END
ELSE
BEGIN
SET #IsNotNumeric = 1
END
IN WHERE Condition Just Check
AND (#IsNumeric IS NULL OR CONVERT(VARCHAR(500),person.people_id)=#peopleId)
AND (#IsNotNumeric IS NULL OR person.first_name+' '+ person.last_name LIKE '%'+#peopleId+'%')
I have approximately 30,000 records where I need to split the Description field and so far I can only seem to achieve this in Excel. An example Description would be:
1USBCP 2RJ45C6 1DVI 1DP 3MD 3MLP HANDS
Below is my Excel function:
=TRIM(MID(SUBSTITUTE($G309," ",REPT(" ",LEN($G309))),((COLUMNS($G309:G309)-1)*LEN($G309))+1,LEN($G309)))
This is then dragged across ten Excel columns, and splits the description field at each space.
I have seen many questions asked about splitting a string in SQL but they only seem to cover one space, not multiple spaces.
There is no easy function in SQL server to split strings. At least I don't know it. I use usually some trick that I found somewhere in the Internet some time ago. I modified it to your example.
The trick is that first we try to figure out how many columns do we need. We can do it by checking how many empty strings we have in the string. The easiest way is lenght of string - lenght of string without empty string.
After that for each string we try to find start and end of each word by position. At the end we cut simply string by start and end position and assign to coulmns. The details are in the query. Have fun!
CREATE TABLE test(id int, data varchar(100))
INSERT INTO test VALUES (1,'1USBCP 2RJ45C6 1DVI 1DP 3MD 3MLP HANDS')
INSERT INTO test VALUES (2,'Shorter one')
DECLARE #pivot varchar(8000)
DECLARE #select varchar(8000)
SELECT
#pivot=coalesce(#pivot+',','')+'[col'+cast(number+1 as varchar(10))+']'
FROM
master..spt_values where type='p' and
number<=(SELECT max(len(data)-len(replace(data,',',''))) FROM test)
SELECT
#select='
select p.*
from (
select
id,substring(data, start+2, endPos-Start-2) as token,
''col''+cast(row_number() over(partition by id order by start) as varchar(10)) as n
from (
select
id, data, n as start, charindex('','',data,n+2) endPos
from (select number as n from master..spt_values where type=''p'') num
cross join
(
select
id, '' '' + data +'' '' as data
from
test
) m
where n < len(data)-1
and substring(odata,n+1,1) = '','') as data
) pvt
Pivot ( max(token)for n in ('+#pivot+'))p'
EXEC(#select)
Here you can find example in SQL Fiddle
I didn't notice that you want to get rid of multiple blank spaces.
To do it please create some function that preprare your data :
CREATE FUNCTION dbo.[fnRemoveExtraSpaces] (#Number AS varchar(1000))
Returns Varchar(1000)
As
Begin
Declare #n int -- Length of counter
Declare #old char(1)
Set #n = 1
--Begin Loop of field value
While #n <=Len (#Number)
BEGIN
If Substring(#Number, #n, 1) = ' ' AND #old = ' '
BEGIN
Select #Number = Stuff( #Number , #n , 1 , '' )
END
Else
BEGIN
SET #old = Substring(#Number, #n, 1)
Set #n = #n + 1
END
END
Return #number
END
After that use the new version that removes extra spaces.
DECLARE #pivot varchar(8000)
DECLARE #select varchar(8000)
SELECT
#pivot=coalesce(#pivot+',','')+'[col'+cast(number+1 as varchar(10))+']'
FROM
master..spt_values where type='p' and
number<=(SELECT max(len(dbo.fnRemoveExtraSpaces(data))-len(replace(dbo.fnRemoveExtraSpaces(data),' ',''))) FROM test)
SELECT
#select='
select p.*
from (
select
id,substring(data, start+2, endPos-Start-2) as token,
''col''+cast(row_number() over(partition by id order by start) as varchar(10)) as n
from (
select
id, data, n as start, charindex('' '',data,n+2) endPos
from (select number as n from master..spt_values where type=''p'') num
cross join
(
select
id, '' '' + dbo.fnRemoveExtraSpaces(data) +'' '' as data
from
test
) m
where n < len(data)-1
and substring(data,n+1,1) = '' '') as data
) pvt
Pivot ( max(token)for n in ('+#pivot+'))p'
EXEC(#select)
I am probably not understanding your question, but all that you are doing in that formula, can be done almost exactly the same in SQL. I see someone has already answered but to my mind, how can it be necessary to do all that when you can do this. I might be wrong. But here goes.
declare #test as varchar(100)
set #test='abcd1234567'
select right(#test,2)
, left(#test,2)
, len(#test)
, case when len(#test)%2>0
then left(right(#test,round(len(#test)/2,0)+1),1)
else left(right(#test,round(len(#test)/2,0)+1),2) end
Results
67 ab 11 2
So right, left, length and mid can all be achieved.
If the spaces are the "substring" dividers, then: I dont remember well the actual syntax for do-while inside selects of sql, neither have i actually done that per se, but I don't see why it should not be possible. If it doesn't work then you need a temporary table and if that does not work you need a cursor. The cursor would be an external loop around this one to fetch and process a single string at a time. Or you can do something more clever. I am just a novice.
declare #x varchar(1)
declare #n integer
declare #i integer
declare #str varchar(100) -- this is your description. Fetch it and assign it. if in a cursor just use column-name
set #x = null
set #n = 0
set #i = 0
while n < len(#str)
while NOT #x = " "
begin
set #x = left(right(#str,n),1)
n = n+1
end
--insert into or update #temptable blablabla here.
Use i and n to locate substring and then left(right()) it out. or you can SELECT it, but that is a messy procedure if the number of substrings are long. Continue with:
set i = n
set #str = right(#str, i) -- this includes the " ". left() it out at will.
end
Now, a final comment, there should perhaps be a third loop checking for if you are at the last "substring" because I see now this code will throw error when it gets to the end. or "add" an empty space at the end to #str, that will also work. But my time is up. This is a suggestion at least.
I want to check if all given word fragments exist in any order in a given text.
The fragments are supplied by a web application user in a single string separated by spaces like 'abc xyz kj'. They exist in 'mn kj qabc pc xyzw' but do not exist in 'mn kj qabc pc xyw'.
I wrote the following function which works but it looks quite convoluted so I must be doing it wrong. Any ideas on different approaches or how to make it perform?
BTW the database is read only for me so I can't full-text index it and the owners will not do it.
create function dbo.tem_fragmentos(
#texto varchar(max),
#fragmentos varchar(max)
)
returns bit as
begin
declare
#inicio integer = 1,
#fim integer,
#fragmento varchar(max);
set #fragmentos = ltrim(rtrim(#fragmentos));
while charindex(' ', #fragmentos) > 0
set #fragmentos = replace(#fragmentos, ' ', ' ');
while #inicio <= len(#fragmentos) begin
set #fim = charindex(' ', #fragmentos, #inicio + 1);
if #fim = 0 set #fim = len(#fragmentos) + 1;
set #fragmento = substring(#fragmentos, #inicio, #fim - #inicio);
if charindex(#fragmento, #texto) = 0 return 0;
set #inicio = #fim + 1;
end -- while
return 1;
end;
select dbo.tem_fragmentos('clodoaldo pinto neto', ' clo cl nto pinto');
This is how I would do it. Not sure it's any less convoluted...
Create Function dbo.tem_fragmentos
(
#texto varchar(max),
#fragmentos varchar(max)
)
Returns Bit As
Begin
Declare #table Table (fragmentos Varchar(Max))
Set #fragmentos = Ltrim(Rtrim(#fragmentos))
While #fragmentos <> ''
Begin
Insert #table (fragmentos)
Select Left(#fragmentos,Charindex(' ',#fragmentos+' ')-1)
Set #fragmentos = Ltrim(Rtrim(Right(#fragmentos,Len(#fragmentos)-(Charindex(' ',#fragmentos+' ')-1))));
end
If Exists (Select 1
From #table t
Where #texto Not Like '%' + fragmentos + '%')
Begin
Return 0;
End
Return 1;
End;
Select dbo.tem_fragmentos('clodoaldo pinto neto', ' clo cl nto pinto');
I'm assuming your text exists in a db table, else you wouldn't have the db server doing the work. So, why not have your app break the string on spaces and build dynamic SQL like:
select *
from MyTable
where charindex('abc', MyColumn) > 0
and charindex('xyz', MyColumn) > 0
and charindex('kj', MyColumn) > 0
Update:
If you don't want to use dynamic SQL, I would split the input into words in my application, and then pass the list of words in to the query using a table valued parameter (TVP). Then it is a simple left join to determine whether they all match or not.
Sounds like a wildcarded LIKE search should work for you:
declare #texto varchar(max) = 'mn kj q abc pc xyzw',
#fragmentos varchar(max) = 'abc xyz kj'
/*
yes = 'mn kj qabc pc xyzw'
no = 'mn kj qabc pc xyw'
*/
--use your own number table
declare #number table (n int identity(1,1) primary key clustered, x char(1) null);
insert into #number(x)
select top 1000 null from master..spt_values
select [IsMatch] = min(case when #texto like '%'+substring(#fragmentos, n, charindex(' ', #fragmentos + ' ', n) - n)+'%' then 1 else 0 end)
from #number
where n <= datalength(#fragmentos)+1 and
substring(' ' + #fragmentos, N, 1) = ' ';