how to create a multi-statement table valued function - sql

I am having a SQL function and I want to return a table, but I'm getting an error:
A RETURN statement with a return value cannot be used in this context.
Here is my query:
ALTER FUNCTION [dbo].[Fnc_GetParentCategories]-- 21
(
#catId int
)
Returns table
As
Begin
Declare #Table table(Id int identity(1,1),Category_Id int,ParentId int);
declare #cid int;
WITH x AS (
SELECT a.Category_Id, a.ParentId
FROM t_Category a
WHERE a.Category_Id=#CatId -- enter dead node walking here
UNION ALL
SELECT b.Category_Id, b.ParentId
FROM t_Category b
JOIN x ON x.Category_Id =b.ParentId
)
insert into #Table select * from x;
return #Table
end
And the error is:
A RETURN statement with a return value cannot be used in this context

Your query is incorrect:
ALTER FUNCTION [dbo].[Fnc_GetParentCategories]-- 21
(
#catId int
)
Returns #Table table
(
Id int identity(1,1),
Category_Id int,
ParentId int
)
AS
Begin
WITH x AS
(
SELECT a.Category_Id, a.ParentId
FROM t_Category a
WHERE a.Category_Id=#CatId -- enter dead node walking here
UNION ALL
SELECT b.Category_Id, b.ParentId
FROM t_Category b
JOIN x ON x.Category_Id =b.ParentId
)
insert into #Table select * from x;
return
end
you declare your table definition just before as

--Transact-SQL Multistatement Table-valued Function Syntax
CREATE FUNCTION [ schema_name. ] function_name
( [ { #parameter_name [ AS ] [ type_schema_name. ] parameter_data_type
[ = default ] [READONLY] }
[ ,...n ]
]
)
RETURNS #return_variable TABLE <table_type_definition>
[ WITH <function_option> [ ,...n ] ]
[ AS ]
BEGIN
function_body
RETURN
END
[ ; ]
Your modified function
ALTER FUNCTION [dbo].[Fnc_GetParentCategories]-- 21
(
#catId int
)
RETURNS #Table TABLE(Id int identity(1,1),Category_Id int,ParentId int)
As
BEGIN
WITH x AS
(
SELECT a.Category_Id, a.ParentId
FROM t_Category a
WHERE a.Category_Id = #CatId -- enter dead node walking here
UNION ALL
SELECT b.Category_Id, b.ParentId
FROM t_Category b
JOIN x ON x.Category_Id = b.ParentId
)
INSERT #Table select * from x;
RETURN
END

Related

sql server, replace chars in string with values in table

