Conditional ON in a stored procedure with OPENJSON - sql

I have this stored procedure that inserts data into a table from an API string that is passed to the OPENJSON function. This API though, sometimes renders a different key name according to the length of the value.
For instance:
{
{
"id":"1",
"shortName": Alex
},
"id":"2",
"longName": Alexander
}
}
Stored procedure:
CREATE PROCEDURE dbo.uspAddToTable
#json NVARCHAR(MAX)
AS
BEGIN
INSERT INTO dbo.MyTable (id, name)
SELECT id, name
FROM OPENJSON(#json)
WITH
(id integer '$.id',
name varchar(100) '$.shortName' /* here how do I do: OR '$.longName' if '$.shortName' does not exist */
) tbl
Is this possible in the stored procedure to take one or the other key value name depending if it can't find the default shortName to longName?

What you need to do, instead, is return both columns in your OPENJSON call, and then use COALESCE or ISNULL in your SELECT to return the non-NULL value:
CREATE PROCEDURE dbo.uspAddToTable #json nvarchar(MAX)
AS
BEGIN
INSERT INTO dbo.MyTable (id,name)
SELECT id,
ISNULL(shortName,longName)
FROM OPENJSON(#json)
WITH (id int,
shortName varchar(100),
longName varchar(100)) OJ;
END;
I assume here that one of the other will be NULL or that shortName is the "prioritised" value.

Related

How to pass a list of strings as a parameter in a stored procedure in SQL?

How to pass a list of strings as a parameter in a stored procedure in SQL?
Like for example if I have a stored procedure proc_aggregation that takes two parameters #Prod_Desc of varchar data type and another parameter #Prod_Code as a list of strings (Eg : #Prod_Code = ('12012', '12011', '12014')).
You will have to use table valued parameters
Define new type as follows
CREATE TYPE Prod_Code AS TABLE ( ProductCode varchar );
then use this type in your stored procedure
create procedure [dbo].[proc_aggregation]
#Prod_Code Prod_Code READONLY,
#Prod_Desc varchar (30)
as
......
Now before calling the stored procedure fill the table
declare #PC Prod_Code;
insert #PC VALUES ('12012'), ('12011'), ('12014')
Now Call the sp like this
EXEC dbo.proc_aggregation #PC, #Prod_Desc;
You can pass this data as varchar(max) parameter and then parse this string using string_split
Declare #Prod_Code VarChar(max) = '12012,12011,12014'
Select value As [Values]
From String_Split(#Prod_Code, ',')
or pass this data as a json array and parse this text using OPENJSON
Declare #Prod_Code VarChar(max) = '[12012, 12011, 12014]'
Select value As [Values]
From OPENJSON(#Prod_Code)
Values
12012
12011
12014
As an interesting fact, let'us compare performances of the different solutions... STRING, JSON, XML.
SET NOCOUNT ON;
GO
CREATE TABLE #test_list (id int identity primary key, style varchar(16), VAL VARCHAR(64), DT DATETIME2)
GO
-- JSON test
INSERT INTO #test_list VALUES ('JSON', '[12012, 12011, 12014]', SYSDATETIME());
GO
Select value As [Values]
From OPENJSON((SELECT VAL FROM #test_list WHERE style = 'JSON'));
GO 10000
-- STRING test
INSERT INTO #test_list VALUES ('STRING', '12012,12011,12014', SYSDATETIME());
GO
Select value As [Values]
From String_Split((SELECT VAL FROM #test_list WHERE style = 'STRING'), ',')
GO 10000
-- XML test
INSERT INTO #test_list VALUES ('XML', '<i v="12012" /><i v="12011" /><i v="12014" />', SYSDATETIME());
GO
WITH TX AS
(SELECT CAST(VAL AS xml) AS x
FROM #test_list AS i
WHERE style = 'XML')
Select y.value('(#v)[1]', 'varchar(16)')
From TX
CROSS APPLY x.nodes('/i') AS T(y);
GO 10000
-- final
INSERT INTO #test_list VALUES ('END', NULL, SYSDATETIME());
Executing the test without any dataset returned... (SSMS menu : Query/Options/Results/Grid/Ignore results after execution...)
Computing the execution time, is done by this query :
SELECT begins.style,
DATEDIFF(millisecond, begins.DT, ends.DT) AS DurationMS
FROM #test_list AS begins
JOIN #test_list AS ends ON begins.id = ends.id - 1
ORDER BY 2;
It is clear that string solution wins :
style DurationMS
---------------- -----------
STRING 2977
JSON 3358
XML 4242

OPENJSON stored procedure [1,2,3,4,5]

I have this data passed from my repo [1,2,3,4,5], it is a List<int> converted to a json string.
Now I want to insert it into the database. How should I write my query?
INSERT INTO CommitteeMember
SELECT
#committeeID,
* // how to call it?
FROM
OPENJSON(#membersJson) AS json
If I understand you correctly you want something like below:
CREATE TABLE CommitteeMember(committeeID INT, memberID INT);
DECLARE #membersJSON NVARCHAR(MAX) = '[1,2,3,4,5]';
DECLARE #commiteeID INT = 10;
INSERT INTO CommitteeMember(committeeId, memberId)
SELECT #commiteeID AS committeeId,value AS memberId
FROM OPENJSON(#membersJSON);
SELECT *
FROM CommitteeMember;
Dbfiddle Demo

Returning or outputting a #tableVariable in SQL

Is it possible to return or output a #tableVariable in SQL Server?
For example for the following stored procedure, how do I return the #TSV table variable?
ALTER PROCEDURE MyStoredProdecure
#Parameter1 INT,
#Parameter2 INT
AS
BEGIN
DECLARE #TSV TABLE
(
Transition_Set_Variable_ID INT,
Passed BIT
)
INSERT INTO #TSV
{ some data }
END
You cannot do it directly. Table variables are valid for READONLY input.
If you have no other data being returned from the stored procedure, you can select from the #TSV at the end and have the caller capture the output, e.g.
ALTER PROCEDURE MyStoredProdecure
#Parameter1 INT,
#Parameter2 INT
AS
BEGIN
DECLARE #TSV TABLE
(
Transition_Set_Variable_ID INT,
Passed BIT
)
INSERT INTO #TSV
{ some data }
SELECT * FROM #TSV
END
Caller
DECLARE #outerTSV TABLE
(
Transition_Set_Variable_ID INT,
Passed BIT
);
insert into #outerTSV
exec MyStoredProdecure 1, 2;
Alternatively, if the SP is really as simple as you showed, turn it into a table valued function instead.
No, but you can write a table valued function that returns a table.
create function MyTVF
#Parameter1 INT,
#Parameter2 INT
returns #tsv table
(
Transition_Set_Variable_ID INT,
Passed BIT
)
AS
BEGIN
INSERT INTO #TSV
{ some data }
return
END
Table Valued Parameters can only be used for input only, not output.
Depending on what your end goal is, here are some options:
change the sproc to a table-valued function to return a TABLE, that can then be used inline in another statement
simply SELECT the data from the #TSV table var at the end of your sproc
return an XML OUTPUT parameter (get a grubby feeling suggesting this, but just to highlight one way to return multiple rows actually using an OUTPUT parameter)
If you go for a Table Valued Function, ideally create an inline one if it is simple as it looks in your case:
e.g.
CREATE FUNCTION dbo.Func()
RETURNS TABLE
AS
RETURN
(
SELECT Something
FROM Somewhere
WHERE x = 1
)

How to run SQL Server stored procedure query for each value of a CSV

I am having following stored procedure in my SQL Server 2008 R2 database
ALTER PROCEDURE [dbo].[usp_send_email]
#pStatus Int Out,
#pEMailId Int Out,
#pSenderUserName varchar(MAX),
#pReceivers VarChar(50), **-- this column can have csv values**
#pSub NVarChar(100),
#pCon NVarchar(MAX),
#pHasAttachments Bit
AS
BEGIN
--SET NOCOUNT ON;
Insert Into MessagingMessage
(
CreatedBy,
[Subject],
Body,
HasAttachments
)
Values
(
#pSenderUserName,
#pSub,
#pCon,
#pHasAttachments
)
SET #pEMailId = SCOPE_IDENTITY()
Insert Into MessagingMessageReceipient
(
MessageId,
ReceipientId,
ReceipientType
)
Values
(
#pEMailId,
#pReceivers,
1
)
SET #pStatus = SCOPE_IDENTITY()
END
In above code I want to run the first statement only one time but second insert statements in loop for each comma separated username.CSV value coming as a parameter is already validated by C# code so no need to validate it.
Using this link: how to split and insert CSV data into a new table in single statement?, there is a function you can use to parse csv to to table and I have used it in the code below. Try the following
Insert Into MessagingMessageReceipient
(
MessageId,
ReceipientId,
ReceipientType
)
SELECT
#pEMailId,
csv.Part, -- each #pReceiver
1
FROM
dbo.inline_split_me(',',#pReceivers) csv
And the function is copied below
CREATE FUNCTION inline_split_me (#SplitOn char(1),#String varchar(7998))
RETURNS TABLE AS
RETURN (WITH SplitSting AS
(SELECT
LEFT(#String,CHARINDEX(#SplitOn,#String)-1) AS Part
,RIGHT(#String,LEN(#String)-CHARINDEX(#SplitOn,#String)) AS Remainder
WHERE #String IS NOT NULL AND CHARINDEX(#SplitOn,#String)>0
UNION ALL
SELECT
LEFT(Remainder,CHARINDEX(#SplitOn,Remainder)-1)
,RIGHT(Remainder,LEN(Remainder)-CHARINDEX(#SplitOn,Remainder))
FROM SplitSting
WHERE Remainder IS NOT NULL AND CHARINDEX(#SplitOn,Remainder)>0
UNION ALL
SELECT
Remainder,null
FROM SplitSting
WHERE Remainder IS NOT NULL AND CHARINDEX(#SplitOn,Remainder)=0
)
SELECT Part FROM SplitSting
)

Use stored procedure output parameter

ALTER PROCEDURE dbo.StoredProcedure8
#emp_code bigint,
#co_id bigint,
#p decimal(8,2) output
AS
SELECT #p = (select sum(tran_value) from emp_ded_ben_trans where emp_code=#emp_code and co_id=#co_id and period_flg=2 and tax_flg=0)
RETURN
To call that sproc and retrieve the output parameter, you do (e.g.):
DECLARE #p DECIMAL(8,2)
EXECUTE dbo.StoredProcedure8 123, 456, #p OUTPUT
-- #p now contains the output value
Update:
You don't need to use RETURN - you are right in that a RETURN can only return an INTEGER. But a return value is different to an OUTPUT parameter which is what you are actually using.
i.e. to get a RETURN value from a sproc, is different syntax:
DECLARE #Result INTEGER
EXECUTE #Result = SomeSproc
Stored procedures aren't made to "return values" - that's what you have stored functions for.
CREATE FUNCTION dbo.CalculateSomething
(#emp_code bigint, #co_id bigint)
RETURNS DECIMAL(8, 2)
AS BEGIN
RETURN
(SELECT SUM(tran_value)
FROM dbo.emp_ded_ben_trans
WHERE
emp_code = #emp_code AND co_id = #co_id
AND period_flg = 2 AND tax_flg = 0)
END
You can then call this stored function like this:
SELECT dbo.CalculateSomething(value_for_emp_code, value_for_co_id)
and get back a DECIMAL(8,2) from the calculation.
Stored procedures will return the number of rows affected by their operation - an INT.
If you need to return a value from a stored proc, you need to use the OUTPUT parameter type and use the technique that AdaTheDev shows - you need to grab the output value into a variable.
First you can use not a Stored Procedure by a Function if you have to return a single value basing on input parameters
If your sp return decimal when you need integer - just cast: SELECT (CAST #d AS INT) but this is very dangerous (possible type overflow)
If your Column tran_value is of Decimal Type, the #p will have decimal values after you run the query...
Create Table #test
(ID1 Int,
ID2 Decimal(8,2)
)
Insert into #test Values (1,1.1)
Insert into #test Values (2,2.2)
Insert into #test Values (3,3.3)
Declare #p Decimal(8,2), #intp int
Select #intp = Sum(ID1), #p = Sum(ID2) from #test
Select #intp as IntegerSum, #p as DecimalSum
Drop Table #test
Output
IntegerSum DecimalSum
----------- ---------------------------------------
6 6.60
Note : You need not have to do anything specific to return the value from Stored Procedure through output parameter... Just assign the value to output parameter inside your SP, it will be returned to the caller automatically.
This means your SP is correct even without the return statement