0

I'm trying to call a string splitting function, the one below:

CREATE FUNCTION SplitStrings_XML
(
  @List       NVARCHAR(MAX),
  @Delimiter  NVARCHAR(255)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
  RETURN 
  (  
    SELECT Item = y.i.value('(./text())[1]', 'nvarchar(4000)')
    FROM 
  ( 
    SELECT x = CONVERT(XML, '<i>' 
      + REPLACE(@List, @Delimiter, '</i><i>') 
      + '</i>').query('.')
  ) AS a CROSS APPLY x.nodes('i') AS y(i)
);

Within a stored procedure, the one below:

CREATE PROCEDURE spSavedPlaces
@GuidList NVARCHAR(MAX)
AS
BEGIN

  SET NOCOUNT ON;

  INSERT INTO OutputTable(
    SavedPlaceId,
    [Name],
    IsOpen
  )
  SELECT
    SavedPlaceId,
    [Name],
    IsOpen
  FROM (UPDATE P
          SET
            IsOpen = 1

        OUTPUT
          INSERTED.SavedPlaceId,
          INSERTED.[Name],
          INSERTED.IsOpen

       FROM SavedPlace AS P

       -- WHERE P.SavedPlaceId IN 'the table returned by the SplitStrings_XML function'
  
  ) AS NDML -- End of nested DML

END

The stored procedure takes a list of Guids as a string, the function turns it into a list of Guids.

However, I don't know where to call the function inside of the stored procedure. It should be used inside the nested dml or the outer FROM but I don't know if I should call it before the update, or before the FROM SavedPlace AS P in the inner query, I tried before the update but it doesn't seem to work and I have no idea where to call it from in order for the inner WHERE to be able to filter from the outputted Guid table, returned by the function.

Here's a db fiddle to be able to test the function and the stored procedure

  • FROM SavedPlace as P INNER JOIN SplitStrings_XML(@GuidList) g ON g.Item = p.PlaceID – siggemannen May 15 '23 at 21:09
  • Looking at your function I would suggest some reading [such as this](https://www.sqlservercentral.com/articles/tally-oh-an-improved-sql-8k-%E2%80%9Ccsv-splitter%E2%80%9D-function) – Stu May 15 '23 at 21:11

1 Answers1

1

You just need to use it inside a subquery.

WHERE P.SavedPlaceId IN (
    SELECT CAST(g.Item AS uniqueidentifier) FROM dbo.SplitString_XML(@GuidList) g
)

You could equally use the STRING_SPLIT function, which is much more efficient.

If the parameters are guaranteed unique then you could also do a join

JOIN dbo.SplitString_XML(@GuidList) g
  ON CAST(g.Item AS uniqueidentifier) = P.SavedPlaceId

Having said that, you should really use a Table Valued Parameter instead.

First declare a table type (note the inline primary key)

CREATE TYPE dbo.GuidList AS TABLE (Id uniqueidentifier PRIMARY KEY);

Then use it like a table

CREATE PROCEDURE spSavedPlaces
  @GuidList dbo.GuidList READONLY
AS
SET NOCOUNT ON;

UPDATE P
SET
  IsOpen = 1

    OUTPUT
        INSERTED.SavedPlaceId,
        INSERTED.Name,
        INSERTED.IsOpen
    INTO OutputTable(
        SavedPlaceId,
        Name,
        IsOpen
    )
FROM SavedPlace AS P
WHERE P.SavedPlaceId IN (
    SELECT g.Id FROM @GuidList g
);

Normally you would pass a TVP from a client application using whatever driver and interface is provided in that language.

If you want to do it in T-SQL from SSMS, you do it like this

DECLARE @GuidList dbo.GuidList;
INSERT @GuidList (Id) VALUES
('b5756fbe-f484-44e5-adae-05d8f46a4151'),
('e18ea843-f2bc-4e5c-900e-09e4722d343c'),
('315472ae-0e88-49d3-a8bc-97d4ca550509');

EXEC spSavedPlaces @GuidList = @GuidList;
Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • As usual, thank you Charlie. I tried it and when I run `EXEC spSavedPlaces @GuidList = 'b5756fbe-f484-44e5-adae-05d8f46a4151,e18ea843-f2bc-4e5c-900e-09e4722d343c,315472ae-0e88-49d3-a8bc-97d4ca550509';` I get the following error: "`Operand type clash: varchar is incompatible with dbo.GuidList`". How can I pass a list of Guids with the TVP way ? – CodingIsFunYouShouldTryIt May 15 '23 at 22:40
  • See latest edit – Charlieface May 15 '23 at 22:55
  • I see. So when I call this code from an API, how should the Guids be sent to SQL Server and inserted to @GuidList ? This is why I used the method that would split the string because I expected an API to call this stored procedure. I should've mentioned that. Should I use both the string splitter and the TVP ? – CodingIsFunYouShouldTryIt May 15 '23 at 23:48
  • Depends on what client language you are using, you haven't said. https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql/table-valued-parameters https://github.com/mkleehammer/pyodbc/wiki/Working-with-Table-Valued-Parameters-(TVPs) https://learn.microsoft.com/en-us/sql/relational-databases/native-client-odbc-how-to/use-table-valued-parameters-odbc?view=sql-server-ver16 for example. Each language has its own way of passing them through, and none of them involve splitting strings. No do not use the string splitter, use just a TVP. – Charlieface May 15 '23 at 23:51
  • Perfect, I'll read through it before I answer, I use C# but I'll check through your articles. – CodingIsFunYouShouldTryIt May 16 '23 at 00:11
  • See also this answer https://stackoverflow.com/a/66067309/14868997 and this https://stackoverflow.com/a/70530476/14868997 – Charlieface May 16 '23 at 00:14
  • So I had time to read the articles, I have two questions: 1- Do I need to recreate the table schema in code by using the `DataTable.Columns.Add()` ( I know I just need the name but is that the use of this function ) ? Would that mean in my case that since the table type only has one column I just need to do `dataTable.Columns.Add("Id", typeof(Guid));` ? 2- Should I use the `SqlParameter(StoredProcedureName, connection)` or `SqlParameter(TableTypeVariableName, connection)` in my case ? I guess I won't need to create a sql command string ( `INSERT` etc ) since the SP takes care of that right ? – CodingIsFunYouShouldTryIt May 16 '23 at 18:55
  • 1. Yes I think so, otherwise you get implicit conversions from `string` 2. `new SqlParameter("@GuidListParam", SqlDbType.Structured) { TypeName = "dbo.GuidList", Value = YourDataTable }` But see also this answer https://stackoverflow.com/a/72925996/14868997 where it shows how to do it with a streaming `IEnumerable` 3. Yes, if you have a Proc then you can use just the name of the Proc and `CommandType.StoredProcedure` see https://stackoverflow.com/a/9267701/14868997 – Charlieface May 16 '23 at 20:20
  • Thank you so much Charlie, and last question, should the table type be created within the SP or as part of the DB schema creation script ? – CodingIsFunYouShouldTryIt May 17 '23 at 18:16
  • No not in the proc, it's part of the schema. The table type is *used* in the declaration of the procedure parameter `@GuidListParam dbo.GuidList READONLY` – Charlieface May 17 '23 at 19:04