Split value from one field to two - mysql

I've got a table field membername which contains both the last name and the first name of users. Is it possible to split those into 2 fields memberfirst, memberlast?
All the records have this format "Firstname Lastname" (without quotes and a space in between).

Unfortunately MySQL does not feature a split string function. However you can create a user defined function for this, such as the one described in the following article:
MySQL Split String Function by Federico Cargnelutti
With that function:
DELIMITER $$
CREATE FUNCTION SPLIT_STR(
x VARCHAR(255),
delim VARCHAR(12),
pos INT
)
RETURNS VARCHAR(255) DETERMINISTIC
BEGIN
RETURN REPLACE(SUBSTRING(SUBSTRING_INDEX(x, delim, pos),
LENGTH(SUBSTRING_INDEX(x, delim, pos -1)) + 1),
delim, '');
END$$
DELIMITER ;
you would be able to build your query as follows:
SELECT SPLIT_STR(membername, ' ', 1) as memberfirst,
SPLIT_STR(membername, ' ', 2) as memberlast
FROM users;
If you prefer not to use a user defined function and you do not mind the query to be a bit more verbose, you can also do the following:
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(membername, ' ', 1), ' ', -1) as memberfirst,
SUBSTRING_INDEX(SUBSTRING_INDEX(membername, ' ', 2), ' ', -1) as memberlast
FROM users;

SELECT variant (not creating a user defined function):
SELECT IF(
LOCATE(' ', `membername`) > 0,
SUBSTRING(`membername`, 1, LOCATE(' ', `membername`) - 1),
`membername`
) AS memberfirst,
IF(
LOCATE(' ', `membername`) > 0,
SUBSTRING(`membername`, LOCATE(' ', `membername`) + 1),
NULL
) AS memberlast
FROM `user`;
This approach also takes care of:
membername values without a space: it will add the whole string to memberfirst and sets memberlast to NULL.
membername values that have multiple spaces: it will add everything before the first space to memberfirst and the remainder (including additional spaces) to memberlast.
The UPDATE version would be:
UPDATE `user` SET
`memberfirst` = IF(
LOCATE(' ', `membername`) > 0,
SUBSTRING(`membername`, 1, LOCATE(' ', `membername`) - 1),
`membername`
),
`memberlast` = IF(
LOCATE(' ', `membername`) > 0,
SUBSTRING(`membername`, LOCATE(' ', `membername`) + 1),
NULL
);

It seems that existing responses are over complicated or not a strict answer to the particular question.
I think, the simple answer is the following query:
SELECT
SUBSTRING_INDEX(`membername`, ' ', 1) AS `memberfirst`,
SUBSTRING_INDEX(`membername`, ' ', -1) AS `memberlast`
;
I think it is not necessary to deal with more-than-two-word names in this particular situation. If you want to do it properly, splitting can be very hard or even impossible in some cases:
Johann Sebastian Bach
Johann Wolfgang von Goethe
Edgar Allan Poe
Jakob Ludwig Felix Mendelssohn-Bartholdy
Petőfi Sándor
Virág Vendelné Farkas Margit
黒澤 明
In a properly designed database, human names should be stored both in parts and in whole. This is not always possible, of course.

If your plan is to do this as part of a query, please don't do that (a). Seriously, it's a performance killer. There may be situations where you don't care about performance (such as one-off migration jobs to split the fields allowing better performance in future) but, if you're doing this regularly for anything other than a mickey-mouse database, you're wasting resources.
If you ever find yourself having to process only part of a column in some way, your DB design is flawed. It may well work okay on a home address book or recipe application or any of myriad other small databases but it will not be scalable to "real" systems.
Store the components of the name in separate columns. It's almost invariably a lot faster to join columns together with a simple concatenation (when you need the full name) than it is to split them apart with a character search.
If, for some reason you cannot split the field, at least put in the extra columns and use an insert/update trigger to populate them. While not 3NF, this will guarantee that the data is still consistent and will massively speed up your queries. You could also ensure that the extra columns are lower-cased (and indexed if you're searching on them) at the same time so as to not have to fiddle around with case issues.
And, if you cannot even add the columns and triggers, be aware (and make your client aware, if it's for a client) that it is not scalable.
(a) Of course, if your intent is to use this query to fix the schema so that the names are placed into separate columns in the table rather than the query, I'd consider that to be a valid use. But I reiterate, doing it in the query is not really a good idea.

use this
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX( `membername` , ' ', 2 ),' ',1) AS b,
SUBSTRING_INDEX(SUBSTRING_INDEX( `membername` , ' ', -1 ),' ',2) AS c FROM `users` WHERE `userid`='1'

In MySQL this is working this option:
SELECT Substring(nameandsurname, 1, Locate(' ', nameandsurname) - 1) AS
firstname,
Substring(nameandsurname, Locate(' ', nameandsurname) + 1) AS lastname
FROM emp

Not exactly answering the question, but faced with the same problem I ended up doing this:
UPDATE people_exit SET last_name = SUBSTRING_INDEX(fullname,' ',-1)
UPDATE people_exit SET middle_name = TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(fullname,last_name,1),' ',-2))
UPDATE people_exit SET middle_name = '' WHERE CHAR_LENGTH(middle_name)>3
UPDATE people_exit SET first_name = SUBSTRING_INDEX(fullname,concat(middle_name,' ',last_name),1)
UPDATE people_exit SET first_name = middle_name WHERE first_name = ''
UPDATE people_exit SET middle_name = '' WHERE first_name = middle_name

