Splitting column value by '\' in Sql server - sql-server

I have some data in FileFullPath Column
Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\Oracle\2022-05-04\MSudaitemlov_20220503
Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OracleABC\2022-05-04\FDERDMSudaitemlov_20220503
Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OCSBAGF\2022-05-04\AASSSMSudaitemlov_20220503
The part I wanted is:
Oracle
OracleABC
OCSBAGF
The letters are dynamic so, I couldn't apply Left,Right function since the length is different. I tried to split it using '\' by using STRING_SPLIT() but it's saying:
Msg 195, Level 15, State 10, Line 18
'string_split' is not a recognized built-in function name.

You should be able to use STRING_SPLIT() in SQL Server 2016, except in two scenarios:
If you're not calling the function correctly - many people try to call it like a scalar function (SELECT STRING_SPLIT(...) instead of a table-valued function (SELECT * FROM STRING_SPLIT(...). It returns a table, so you must treat it like a table.
If your database's compatibility level is lower than 130. This is called out at the very top of the documentation, and I've given several workarounds in this tip in cases where you can't change compat level.
But STRING_SPLIT() won't solve this problem anyway...
...because the output order is not guaranteed, so you could never reliably determine which element is 3rd from last.
Borrowing shamelessly from my work in this article, you can create the following simple function:
CREATE FUNCTION dbo.SplitOrdered_JSON
(
#List nvarchar(4000),
#Delimiter nvarchar(255)
)
RETURNS table WITH SCHEMABINDING
AS
RETURN
(
SELECT [key], value FROM OPENJSON
(
CONCAT
(
N'["',
REPLACE(STRING_ESCAPE(#List, 'JSON'),
#Delimiter, N'","'),
N'"]')
) AS x
);
Then if you're after the 3rd-last element in the string, you can just reverse before parsing, and then reverse again after parsing. e.g.
CREATE TABLE #f(ID int, FullFilePath nvarchar(4000));
INSERT #f VALUES
(1,N'Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\Oracle\2022-05-04\MSudaitemlov_20220503'),
(2,N'Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OracleABC\2022-05-04\FDERDMSudaitemlov_20220503'),
(3,N'Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OCSBAGF\2022-05-04\AASSSMSudaitemlov_20220503');
DECLARE #ElementOfInterest int = 3;
SELECT REVERSE(value)
FROM #f CROSS APPLY dbo.SplitOrdered_JSON(REVERSE(FullFilePath), N'\')
WHERE [key] = #ElementOfInterest - 1;
Example db<>fiddle

Here is another solution for a complete coverage.
It will work starting from SQL Server 2012 onwards.
It is using XML and XQuery for tokenization. No need in any User-Defined-Function (UDF).
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, FullFilePath nvarchar(4000));
INSERT INTO #tbl (FullFilePath) VALUES
(N'Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\Oracle\2022-05-04\MSudaitemlov_20220503'),
(N'Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OracleABC\2022-05-04\FDERDMSudaitemlov_20220503'),
(N'Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OCSBAGF\2022-05-04\AASSSMSudaitemlov_20220503');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = '\'
, #token int = 8;
SELECT t.*
, c.value('(/root/r[sql:variable("#token")]/text())[1]', 'NVARCHAR(20)')
FROM #tbl AS t
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(FullFilePath, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t1(c);
Output
+----+--------------------------------------------------------------------------------------------------------+------------------+
| ID | FullFilePath | (No column name) |
+----+--------------------------------------------------------------------------------------------------------+------------------+
| 1 | Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\Oracle\2022-05-04\MSudaitemlov_20220503 | Oracle |
| 2 | Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OracleABC\2022-05-04\FDERDMSudaitemlov_20220503 | OracleABC |
| 3 | Y:\dfs-dc-01\Split\Retail\Kroger\Kroger\FTP-FromClient\OCSBAGF\2022-05-04\AASSSMSudaitemlov_20220503 | OCSBAGF |
+----+--------------------------------------------------------------------------------------------------------+------------------+

Related

How do I enable ordinals from the STRING_SPLIT function in MSSQL

I'm trying to use the STRING_SPLIT function in Microsoft SQL Server 2019. The function works, if I only put in two arguments, but since I want to extract a specific element from the string, I would like to enable ordinals.
When I add the third argument to the STRING_SPLIT function it returns
Msg 8144, Level 16, State 3, Line 5 Procedure or function STRING_SPLIT
has too many arguments specified.
I don't understand what I'm doing wrong, since hovering over the STRING_SPLIT function clearly states that the function can take a third argument as an int.
My SQL code is as follows
SELECT *
FROM STRING_SPLIT('[Control Structure].Root.NP_02.ABC01_02_03.Applications.Prototype.Control Modules.ABC060V.ABC060VXFR2','.',1)
WHERE ORDINAL = 4
You can't enable it, since it is not available in SQL Server 2019 (and is almost certainly not going to be back-ported there).
The problem is that SSMS has IntelliSense / tooltips coded without conditional logic based on version, and the code is ahead of the engine. Currently the functionality is only available in Azure SQL Database, Managed Instance, and Synapse.
From the documentation:
The enable_ordinal argument and ordinal output column are currently only supported in Azure SQL Database, Azure SQL Managed Instance, and Azure Synapse Analytics (serverless SQL pool only).
Some more background:
Trusting STRING_SPLIT() order in Azure SQL Database
What you can do instead is create your own inline table-valued UDF that provides the same type of ordinal output (and make it return the same output as STRING_SPLIT to make it easy to change later). There are many variations on this; here's one:
CREATE FUNCTION dbo.SplitStrings_Ordered
(
#List nvarchar(max),
#Delimiter nvarchar(255)
)
RETURNS TABLE
AS
RETURN (SELECT value = Item ,
ordinal = ROW_NUMBER() OVER (ORDER BY Number),
FROM (SELECT Number, Item = SUBSTRING(#List, Number,
CHARINDEX(#Delimiter, #List + #Delimiter, Number) - Number)
FROM (SELECT ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1 CROSS JOIN sys.all_objects AS s2) AS n(Number)
WHERE Number <= CONVERT(INT, LEN(#List))
AND SUBSTRING(#Delimiter + #List, Number, LEN(#Delimiter)) = #Delimiter
) AS y);
GO
Another simpler way would be to use JSON, which I forgot I even wrote recently in this tip:
CREATE FUNCTION dbo.SplitStrings_Ordered
(
#List nvarchar(max),
#Delimiter nchar(1)
)
RETURNS table WITH SCHEMABINDING
AS
RETURN
(
SELECT value, ordinal = [key]
FROM OPENJSON(N'["' + REPLACE(#List, #Delimiter, N'","') + N'"]') AS x
);
GO
Also, if you're just trying to get the last ordinal in a (1-)4-part name and each part is <= 128 characters, you can use PARSENAME():
DECLARE #str nvarchar(512) = N'here is one.here is two.and three.and four';
SELECT p1 = PARSENAME(#str, 4),
p2 = PARSENAME(#str, 3),
p3 = PARSENAME(#str, 2),
p4 = PARSENAME(#str, 1);
Output:
p1
p2
p3
p4
here is one
here is two
and three
and four
Example db<>fiddle
We can sort of cheat our way around ordinal as our order by using the current order instead. Keep in mind that the default order for STRING_SPLIT is non-deterministic:
STRING_SPLIT() reference
The output rows might be in any order. The order is not guaranteed to match the order of the substrings in the input string. You can override the final sort order by using an ORDER BY clause on the SELECT statement, for example, ORDER BY value or ORDER BY ordinal.
DECLARE #object as nvarchar(500) = 'test_string_split_order_string'
select
value,
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS RowNum
from STRING_SPLIT(#object, '_')
SQL Server's XML/XQuery allow to implement very easy tokenization of a string.
XML/XQuery data model is based on ordered sequences.
It allows to retrieve any token based on its position is a string of tokens.
SQL
DECLARE #tokens VARCHAR(256) = '[Control Structure].Root.NP_02.ABC01_02_03.Applications.Prototype.Control Modules.ABC060V.ABC060VXFR2'
, #separator CHAR(1) = '.'
, #pos INT = 4;
SELECT c.value('(/root/r[sql:variable("#pos")]/text())[1]', 'VARCHAR(100)') AS token
FROM (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(#tokens, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t(c);
Output
+-------------+
| token |
+-------------+
| ABC01_02_03 |
+-------------+
yet another way (with ';' as delimiter)
create function dbo.split_string_ord
(
#sentence nvarchar(max)
)
returns table
as
return(
with first_word(ordinal,word,sentence) as (
Select
1 as ordinal,
substring(#sentence+';',1,charindex(';',#sentence+';',1)-1) as word,
substring(#sentence+';',charindex(';',#sentence+';',1)+1,LEN(#sentence+';')-charindex(';',#sentence+';',1)+1) as sentence
union all
Select
ordinal + 1 as ordinal,
substring(sentence,1,charindex(';',sentence,1)-1) as word,
substring(sentence,charindex(';',sentence,1)+1,LEN(sentence)-charindex(';',sentence,1)+1) as sentence
from
first_word
where
sentence != ''
)
Select
ordinal,
word
from
first_word
)
;

Best way to get part of a number from a file name with SQL

I need to extract part of a name from text files in SQL. The file names are always something like this:
AA_123456789_MoreText_MMDDYYYYHHMM.txt
What I need to get is between the first and second underscore. This part varies in character length. Usually they are all numbers, but sometimes includes a letter in it.
Any ideas on the best way to handle this in SQL?
Here is one way to do this using SUBSTRING and CHARINDEX:
SELECT SUBSTRING(filename,
CHARINDEX('_', filename) + 1,
CHARINDEX('_', filename, CHARINDEX('_', filename, 1)) - 2)
FROM yourTable;
Demo
In plain English, the above substring operation says to take all content after the first underscore, but before the second underscore.
Please try the following solution based on tokenization.
This way you can select any token in any position in a string.
SQL
-- DDL and sample data population, start
DECLARE #tbl TABLE (ID INT IDENTITY PRIMARY KEY, tokens VARCHAR(8000));
INSERT INTO #tbl (tokens) VALUES
('AA_123456789_MoreText_MMDDYYYYHHMM.txt'),
('BB_770_MoreText_MMDDYYYYHHMM.xml');
-- DDL and sample data population, end
DECLARE #separator CHAR(1) = '_'
, #position INT = 2;
SELECT ID, tokens
, c.value('(/root/r[sql:variable("#position")]/text())[1]', 'VARCHAR(MAX)') AS Token
FROM #tbl
CROSS APPLY (SELECT TRY_CAST('<root><r><![CDATA[' +
REPLACE(tokens, #separator, ']]></r><r><![CDATA[') +
']]></r></root>' AS XML)) AS t(c);
Output
+----+----------------------------------------+-----------+
| ID | tokens | Token |
+----+----------------------------------------+-----------+
| 1 | AA_123456789_MoreText_MMDDYYYYHHMM.txt | 123456789 |
| 2 | BB_770_MoreText_MMDDYYYYHHMM.xml | 770 |
+----+----------------------------------------+-----------+

Drop table in Stored Procedure not working properly?

I have a stored procedure which drops a table if it exists, then it re-creates the table & fills it with relevant data, a friend of mine has about the same code, the only real difference is in the column headers for the table.
As an illustration, here's how mine looks (not really, just a representation).
+----+-----+-----+--------+
| ID | Foo | Bar | Number |
+----+-----+-----+--------+
| 1 | x | x | 0 |
| 2 | x | x | 1 |
+----+-----+-----+--------+
And here's what his might look like
+----+--------+--------+-----+--------+
| ID | BarFoo | FooBar | Num | Suffix |
+----+--------+--------+-----+--------+
| 1 | x | x | 0 | a |
| 2 | x | x | 1 | b |
+----+--------+--------+-----+--------+
Again, these are merely representations of the situation.
As this is to be a school assignment, the teacher will be creating & executing both SP's, however when creating the SP after using another, I get this error:
Msg 207, Level 16, State 1, Procedure XYZ, Line 59
Invalid column name 'Foo'.
Msg 213, Level 16, State 1, Procedure XYZ, Line 61
Column name or number of supplied values does not match table definition.
However, at the start of both stored procedures, we have this:
CREATE PROCEDURE XYZ
AS
BEGIN
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'TABLENAME'
AND xtype = 'u')
DROP TABLE TABLENAME;
From what I understand, this should remove the entire table? Including table/column definitions & data?
The only fix I've found so far, is to either execute the DROP TABLE separately before creating the stored procedure, which won't work for us as it really has to be within the stored procedure.
Help would be much appreciated :)
EDIT: Here's my ACTUAL code, apart from comments, this is exactly how it looks in my script (excluding other code behind it).
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'BerekenStatistiek'
AND xtype = 'p')
DROP PROCEDURE BerekenStatistiek;
GO
CREATE PROCEDURE BerekenStatistiek
#jaar INT=0
AS
BEGIN
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'Statistiek'
AND xtype = 'u')
DROP TABLE Statistiek;
DECLARE #year AS NVARCHAR (4);
SET #year = CONVERT (NVARCHAR (4), #jaar);
SELECT *,
CAST (Kost - Korting + Freight AS MONEY) AS Netto,
'' AS Richting
INTO Statistiek
FROM (SELECT O.Kwartaal,
CAST (SUM(O.Kost) AS MONEY) AS Kost,
CAST (SUM(O.Korting) AS MONEY) AS Korting,
CAST (SUM(O.Freight) AS MONEY) AS Freight
FROM (SELECT CASE
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '0101' AND #year + '0331' THEN 1
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '0401' AND #year + '0630' THEN 2
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '0701' AND #year + '0930' THEN 3
WHEN CONVERT (NVARCHAR (8), OrderDate, 112) BETWEEN #year + '1001' AND #year + '1231' THEN 4
END AS 'Kwartaal',
ROUND(UnitPrice * Quantity, 2) AS Kost,
Round((UnitPrice * Quantity) * Discount, 2) AS Korting,
Freight
FROM Orders AS O
INNER JOIN
OrderDetails AS Od
ON O.OrderID = Od.OrderID
WHERE CONVERT (NVARCHAR (4), OrderDate, 112) = #year) AS O
GROUP BY O.Kwartaal) AS O1;
ALTER TABLE Statistiek ALTER COLUMN Kwartaal INT NOT NULL;
ALTER TABLE Statistiek ALTER COLUMN Richting NVARCHAR (8);
ALTER TABLE Statistiek
ADD PRIMARY KEY (Kwartaal);
...
And here's his code (the insertion of values in the variables are excluded just for readability (his code is a bit more bulky):
IF EXISTS (SELECT name
FROM sysobjects
WHERE name = 'BerekenStatistiek'
AND xtype = 'p')
BEGIN
DROP PROCEDURE BerekenStatistiek;
END
GO
CREATE PROCEDURE BerekenStatistiek
#jaartal INT
AS
BEGIN
DECLARE #huidigkwartaal AS INT = 1;
DECLARE #beginmaand AS INT;
DECLARE #eindmaand AS INT;
DECLARE #vorige_netto_ontvangsten AS MONEY;
IF EXISTS (SELECT *
FROM sysobjects
WHERE name = 'Statistiek'
AND xtype = 'U')
BEGIN
DROP TABLE Statistiek;
END
CREATE TABLE Statistiek
(
kwartaalnummer INT ,
beginmaand INT ,
eindmaand INT ,
orderbedrag MONEY ,
korting MONEY ,
vervoerskost MONEY ,
netto_ontvangsten MONEY ,
stijgend_dalend_gelijk NVARCHAR (10)
);
--Variables get their data here.
INSERT INTO Statistiek (kwartaalnummer, beginmaand, eindmaand, orderbedrag, korting, vervoerskost, netto_ontvangsten, stijgend_dalend_gelijk)
VALUES (#huidigkwartaal, #beginmaand, #eindmaand, #orderbedrag, #korting, #vervoerskost, #netto_ontvangsten, #stijgend_dalend_gelijk);
"however when creating the SP after using another, I get this error" (Emphasis added.)
SQL Server will insist that a stored procedure match the definitions of tables that exist as the time the stored procedure is created. If the table does not exist when the stored procedure is created, SQL Server will assume that a matching table will appear at run time.
create table t (c int)
go
create procedure p as begin
drop table t
select 1 as diff_column_name into t
select diff_colun_name from t
end
results in:
Msg 207, Level 16, State 1, Procedure p, Line 6
Invalid column name 'diff_colun_name'.
Now, drop table t, and the procedure cane be created:
drop table t
go
create procedure p as begin
drop table t
select 1 as diff_column_name into t
select diff_colun_name from t
end
Command(s) completed successfully.
If you can use a different table name, start with that. And, if the table has to exist only for a moment after the proc is executed so that it can be selected from, then create a global temporary table (i.e. table name starts with ## as in ##MyTable).
However, if it is a requirement to use the same table name as your classmate, then the teacher is probably trying to get you to learn about deferred object resolution (i.e. #Shannon's answer) and how to get around it, because outside of learning this, the scenario makes no sense since one would never do such a thing in reality.
Sub-processes (i.e. EXEC and sp_executesql) do not resolve immediately since they aren't executed when creating the stored procedure. So, simplistically, just declare a new NVARCHAR(MAX) variable to hold some Dynamic SQL and put your SELECT statement in there. Use sp_executesql to pass in the #year variable. You are creating a real table so it will survive beyond the subprocess ending and then the ALTER TABLE statement will work.
Additional notes:
You don't really need the ALTER statement to set the datatype of the [Richting] field. Just tell SQL Server what the type is in your SELECT statement:
CONVERT(NVARCHAR(8), '') AS [Richting]
You don't really want to do CONVERT(NVARCHAR(8), OrderDate, 112) to compare to a value as it invalidates the use of any indexes that might be on [OrderDate]. Instead, construct a date value from the strings and convert that to a DATETIME or DATE (i.e. CONVERT(DATETIME, #year + '0101')).
To better understand this issue, please read Sargability: Why %string% Is Slow, and at least the first link at the bottom, which is: What makes a SQL statement sargable?
You don't really want to convert the OrderDate field to NVARCHAR(4) just to compare the year, for the same reason as just mentioned in the above point. At the very least using the YEAR() function would be more direct. But if you want to make sure indexes can be used, you can't put a function on the field. But you only want the year. So isn't the year the same as BETWEEN #Year + '0101' AND #Year + '1231'? ;-)
Interestingly enough, the first example in the accepted answer in the "What makes a SQL statement sargable?" S.O. question linked in the previous bullet is exactly what I am recommending here :).
For I can understand, the wrong queries are the inserts, because the engine can't find correct table structure, check if the inserts have the same structure of your second table example. Dont forget to check the USE at the beginning of the script, maybe you are using a different db, this can happen :).
In the last bit of code, you are having
AND xtype = 'U'
If your collation is case sensitive, the drop is not taking place and thus the error.

Sql server using variable in pivot query

I have a returned string from a query that reads:
+----------------------+
| returnquerystring |
+----------------------+
| exam1,exam2,exam3 |
+----------------------+
I am using this returned string as column names in a pivot query.
select * from (select score,exam from table1) x
pivot ( max(score) for exam in (exam1,exam2,exam3)
This query works giving me
+-------------+-----------+-----------+
| exam1 | exam2 | exam3 |
+-------------+-----------+-----------+
| 10 | 20 | 30 |
+-------------+-----------+-----------+
However I have not been able to get the pivot "in" statement to use anything but the hard coded values of exam1,exam2,exam3. For example I have used SSMS and created a query that successfully puts exam1,exam2,exam3 into #var1. However #var1 will throws and error when used in place of exam1,exam2,exam3.
declare #var1 varchar(100)
select #var1 = value from table
select * from (select score,exam from table1) x
pivot ( max(score) for exam in (#var1)
Incorrect syntax near '#var1'.
To verify that I was doing it correctly I did this and it worked.
declare #var1 int
select top 1 #var1 = id from name
select * from name where id = #var1
This provided the data row for id 1 on the name table with no error.
I have noticed in my experiments that (exam1,exam2,exam3) cannot be ('exam1,exam2,exam3') with the quotes.
I am using ColdFusion CFSCRIPT and it does appear that the single quotes are getting into the query so I tried various tests with ColdFusion functions to remove them with no success.
So I tried using the SQL Server function 'replace' around the #var1 and that throws an error about syntax at replace.
This is when I tried using an example like above in SSMS and still got errors. So by removing ColdFusion from the equation it still does not work. My thought was to send the whole declare through pivot as a query to avoid ColdFusion issues but it does not work in SSMS.
I am using SQL SERVER 8 and SSMS 11.
Any ideas on how to make this work?
examColumns = exam1,exam2,exam3
public any function qryExamScores(string examColumns) {
thisQry = new Query();
thisQry.setName("returnqry");
thisQry.setDatasource(application.datasource);
thisQry.addParam(name="columnNames",value=arguments.examColumns,cfsqltype="cf_sql_varchar");
result = thisQry.execute(sql="
select * from
(select id,score,exam
from table
where value1 = 'XXXXX'
and value2 = '11111') x
pivot
(
max(score) for exam in (:columnNames)
) p
");
returnqry = result.getResult();
return returnqry;
}
You need to use Dynamic SQL to use the value of variable(#var1) inside Pivot
declare #var1 varchar(100)='',#sql nvarchar(max)
select top 1 #var1 = value from table
set #sql = 'select * from (select score,exam from table1) x
pivot ( max(score) for exam in (['+#var1+'])) piv'
exec sp_executesql #sql
If you want to have more then one value in pivot columns use this.
SELECT #var1 += '[' + Isnull(CONVERT(VARCHAR(50), value), '') + '],'
FROM table
SELECT #var1 = LEFT(#var1, Len(#var) - 1)
SET #sql = 'select * from (select score,exam from table1) x
pivot ( max(score) for exam in (' + #var1 + ')) piv'
EXEC Sp_executesql #sql
passing exam1,exam2,exam3 as a param varchar as :parametervalue
Queryparam (or bind variables) can only be used on literals. Since "exam1,exam2,exam3" are being used as column names in this specific query, you cannot apply queryparam to them. When you do that, you are telling the database those values are simple strings. That causes an error because pivot expects object names, not strings.
Remove the queryparam and the query will work as expected. However, obviously that may expose your database to sql injection (depending on the source of columnNames). The same applies to using any dynamic SQL (exec, sp_executesql, ...). So be sure to fully validate the input before implementing this approach.
...
// build pivot statement with dynamic column names
columnNames = "exam1,exam2,exam3";
sqlString = "SELECT *
FROM (
SELECT score,exam
FROM table1
) x
PIVOT
(
MAX(score) FOR exam IN ("& columnNames &")
)
AS pvt ";
result = qry.execute( sql=sqlString ).getResult();
writeDump( result );
Edit:
Also, you should probably enclose the column names in brackets to avoid syntax errors if the values contain spaces, or other invalid characters for column names.
"[exam1],[exam2],[exam3]";

SQL Server split CSV into multiple rows

I realize this question has been asked before, but I can't get it to work for some reason.
I'm using the split function from this SQL Team thread (second post) and the following queries.
--This query converts the interests field from text to varchar
select
cp.id
,cast(cp.interests as varchar(100)) as interests
into #client_profile_temp
from
client_profile cp
--This query is supposed to split the csv ("Golf","food") into multiple rows
select
cpt.id
,split.data
from
#client_profile_temp cpt
cross apply dbo.split(
cpt.interests, ',') as split <--Error is on this line
However I'm getting an
Incorrect syntax near '.'
error where I've marked above.
In the end, I want
ID INTERESTS
000CT00002UA "Golf","food"
to be
ID INTERESTS
000CT00002UA "Golf"
000CT00002UA "food"
I'm using SQL Server 2008 and basing my answer on this StackOverflow question. I'm fairly new to SQL so any other words of wisdom would be appreciated as well.
TABLE
x-----------------x--------------------x
| ID | INTERESTS |
x-----------------x--------------------x
| 000CT00002UA | Golf,food |
| 000CT12303CB | Cricket,Bat |
x------x----------x--------------------x
METHOD 1 : Using XML format
SELECT ID,Split.a.value('.', 'VARCHAR(100)') 'INTERESTS'
FROM
(
-- To change ',' to any other delimeter, just change ',' before '</M><M>' to your desired one
SELECT ID, CAST ('<M>' + REPLACE(INTERESTS, ',', '</M><M>') + '</M>' AS XML) AS Data
FROM TEMP
) AS A
CROSS APPLY Data.nodes ('/M') AS Split(a)
SQL FIDDLE
METHOD 2 : Using function dbo.Split
SELECT a.ID, b.items
FROM #TEMP a
CROSS APPLY dbo.Split(a.INTERESTS, ',') b
SQL FIDDLE
And dbo.Split function is here.
CREATE FUNCTION [dbo].[Split](#String varchar(8000), #Delimiter char(1))
returns #temptable TABLE (items varchar(8000))
as
begin
declare #idx int
declare #slice varchar(8000)
select #idx = 1
if len(#String)<1 or #String is null return
while #idx!= 0
begin
set #idx = charindex(#Delimiter,#String)
if #idx!=0
set #slice = left(#String,#idx - 1)
else
set #slice = #String
if(len(#slice)>0)
insert into #temptable(Items) values(#slice)
set #String = right(#String,len(#String) - #idx)
if len(#String) = 0 break
end
return
end
FINAL RESULT
from
#client_profile_temp cpt
cross apply dbo.split(
#client_profile_temp.interests, ',') as split <--Error is on this line
I think the explicit naming of #client_profile_temp after you gave it an alias is a problem, try making that last line:
cpt.interests, ',') as split <--Error is on this line
EDIT You say
I made this change and it didn't change anything
Try pasting the code below (into a new SSMS window)
create table #client_profile_temp
(id int,
interests varchar(500))
insert into #client_profile_temp
values
(5, 'Vodka,Potassium,Trigo'),
(6, 'Mazda,Boeing,Alcoa')
select
cpt.id
,split.data
from
#client_profile_temp cpt
cross apply dbo.split(cpt.interests, ',') as split
See if it works as you expect; I'm using sql server 2008 and that works for me to get the kind of results I think you want.
Any chance when you say "I made the change", you just changed a stored procedure but haven't run it, or changed a script that creates a stored procedure, and haven't run that, something along those lines? As I say, it seems to work for me.
As this is old, it seems the following works in SQL Azure (as of 3/2022)
The big changes being split.value instead of .data or .items as shown above; no as after the function, and lastly string_split is the method.
select Id, split.value
from #reportTmp03 rpt
cross apply string_split(SelectedProductIds, ',') split
Try this:
--This query is supposed to split the csv ("Golf","food") into multiple rows
select
cpt.id
,split.data
from
#client_profile_temp cpt
cross apply dbo.split(cpt.interests, ',') as split <--Error is on this line
You must use table alias instead of table name as soon as you define it.

Resources