how can i replace values in string with values that are in a table?
for example
select *
into #t
from
(
select 'bla'c1,'' c2 union all
select 'table'c1,'TABLE' c2 union all
select 'value'c1,'000' c2 union all
select '...'c1,'' c2
)t1
declare #s nvarchaR(max)='this my string and i want to replace all values that are in table #t'
i have some values in my table and i want to replace C1 with C2 in my string.
the results should be
this my string and i want to replace all 000 that are in TABLE #t
UPDATE:
i solved with a CLR
using System;
using Microsoft.SqlServer.Server;
using System.Data.SqlTypes;
using System.Data.Linq;
namespace ReplaceValues
{
public partial class Functions
{
[SqlFunction
(
//DataAccess = DataAccessKind.Read,
SystemDataAccess = SystemDataAccessKind.Read
)
]
public static string ReplaceValues(string row, string delimitator, string values, string replace/*, bool CaseSensitive*/)
{
//return row;
string[] tmp_values = values.Split(new string[] { delimitator }, StringSplitOptions.None);
string[] tmp_replace = replace.Split(new string[] { delimitator }, StringSplitOptions.None);
row = row.ToUpper();
for (int i = 0; i < Math.Min(tmp_values.Length, tmp_replace.Length); i++)
{
row = row.Replace(tmp_values[i].ToUpper(), tmp_replace[i]);
}
return row;
}
}
}
and then
select *
into #t
from
(
select 'value1'OldValue,'one'NewValue union all
select 'value2'OldValue,'two'NewValue union all
select 'value3'OldValue,'three'NewValue union all
select 'value4'OldValue,'four'NewValue
)t1
select dbo.ReplaceValues(t1.column,'|',t2.v,t2.r)
from MyTable t1
cross apply
(
select dbo.inlineaggr(i1.OldValue,'|',1,1)v,
dbo.inlineaggr(i1.NewValue,'|',1,1)r
from #t i1
)t2
i have to improved it to manage better the case sensitive, but performance are not bad.
(also 'inlineaggr' is a CLR i wrote years ago)
You can do this via recursion. Assuming you have a table of find-replace pairs, you can number the rows and then use recursive cte:
create table #t(c1 nvarchar(100), c2 nvarchar(100));
insert into #t(c1, c2) values
('bla', ''),
('table', 'table'),
('value', '000'),
('...', '');
declare #s nvarchar(max) = 'this my string and i want to replace all values that are in table #t';
with ncte as (
select row_number() over (order by (select null)) as rn, *
from #t
), rcte as (
select rn, replace(#s, c1, c2) as newstr
from ncte
where rn = 1
union all
select ncte.rn, replace(rcte.newstr, ncte.c1, ncte.c2)
from ncte
join rcte on ncte.rn = rcte.rn + 1
)
select *
from rcte
where rn = 4

Json input of a stored Procedure

I have a Stored Procedure with a JSON in input ,Is it possible to use the JSON as input for a stored procedure? how could i do that ?
CREATE PROCEDURE [warm].[stored _table]
(
#Json NVARCHAR(MAX)
)
AS
BEGIN
SET NOCOUNT ON;
BEGIN TRY
BEGIN
WITH JsonToTable AS
(
SELECT * FROM OPENJSON (#Json) WITH (
[type] [nvarchar](100),
[source] [nvarchar](38),
[time] [nvarchar](28),
[ID] [varchar](50) '$.data.ID',
[RegionCode] [varchar](10)'$.data.RegionCode'
[DueDate] [datetime2](7)'$.data.DueDate',
[SchedulStartDate] [datetime2](7)'$.data.SchedulStartDate',
)
)
MERGE [warm].[table] AS TARGET
USING JsonToTable AS SOURCE
ON (TARGET.ID = SOURCE.ID)
WHEN MATCHED THEN
UPDATE SET
TARGET.[RegionCode] = (SOURCE.[RegionCode]
TARGET.[DueDate] = [dbo].[ufn_cast_string_to_date](SOURCE.[DueDate])
,TARGET.[SchedulStartDate] = [dbo].[ufn_cast_string_to_date](SOURCE.[SchedulStartDate])
WHEN NOT MATCHED THEN
INSERT
(
[SourceID]
,[ID]
,[RegionCode]
,[DueDate]
,[SchedulStartDate])
VALUES
(
1
,[ID]
,[RegionCode]
,[dbo].[ufn_cast_string_to_date](SOURCE.[DueDate])
,[dbo].[ufn_cast_string_to_date](SOURCE.[SchedulStartDate])
);
END
END TRY
END
I Want to execute it with the request below :
DECLARE #return_value int
EXEC #return_value = [warm].[usp_upsert_warm_table]
#Json = N'{
"type" : "table",
"source" : "informations",
"time" : "2018-04-05T17:31:00Z",
"id" : "A11-111-111",
"data" : {"
"ID":"123-56",
"RegionCode":"2",
"DueDate":"2020-13-14T10:54:00",
"SchedulStartDate":"2020-12-14T10:54:00"
}'}
I get this Message Error :
JSON text is not properly formatted. Unexpected character '.' is found at position 480.**
This is the Valid input :
DECLARE #return_value int
EXEC #return_value = [warm].[usp_upsert_warm_table]
#Json = N'
{
"type" : "table1",
"source" : "",
"id" : "A111-111-11",
"time" : "2020-12-14 10:54:00",
"data" :{
"ApplicationID":"INFORMATIONS",
"ID":"157faf1657c-100",
"RegionCode":"2",
"DueDate":"2020-12-14 10:54:00",
"SchedulStartDate":"2020-12-14 10:54:00"}'}
For my input date : 2020-12-14 10:54:00 i deleteed the T in the middle
for my S.P : I change the type of My variables from datetime To varchar :
CREATE PROCEDURE [warm].[usp_upsert_warm_table]
...
[ID] [varchar](50) '$.data.ID',
[RegionCode] [varchar](10)'$.data.RegionCode'
[DueDate] [varchar](40)'$.data.DueDate',
[SchedulStartDate] [varchar](10)'$.data.SchedulStartDate',
....

How to set more than a filter over JSON data using JSON_VALUE?

I'm just trying to set a query to get data from a collection of object JSON:
create table test (LINE_SPECS nvarchar(max));
insert into test values (N'
{
"lineName":"GHjr",
"pipeDiameter":"12",
"pipeLength":"52000",
"pressure":"15",
"volume":"107"
},
{
"lineName":"Ks3R",
"pipeDiameter":"9",
"pipeLength":"40000",
"pressure":"15",
"volume":"80"
}
');
Now, as getting lineName of the first object ( lineName : Ghjr) is a success
select
JSON_VALUE(LINE_SPECS, '$.lineName') as line_name
, JSON_VALUE(LINE_SPECS, '$.pipeDiameter') as diameter
from test
WHERE JSON_VALUE(LINE_SPECS, '$.lineName') = 'GHjr'
;
that is not possible when I try to get the second that is "Ks3R" :
select
JSON_VALUE(LINE_SPECS, '$.lineName') as line_name
, JSON_VALUE(LINE_SPECS, '$.pipeDiameter') as diameter
from test
WHERE JSON_VALUE(LINE_SPECS, '$.lineName') = 'Ks3R'
How can I do that ?
Thanks.
First your JSON data isn't valid, it might be an array.
look like this.
create table test (LINE_SPECS nvarchar(max));
insert into test values (N'
[
{
"lineName":"GHjr",
"pipeDiameter":"12",
"pipeLength":"52000",
"pressure":"15",
"volume":"107"
},
{
"lineName":"Ks3R",
"pipeDiameter":"9",
"pipeLength":"40000",
"pressure":"15",
"volume":"80"
}
]');
You can try to use OPENJSON with CROSS APPLY to parse JSON and make it.
select
t2.*
from test t1
CROSS APPLY
OPENJSON(t1.LINE_SPECS)
WITH
(
line_name varchar(MAX) N'$.lineName',
diameter varchar(MAX) N'$.pipeDiameter'
) AS t2
WHERE line_name = 'Ks3R'
sqlfiddle

Parent child hierarchy path without using CTE

Hi I have following tables:
create table Features
(
FeatureId bigint,
FeatureName varchar(255),
ParentId bigint
)
insert into Features values(10, 'Feature 1', 1);
insert into Features values(11, 'Feature 2', 10);
insert into Features values(12, 'Feature 3', 11);
insert into Features values(13, 'Feature 4', 2);
insert into Features values(14, 'Feature 5', 13);
insert into Features values(15, 'Feature 6', 3);
insert into Features values(16, 'Feature 7', 15);
insert into Features values(17, 'Feature 8', 16);
insert into Features values(18, 'Feature 9', 17);
insert into Features values(19, 'Feature 10', 18);
insert into Features values(20, 'Feature 11', 19);
insert into Features values(21, 'Feature 12', 12);
create table Scenarios
(
ScenarioId bigint,
ParentId bigint,
ScenarioTitle varchar(25)
)
insert into Scenarios values(1, 0, 'Scenario 1')
insert into Scenarios values(2, 0, 'Scenario 2')
insert into Scenarios values(3, 0, 'Scenario 3')
Here, a feature can have either another feature as parent or a scenario as parent. For scenario, parent id can either be 0, or another scenario.
I would like to get path of each feature as follows:
FeatureId ParentId FeatureName PathString PathLength
10 1 Feature 1 1 0
11 10 Feature 2 1/10 1
12 11 Feature 3 1/10/11 2
13 2 Feature 4 2 0
14 13 Feature 5 2/13 1
15 3 Feature 6 3 0
16 15 Feature 7 3/15 1
17 16 Feature 8 3/15/16 2
18 17 Feature 9 3/15/16/17 3
19 18 Feature 10 3/15/16/17/18 4
20 19 Feature 11 3/15/16/17/18/19 5
21 12 Feature 12 1/10/11/12 3
Since I would like to collect this result in a temp table for further processing, I tried select into and Azure SQL DW throws Using SELECT INTO statement is not supported in Parallel Data Warehouse. Modify the statement and re-try executing it.
Here is my query (may not be in great shape as I am still figuring out recursive sql)
drop table FeaturesWithPath;
;WITH FeaturePaths (FeatureId, ParentId, FeatureName, PathString)
AS
(
-- Anchor member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, cast(CAST(g.ParentId as nvarchar(max)) as varchar(max)) as PathString
FROM dbo.Features AS g
UNION ALL
-- Recursive member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, PathString + '/' + cast(g.ParentId as varchar(max))
FROM dbo.Features AS g
INNER JOIN FeaturePaths AS gp
ON g.ParentId = gp.FeatureId
)
SELECT FeatureId, ParentId, FeatureName, PathString into FeaturesWithPath FROM FeaturePaths;
--select * from FeaturesWithPath order by FeatureId;
drop table FeaturesWithPathLength;
select *, LEN(PathString) - LEN(REPLACE(PathString, '/', '')) as PathLength into FeaturesWithPathLength from FeaturesWithPath
--select * from FeaturesWithPathLength order by FeatureId
drop table MaxFeaturePathLenghtRowTable;
select * into MaxFeaturePathLenghtRowTable
from FeaturesWithPathLength
where PathLength = (select max(PathLength) from FeaturesWithPathLength as f where f.FeatureId = FeaturesWithPathLength.FeatureId)
or PathLength = (select max(PathLength) from FeaturesWithPathLength as f where f.FeatureId = FeaturesWithPathLength.FeatureId
and PathLength > (select max(PathLength) from FeaturesWithPathLength as f2 where f2.FeatureId = FeaturesWithPathLength.FeatureId));
--select * from MaxFeaturePathLenghtRowTable order by FeatureId
drop table FeaturesPerParentTable
select FeatureId, [value] as NewParentId, FeatureName, COALESCE(NULLIF(SUBSTRING(PathString, 0, CHARINDEX('/', PathString)), ''), [value]) AS ScenarioId into FeaturesPerParentTable
from MaxFeaturePathLenghtRowTable
cross apply STRING_SPLIT (PathString, '/') cs order by FeatureId
select * from FeaturesPerParentTable order by FeatureId;
I tried to convert the CTE to use CTAS which did not work either.
This is how I tried CTAS:
;WITH FeaturePaths (FeatureId, ParentId, FeatureName, PathString)
AS
(
-- Anchor member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, cast(CAST(g.ParentId as nvarchar(max)) as varchar(max)) as PathString
FROM dbo.Features AS g
--WHERE parentId=0
UNION ALL
-- Recursive member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, PathString + '/' + cast(g.ParentId as varchar(max))
FROM dbo.Features AS g
INNER JOIN FeaturePaths AS gp
ON g.ParentId = gp.FeatureId
)
CREATE TABLE #tmp_fct
WITH
(
DISTRIBUTION = ROUND_ROBIN
)
AS
SELECT FeatureId, ParentId, FeatureName, PathString
FROM FeaturePaths;
Now I am wondering if there is a way to get path for each Feature on Azure SQL DW and collect result in to a table.
-- UPDATE --
For solution in SQL see this
Here is solution in C#
void Main()
{
var scenarios = new List<Scenario> {
new Scenario{Id = 1, Title = "Scenario 1", ParentId = 0},
new Scenario{Id = 2, Title = "Scenario 2", ParentId = 0},
new Scenario{Id = 3, Title = "Scenario 3", ParentId = 0},
};
var features = new List<Feature> {
new Feature{Id =10, Title = "Feature 1", ParentId =1},
new Feature{Id =11, Title = "Feature 2", ParentId =10},
new Feature{Id =12, Title = "Feature 3", ParentId =11},
new Feature{Id =13, Title = "Feature 4", ParentId =2},
new Feature{Id =14, Title = "Feature 5", ParentId =13},
new Feature{Id =15, Title = "Feature 6", ParentId =3},
new Feature{Id =16, Title = "Feature 7", ParentId =15},
new Feature{Id =17, Title = "Feature 8", ParentId =16},
new Feature{Id =18, Title = "Feature 9", ParentId =17},
new Feature{Id =19, Title = "Feature 10", ParentId =18},
new Feature{Id =20, Title = "Feature 11", ParentId =19},
new Feature{Id =21, Title = "Feature 12", ParentId =12}
};
var scenarioIds = new HashSet<long>(scenarios.Select(x => x.Id));
//get path
IList<Feature> withPath = features.Select(x => { x.Path = GetPath(x, features, scenarioIds); return x; }).ToList().Dump("With path");
}
private string GetPath(Feature f, IList<Feature> features, HashSet<long> scenarioIds)
{
if (scenarioIds.Contains(f.ParentId))
{
return f.ParentId.ToString();
}
else
{
var parent = features.First(d => d.Id == f.ParentId);
return GetPath(parent, features, scenarioIds) + "/" + f.ParentId;
}
}
public class Scenario
{
public long Id { get; set; }
public string Title { get; set; }
public long ParentId { get; set; }
}
public class Feature
{
public long Id { get; set; }
public string Title { get; set; }
public long ParentId { get; set; }
public string Path { get; set; } //temp
}
As Azure SQL Data Warehouse does not support recursive CTEs or cursors at this time, you could do this with a good old-fashioned loop, eg:
-- Loop thru Features
DECLARE #counter INT = 1;
-- Insert first record where no parent exists
IF OBJECT_ID('tempdb..#features') IS NOT NULL DROP TABLE #features;
CREATE TABLE #features
WITH
(
DISTRIBUTION = HASH ( FeatureId ),
LOCATION = USER_DB
)
AS
WITH cte AS
(
SELECT 1 AS xlevel, p.FeatureId, p.ParentId, p.FeatureName, CAST( p.ParentId AS VARCHAR(255) ) AS PathString, 0 AS PathLength
FROM dbo.Features p
WHERE NOT EXISTS
(
SELECT *
FROM dbo.Features c
WHERE p.ParentId = c.FeatureId
)
)
SELECT *
FROM cte;
SELECT 'before' s, * FROM #features ORDER BY FeatureId;
-- Loop recursively through the child records
WHILE EXISTS (
SELECT *
FROM #features p
INNER JOIN dbo.features c ON p.FeatureId = c.ParentId
WHERE p.xlevel = #counter
)
BEGIN
-- Insert next level
INSERT INTO #features ( xlevel, FeatureId, ParentId, FeatureName, PathString, PathLength )
SELECT #counter + 1 AS xlevel, c.FeatureId, c.ParentId, c.FeatureName, p.PathString + '/' + CAST( c.ParentId AS VARCHAR(255) ) AS PathString, #counter AS PathLength
FROM #features p
INNER JOIN dbo.features c ON p.FeatureId = c.ParentId
WHERE p.xlevel = #counter;
SET #counter += 1;
-- Loop safety
IF #counter > 99
BEGIN
RAISERROR( 'Too many loops!', 16, 1 )
BREAK
END;
END
SELECT 'after' s, * FROM #features ORDER BY FeatureId;
Full code including setup is available here.
My results:
Hope that helps.
Why not create the FeaturesWithPath table beforehand and insert into it using the following pseudocode?
CREATE TABLE FeaturesWithPath (FeatureId type, ParentId type, FeatureName type, PathString type)
;with FeaturePaths (FeatureId, ParentId, FeatureName, PathString)
AS
(
-- Anchor member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, cast(CAST(g.ParentId as nvarchar(max)) as varchar(max)) as PathString
FROM dbo.Features AS g
UNION ALL
-- Recursive member definition
SELECT g.FeatureId, g.ParentId, g.FeatureName, PathString + '/' + cast(g.ParentId as varchar(max))
FROM dbo.Features AS g
INNER JOIN FeaturePaths AS gp
ON g.ParentId = gp.FeatureId
)
insert FeaturesWithPath (FeatureId, ParentId, FeatureName, PathString)
SELECT FeatureId, ParentId, FeatureName, PathString FROM FeaturePaths;

SQL use return value of function as procedure parameter

Let's define:
create table cities
(
ID int identity,
name varchar(50) unique
)
create function getID (#name varchar(50)) returns int
begin
declare #id int
select #id=ID from cities where name=#name
return #id
end
create procedure addLine
#cityID int
as begin
...
end
Is it possible to execute a procedure with value returned by function, as follows?
exec addLine getID('Warsaw')
exec addLine dbo.getID('Warsaw')
exec addLine (select dbo.getID('Warsaw'))
I tried above and it did not help.
Microsoft says that this is not possible. You can not pass result of a function to procedure as a parameter:
[ { EXEC | EXECUTE } ]
{
...
[ [ #parameter = ] { value
| #variable [ OUTPUT ]
| [ DEFAULT ]
}
]
...
}
[;]
So, only constant values, variables and DEFAULT keyword are permitted.
2 possible ways of doing this are:
1: declare variable
declare #i int = dbo.getID('Warsaw')
exec addLine #i
2: use dynamic query
DECLARE #cmd NVARCHAR(MAX) = 'exec addLine ' + CAST(dbo.getID('Warsaw') AS NVARCHAR(MAX))
EXEC(#cmd)
This will "execute a procedure with value returned by function"
It just won't do it 'inline'
declare #id int
select #id = dbo.getID('Warsaw')
exec addLine #id