The only case where you may want such a function is an UPDATE query which will alter your table to store Firstname and Lastname into separate fields.
Database design must follow certain rules, and Database Normalization is among most important ones

I had a column where the first and last name were both were in one column. The first and last name were separated by a comma. The code below worked. There is NO error checking/correction. Just a dumb split. Used phpMyAdmin to execute the SQL statement.
UPDATE tblAuthorList SET AuthorFirst = SUBSTRING_INDEX(AuthorLast,',',-1) , AuthorLast = SUBSTRING_INDEX(AuthorLast,',',1);
13.2.10 UPDATE Syntax

This takes smhg from here and curt's from Last index of a given substring in MySQL and combines them. This is for mysql, all I needed was to get a decent split of name to first_name last_name with the last name a single word, the first name everything before that single word, where the name could be null, 1 word, 2 words, or more than 2 words. Ie: Null; Mary; Mary Smith; Mary A. Smith; Mary Sue Ellen Smith;
So if name is one word or null, last_name is null. If name is > 1 word, last_name is last word, and first_name all words before last word.
Note that I've already trimmed off stuff like Joe Smith Jr. ; Joe Smith Esq. and so on, manually, which was painful, of course, but it was small enough to do that, so you want to make sure to really look at the data in the name field before deciding which method to use.
Note that this also trims the outcome, so you don't end up with spaces in front of or after the names.
I'm just posting this for others who might google their way here looking for what I needed. This works, of course, test it with the select first.
It's a one time thing, so I don't care about efficiency.
SELECT TRIM(
IF(
LOCATE(' ', `name`) > 0,
LEFT(`name`, LENGTH(`name`) - LOCATE(' ', REVERSE(`name`))),
`name`
)
) AS first_name,
TRIM(
IF(
LOCATE(' ', `name`) > 0,
SUBSTRING_INDEX(`name`, ' ', -1) ,
NULL
)
) AS last_name
FROM `users`;
UPDATE `users` SET
`first_name` = TRIM(
IF(
LOCATE(' ', `name`) > 0,
LEFT(`name`, LENGTH(`name`) - LOCATE(' ', REVERSE(`name`))),
`name`
)
),
`last_name` = TRIM(
IF(
LOCATE(' ', `name`) > 0,
SUBSTRING_INDEX(`name`, ' ', -1) ,
NULL
)
);

Method I used to split first_name into first_name and last_name when the data arrived all in the first_name field. This will put only the last word in the last name field, so "john phillips sousa" will be "john phillips" first name and "sousa" last name. It also avoids overwriting any records that have been fixed already.
set last_name=trim(SUBSTRING_INDEX(first_name, ' ', -1)), first_name=trim(SUBSTRING(first_name,1,length(first_name) - length(SUBSTRING_INDEX(first_name, ' ', -1)))) where list_id='$List_ID' and length(first_name)>0 and length(trim(last_name))=0

UPDATE `salary_generation_tbl` SET
`modified_by` = IF(
LOCATE('$', `other_salary_string`) > 0,
SUBSTRING(`other_salary_string`, 1, LOCATE('$', `other_salary_string`) - 1),
`other_salary_string`
),
`other_salary` = IF(
LOCATE('$', `other_salary_string`) > 0,
SUBSTRING(`other_salary_string`, LOCATE('$', `other_salary_string`) + 1),
NULL
);

In case someone needs to run over a table and split a field:
First we use the function mention above:
CREATE DEFINER=`root`#`localhost` FUNCTION `fn_split_str`($str VARCHAR(800), $delimiter VARCHAR(12), $position INT) RETURNS varchar(800) CHARSET utf8
DETERMINISTIC
BEGIN
RETURN REPLACE(
SUBSTRING(
SUBSTRING_INDEX($str, $delimiter, $position),
LENGTH(
SUBSTRING_INDEX($str, $delimiter, $position -1)
) + 1
),
$delimiter, '');
END
Second, we run in a while loop on the string until there isn't any results (I've added $id for JOIN clause):
CREATE DEFINER=`root`#`localhost` FUNCTION `fn_split_str_to_rows`($id INT, $str VARCHAR(800), $delimiter VARCHAR(12), $empty_table BIT) RETURNS int(11)
BEGIN
DECLARE position INT;
DECLARE val VARCHAR(800);
SET position = 1;
IF $empty_table THEN
DROP TEMPORARY TABLE IF EXISTS tmp_rows;
END IF;
SET val = fn_split_str($str, ',', position);
CREATE TEMPORARY TABLE IF NOT EXISTS tmp_rows AS (SELECT $id as id, val as val where 1 = 2);
WHILE (val IS NOT NULL and val != '') DO
INSERT INTO tmp_rows
SELECT $id, val;
SET position = position + 1;
SET val = fn_split_str($str, ',', position);
END WHILE;
RETURN position - 1;
END
Finally we can use it like that:
DROP TEMPORARY TABLE IF EXISTS tmp_rows;
SELECT SUM(fn_split_str_to_rows(ID, FieldToSplit, ',', 0))
FROM MyTable;
SELECT * FROM tmp_rows;
You can use the id to join to other table.
In case you are only splitting one value you can use it like that
SELECT fn_split_str_to_rows(null, 'AAA,BBB,CCC,DDD,EEE,FFF,GGG', ',', 1);
SELECT * FROM tmp_rows;
We don't need to empty the temporary table, the function will take care of that.

mysql 5.4 provides a native split function:
SPLIT_STR(<column>, '<delimiter>', <index>)

Related

Setting one column the records from another, just replacing spaces with commas

So, our database has a column "meta_keywords" that is largely empty. I want to change this by making the meta_keywords the same results as the "title" column, except rather than spaces I want commas.
So if the title was "Virginia State Bank" the meta_keywords would be "virginia,state,bank"
However, this doesn't work:
update my_table set meta_keywords = concat(title, ',', REPLACE(meta_keywords, ' ', ',') WHERE meta_keywords LIKE (' ');
I'm guessing I can't do a replace like that, however, if I simply set the meta_keywords the "title" column and run this:
UPDATE my_table SET meta_keywords= REPLACE(meta_keywords, ' ', ',') WHERE meta_keywords LIKE (' ');
Nothing happens (query runs, but no effected records) even though there are spaces in the mea_keywords table.
Any thoughts?
The issue here is using LIKE (' '). You're doing it like IN (' '). If you want to replace all spaces into comma, you can just skip defining WHERE. Just do:
UPDATE my_table SET meta_keywords = CONCAT(title, ',', REPLACE(meta_keywords, ' ', ','));
Also, it's a good practice to test your query first to see what & how will it update by doing a SELECT like following:
SELECT CONCAT(title, ',', REPLACE(meta_keywords, ' ', ',')) FROM my_table;
This shows how your query works, yourt prior Update would not find any rows matching
I added LOWER because your question put sall workds into lower case
CREATE TABLE my_table (
`title` varchar(10)
,`meta_keywords` VARCHAR(50)
);
INSERT INTO my_table
( `title`,`meta_keywords`)
VALUES
( 'Apple','Virginia State Bank'),
( 'Banana','Pensilvania State Bank'),
( 'Pear','washington State BANK');
✓
✓
update my_table
set meta_keywords = CONCAT(`title`,',',REPLACE(LOWER(`meta_keywords`),' ',','))
WHERE meta_keywords LIKE ('% %');;
✓
SELECT * FROM my_table ;
title | meta_keywords
:----- | :----------------------------
Apple | Apple,virginia,state,bank
Banana | Banana,pensilvania,state,bank
Pear | Pear,washington,state,bank
db<>fiddle here

Query to Search All possible words

I need to write a Delphi 7 and mysql database query which would return the records including ALL words in the submitted name. So query will return records which has all those name words but can have different order.
For example, if search string is John Michael Smith, query should be able to return records with names such as John Smith Michael, Michael Smith John, Smith John Michael or other combination with all those words there.
As can be seen return only records which still has all words in name field but can have different order.
I can't figure out how to write a query for such requirement that I have. Please help.
procedure Tfrm_Query.Button1Click(Sender: TObject);
var
mask : string;
begin
mask:='''%'+StringReplace(Edit1.text,' ','%',[rfReplaceAll, rfIgnoreCase])+'%''';
if Edit1.Text > '' then
begin
Adosorgulama.Close;
Adosorgulama.SQL.Clear;
Adosorgulama.SQL.Add('SELECT * FROM stok.product');
Adosorgulama.SQL.ADD('Where (P_Name like '+mask+') limit 50');
Adosorgulama.Open;
end;
end;
as a result;
edit1.text:='Jo Mich'; // Result Ok!
edit1.text:='Smi Jo Mic'; //No result
edit1.text:='Mich Sm'; // No result
Instead of replacing spaces with %, you could replace them with % AND P_Name LIKE %:
mask:='''WHERE (P_Name LIKE %'+StringReplace(Edit1.text,' ','% AND P_Name LIKE %',[rfReplaceAll, rfIgnoreCase])+'%)''';
Apologies if there is some problem with the syntax (I don't know Delphi), but if Edit1.text:= 'John Michael Smith' this should generate the following WHERE clause:
WHERE (P_Name LIKE %John% AND P_Name LIKE %Michael% AND P_Name LIKE %Smith%)
Which should find all records where P_Name contains the strings 'John', 'Michael' and 'Smith'.
Then, of course, instead of
Adosorgulama.SQL.ADD('Where (P_Name like '+mask+') limit 50');
you'd do something like
Adosorgulama.SQL.ADD(mask + ' limit 50');
If the input can contain extraneous spaces, you will need to remove those first, otherwise this won't work.
Forming SQL queries with string concatenation could make your application vulnerable to SQL injection, just so you know. I don't know how to do prepared statements with Delphi, so I can't help you there.
Write a function that can split the name according to space. Use the following code inside a loop that is looping split results.
Declare #sqlq as nvarchar(max);
-- loop start
#sqlq = sqlq + 'Select * from mytable where names';
#sqlq = sqlq + 'like ''%' + loopvalue + '%''';
--loop end
Exec #sqlq
You can build table of words dynamically. To find yours match do query that join both tables in possible match, and by grouping results test it - is name have all of words, try this:
WITH
words AS (SELECT 'John' AS word FROM dual union
SELECT 'Michael' FROM dual union
SELECT 'Smith' FROM dual ) , --build your table of words (this is example on oracle DB engine)
names AS (SELECT 'John Michael Smith' AS name FROM dual UNION
SELECT 'John SmithMichael' FROM dual union
SELECT 'Smith Michael' FROM dual union
SELECT 'Smith Michael John' FROM dual union
SELECT 'John' FROM dual union
SELECT 'John John' FROM dual union
SELECT 'John John John' FROM dual union
SELECT 'xyz abc' FROM dual ) --this is simulation of yours table of names
SELECT name, Count(DISTINCT word)
FROM names, words
WHERE ' ' || name || ' ' LIKE '% ' || word || ' %'
GROUP BY name
HAVING Count(DISTINCT word) = (SELECT Count(1) FROM words)
;
Problem is solved.
Delphi + MySQL connection with word order with the following code, regardless of calls can be made. Thank you for inspiration. Respects.
Database model;
CREATE TABLE IF NOT EXISTS `TableName` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`PNUMBER` varchar(20) DEFAULT NULL,
`PNAME` varchar(255) DEFAULT NULL,
`PBARCODE` varchar(30) DEFAULT NULL,
`PSearch` mediumtext,
PRIMARY KEY (`ID`),
FULLTEXT KEY `PSearch` (`PSearch`)
) ENGINE=MyISAM DEFAULT CHARSET=latin5 ;
Psearch = PNUMBER + PNAME + PBARCODE ...; (Type in all areas PSearch)
Delphi7 Code;
procedure TForm1.Button1Click(Sender: TObject);
var
mask : string;
begin
mask:='+'+StringReplace(Edit1.text,' ','* +',[rfReplaceAll, rfIgnoreCase])+'*';
if Edit1.Text > '' then
begin
Query1.Close;
Query1.SQL.Clear;
Query1.SQL.Add('SELECT MATCH(PSearch) AGAINST("'+mask+'" IN BOOLEAN MODE), tablename.* FROM database.tablename');
Query1.SQL.Add('WHERE MATCH(PSearch) AGAINST("'+mask+'" IN BOOLEAN MODE) limit 300;');
Query1.Open;
end; end;

MySql replacing spaces with hyphens

A day ago, I asked this question on stackoverflow. Sure, that works well, but does anyone know how I can do the same thing in a MySql statement without any php involved?
Eg: select preg_replace(:songName,' ', '-') //Ugh, this is wrong.
What I'm trying to do Is replace spaces with a -. But sometimes, when there is a space, I'll get more -
Eg: Metallica - Hero of the Day ends up as Metallica---Hero-of-the-Day
Any chance of making it just: Metallica-Hero-of-the-Day
BTW: It's not only song names I'm replacing.
I'm ok with a simple MySql replace, but I can see doing the above is going to need more than that.
I would replace spaces with hyphens first, then deal with any multiple hyphens that may have been created:
select replace(replace(replace(songTitle, ' ', '-'), '---', '-'), '--', '-')
I've replaced --- and -- separately because there are edge cases which overall would require both, and in that order.
See SQLFiddle
Use a user defined function like this(use delimetres accordingly)
CREATE FUNCTION replace_spaceWithHyphen(textToReplace varchar(100))
RETURNS TEXT
BEGIN
DECLARE occHyphen int;
DECLARE occSpace int;
set occHyphen = 1;
set occSpace = 1;
WHILE (occHyphen <> 0 || occSpace <> 0) DO
SELECT LOCATE('--',textToReplace) into occHyphen;
SELECT LOCATE(' ',textToReplace) into occSpace;
SELECT REPLACE(textToReplace,' ','-') into textToReplace;
SELECT REPLACE(textToReplace,'--','-') into textToReplace;
END WHILE;
RETURN textToReplace;
END;
Then call your select like this:
SELECT replace_spaceWithHyphen('Metallica - Hero of the Day');
Answer would be:
TEXT
Metallica-Hero-of-the-Day
SAMPLE FIDDLE
This should work:
select
replace(
replace(
replace('Metallica - Hero of the Day', '-', ' ')
, ' ', '')
, ' ', '-')
You may write your query. It is so easy for you.
Suppose you have a table named class(id, classname) with two fields. Now you insert in your table in classname field i.e. Metallica - Hero of the Day.
Now you can execute this by below given program.
mysql_connect('localhost','root','');
mysql_select_db('dbname'); // Please insert your dbname
$query = mysql_query('SELECT classname, REPLACE(classname," ","-") from class');
$record = mysql_fetch_array($query);
echo $record['REPLACE(classname," ","-")'];
It will give output. i.e. Metallica---Hero-of-the-Day.
and if you replace your query with. I have taken help from Bohemian answer for below query.
$query = mysql_query("SELECT classname, replace(replace(replace(classname, ' ', '-'), '---', '-'), '--', '') from class");
$record = mysql_fetch_array($query);
echo $record["replace(replace(replace(classname, ' ', '-'), '---', '-'), '--', '')"];
You will get result i.e. Metallica-Hero-of-the-Day
That's it. Easy.
Thanks
You can try this method.
UPDATE TABLE_NAME SET column_name = REPLACE(column_name, old_value, new_value);
For Example
UPDATE TABLENAME SET column_name = REPLACE(column_name, '-', ' ');
Hope this will helps you.

Get single item from a list string in MySQL

Given the following strings which represent possible lists, how may I get an item at a specified index n
1,2,3,4,5
word1 word2 word3
pipe|delimited|list
Possible reasons for this functionality are
Extraction of specific elements from GROUP_CONCAT output
Extraction of specific elements from a SET column output (when cast to string)
Extraction of specific elements from a poorly normalised table containing a comma-separated list
Use within an iteration procedure to loop over a list and perform an action on each element within
There is no native function for this. You can use two SUBSTRING_INDEX functions. And you need to check if that specific index item exists:
SET #string:='1,2,3,4,5';
SET #delimiter:=',';
SET #n:=6;
SELECT
CASE WHEN
CHAR_LENGTH(#string)-CHAR_LENGTH(REPLACE(#string, #delimiter, ''))>=
#n*CHAR_LENGTH(#delimiter)-1
THEN
SUBSTRING_INDEX(SUBSTRING_INDEX(#string, #delimiter, #n), #delimiter, -1)
END;
SUBSTRING_INDEX(#string, #delimiter, #n) returns the substring from string #string before #n occurrences of the #delimiter.
SUBSTRING_INDEX( ... , #delimiter, -1) returns everything to the right of the final delimiter
you need to check if delimiter #n exists. We can substract the length of the string with the delimiter, and the string with the delimiter removed - using REPLACE(#string, #delimiter, '') - and see if it is greater than #n*CHAR_LENGTH(#delimiter)-1
Pure SQL way of doing it:-
SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(somefield, '|', 3), '|', -1)
FROM sometable a
If you want to return NULL (or some other fixed value) if there is no 3rd element (for example):-
SELECT IF((LENGTH(somefield) - LENGTH(REPLACE(somefield, '|', '')) + 1) >= 10, SUBSTRING_INDEX(SUBSTRING_INDEX(somefield, '|', 10), '|', -1), NULL)
FROM sometable a
update totally forgot the SUBSTRING_INDEX -1 thing (pointed out by #fthiella and #Kickstart), so updated the examples below
Solved by creating a stored function which is able to be used in-line. The stored function is able to accept an input string, any single-character delimiter, and an index of the desired item to extract.
The stored function definition is as follows
CREATE DEFINER = `user`#`%`
FUNCTION `getListElement`(
inString VARCHAR(255) ,
inDelimiter CHAR(1) ,
inIndexToReturn TINYINT UNSIGNED
) RETURNS varchar(255) READS SQL DATA DETERMINISTIC SQL SECURITY INVOKER
BEGIN
-- Takes in as argument a string, and then breaks out the desired string
DECLARE resultString VARCHAR(255) DEFAULT inString;
DECLARE numberOfListElements TINYINT UNSIGNED DEFAULT 0;
DECLARE errorMessage VARCHAR(255) DEFAULT 'Requested index was < 1 which was invalid';
-- First of all, additional processing only needed for element 2 upwards
IF inIndexToReturn = 1 THEN
RETURN SUBSTRING_INDEX( resultString , inDelimiter , inIndexToReturn);
ELSEIF inIndexToReturn > 1 THEN
-- Count the number of elements
-- This will count the missing delimiters based off the replace. A list of 4 will be missing 3 characters.
SET numberOfListElements = ( CHAR_LENGTH( resultString ) + 1 ) - CHAR_LENGTH( REPLACE( resultString , inDelimiter , '' ) );
IF numberOfListElements >= inIndexToReturn THEN
-- Make sure to only return the last of the elements returend by the first SUBSTRING_INDEX
RETURN SUBSTRING_INDEX( SUBSTRING_INDEX( inString , inDelimiter , inIndexToReturn ) , inDelimiter , -1 );
END IF;
SET errorMessage = CONCAT('List index ',inIndexToReturn,' was requested from a list with ',numberOfListElements,' elements');
END IF;
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = errorMessage;
END
In the examples above, the following could be used to extract specific list elements
SELECT getListElement( '1,2,3,4,5' , ',' , 2 ) returns 2
SELECT getListElement( REPLACE( 'word1 word2 word3' ,' ', ',' ) , ',' , 3 ) returns word3 (see note below for reason on the REPLACE)
SELECT getListElement( 'pipe|delimited|list' , '|' , 1 ) returns pipe
It would also be possible to use this within an iterator to loop over elements in a list. First of all you need to count the items in the list (see How to count items in comma separated list MySQL) however once you have that, it's just a matter of iterating over them as in this fragment from a stored procedure
-- Reinitialise variables
SET #list = '1,2,3,4,5';
SET #delimiter = ',';
SET #listLength = (CHAR_LENGTH( #list ) + 1 ) - CHAR_LENGTH( REPLACE( #list , #delimiter , '' ) );
SET #currentElement = 1;
listLoop: REPEAT
-- Select the list element and do something with it
SELECT getListElement( #list , #delimiter , #currentElement );
-- Increment the current element
SET #currentElement = #currentElement + 1;
UNTIL #currentElement > #listLength
END REPEAT listLoop;
important space-delimited lists seem to cause issues for this procedure, and so prior to parsing a string into the function I would recommend doing a simple REPLACE to replace the spaces with another suitable single-character separator (i.e. , or | depending on the content in the string)

Optimizing SQL Server stored procedures that use functions?

I'd like some help in optimizing the following query:
SELECT DISTINCT TOP (#NumberOfResultsRequested) dbo.FilterRecentSearchesTitles(OriginalSearchTerm) AS SearchTerms
FROM UserSearches
WHERE WebsiteID = #WebsiteID
AND LEN(OriginalSearchTerm) > 20
--AND dbo.FilterRecentSearchesTitles(OriginalSearchTerm) NOT IN (SELECT KeywordUrl FROM PopularSearchesBaseline WHERE WebsiteID = #WebsiteID)
GROUP BY OriginalSearchTerm, GeoID
It runs fine without the line that is commented out. I have an index set on UserSearches.OriginalSearchTerm, WebsiteID, and PopularSearchesBaseline.KeywordUrl, but the query still runs slow with this line in there.
-- UPDATE --
The function used is as follows:
ALTER FUNCTION [dbo].[FilterRecentSearchesTitles]
(
#SearchTerm VARCHAR(512)
)
RETURNS VARCHAR(512)
AS
BEGIN
DECLARE #Ret VARCHAR(512)
SET #Ret = dbo.RegexReplace('[0-9]', '', REPLACE(#SearchTerm, '__s', ''), 1, 1)
SET #Ret = dbo.RegexReplace('\.', '', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\s{2,}', ' ', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\sv\s', ' ', #Ret, 1, 1)
RETURN(#Ret)
END
Using the Reglar Expression Workbench code.
However, as I mentioned - without the line that is currently commented out it runs fine.
Any other suggestions?
I am going to guess that dbo.FilterRecentSearchesTitles(OriginalSearchTerm) is a function. My suggestion would be to see about rewriting it into a table valued function so you can return a table that could be joined on.
Otherwise you are calling that function for each row you are trying to return which is going to cause your problems.
If you cannot rewrite the function, then why not create a stored proc that will only execute it once, similar to this:
SELECT DISTINCT TOP (#NumberOfResultsRequested) dbo.FilterRecentSearchesTitles(OriginalSearchTerm) AS SearchTerms
INTO #temp
WHERE WebsiteID = #WebsiteID
SELECT *
FROM #temp
WHERE SearchTerms NOT IN (SELECT KeywordUrl
FROM PopularSearchesBaseline
WHERE WebsiteID = #WebsiteID)
Then you get your records into a temp table after executing the function once and then you select on the temp table.
I might try to use a persisted computed column in this case:
ALTER TABLE UserSearches ADD FilteredOriginalSearchTerm AS dbo.FilterRecentSearchesTitles(OriginalSearchTerm) PERSISTED
You will probably have to add WITH SCHEMABINDING to your function (and the RegexReplace function) like so:
ALTER FUNCTION [dbo].[FilterRecentSearchesTitles]
(
#SearchTerm VARCHAR(512)
)
RETURNS VARCHAR(512)
WITH SCHEMABINDING -- You will need this so the function is considered deterministic
AS
BEGIN
DECLARE #Ret VARCHAR(512)
SET #Ret = dbo.RegexReplace('[0-9]', '', REPLACE(#SearchTerm, '__s', ''), 1, 1)
SET #Ret = dbo.RegexReplace('\.', '', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\s{2,}', ' ', #Ret, 1, 1)
SET #Ret = dbo.RegexReplace('\sv\s', ' ', #Ret, 1, 1)
RETURN(#Ret)
END
This makes your query look like this:
SELECT DISTINCT TOP (#NumberOfResultsRequested) FilteredOriginalSearchTerm AS SearchTerms
FROM UserSearches
WHERE WebsiteID = #WebsiteID
AND LEN(OriginalSearchTerm) > 20
AND FilteredOriginalSearchTerm NOT IN (SELECT KeywordUrl FROM PopularSearchesBaseline WHERE WebsiteID = #WebsiteID)
GROUP BY OriginalSearchTerm, GeoID
Which could potentially be optimized for speed (if necessary) with a join instead of not in, or maybe different indexing (perhaps on the computed column, or some covering indexes). Also, DISTINCT with a GROUP BY is somewhat of a code smell to me, but it could be legit.
Instead of using using the function on SELECT, I modified the INSERT query to include this function. That way, I avoid calling the function for every row when I later want to retrieve the data.