How to make this stored procedure use a JSON check? - sql

I am looking at using a stored procedure to help out with a situation of where I have to update/insert about a 1000 records. I was given the suggestion to use MERGE with a table-valued parameter to achieve this but the problem is one of the columns is a JSON string.
ItemsTbl
id -PK
BrandId- int (FK)
LocationId- int (FK)
Field3 - nvarchar(Max) Json string containing a jsonKey called itemNumber
select *
from ItemsTbl
where BrandId = 1
and LocationId = 1
and JSON_VALUE('Field3',$.itemNumber) = 12345
Now the stored procedure (what is pretty much all new to me) looks currently like this:
/* Create a table type. */
CREATE TYPE SourceTableType AS TABLE
( BrandId INT
, LocationId INT
, ItemNumber INT
, ...
);
GO
CREATE PROCEDURE dbo.usp_InsertTvp
#Source SourceTableType READONLY
AS
MERGE INTO Table1 AS Target
USING #Source As Source ON Target.BrandId = Source.BrandId
AND Target.LocationId = Source.LocationId
AND Target.ItemNumber = Source.ItemNumber
WHEN MATCHED THEN
UPDATE SET OtherParam = Source.OtherParam
WHEN NOT MATCHED BY TARGET THEN
INSERT (BrandId, LocationId, ItemNumber, OtherParam)
VALUES (BrandId, LocationId, ItemNumber, OtherParam) ;
The problem is this right now does not seem to account on that ItemNumber is inside a JSON string and not it's own column. So I don't think this would work
Target.ItemNumber = Source.ItemNumber
Also I am guessing SourceTableType would have to take in Field3 as a parameter and then extract it out itself?

I hope I understand you right.
Please try this:
;WITH MergeTarget AS (
SELECT t.BrandId,t.LocationId,t.Field3,JSON_VALUE(t.Field3,'$.itemNumber') AS [ItemNumber],t.OtherParam
FROM Table1 AS t
)
MERGE MergeTarget AS target
USING (
SELECT s.BrandId,s.LocationId,s.ItemNumber,'{"itemNumber":"'+CONVERT(NVARCHAR(255),s.ItemNumber)+'"' AS [Field3],s.OtherParam
FROM #Source AS s
) AS source ON source.BrandId = target.BrandId
AND source.LocationId = target.LocationId
AND source.ItemNumber = target.ItemNumber
WHEN MATCHED AND target.OtherParam <> source.OtherParam THEN UPDATE SET target.OtherParam = source.OtherParam
WHEN NOT MATCHED THEN INSERT (BrandId, LocationId, Field3, OtherParam)
VALUES(source.BrandId,source.LocationId,source.Field3,source.OtherParam)
;
Any questions also please let me know.

Related

SQL: how can I set a variable in this context?

I'm trying to set a couple of variables, one is a name of the table (#table), the other one is a string.
I've got the error message:
Must declare the table variable "#table".
as far as I could understand it's because I must use #table as a table variable, but I just need it as a string
declare #a varchar(50);
declare #table varchar(100);
select #table =
case
WHEN Version = 'Advanced' THEN ("tableadv")
WHEN Version = 'Professional' THEN ("tablepro")
WHEN Version = 'Light' THEN ("tablelight")
WHEN Version = 'Short' THEN ("tableshort")
END
FROM partno where inpn=3
set #a = (select top (1) LicenseNumber from #table where used is null)
insert into seriali (LicenseNumber, idpn, serdgtrace)
select #a, 2, 'DAT-enrico'
update #table set used = 1 where LicenseNumber=#a
any help will be appreciated.
Many thanks,
enrico
You need table variable not only variable to store more values with more columns not just single value :
declare #table table (
#col int,
. . .
)
insert into #table (col)
select case WHEN Version = 'Advanced' THEN 'tableadv'
WHEN Version = 'Professional' THEN 'tablepro'
WHEN Version = 'Light' THEN 'tablelight'
WHEN Version = 'Short' THEN 'tableshort'
end
from partno
where inpn = 3
hello Yogesh and thanks for the answer,
anyway I need this case structure to pick the right table name based on a field in another table.
Then I have to pass this value to update the right table;
set #a = (select top (1) LicenseNumber from #table where used is null)
update #table set used = 1 where LicenseNumber=#a
I don't need to build a table variable with one of the case values in a record, because I won't be able to pass the right table name either
Many thanks,
Enrico

Need to use `IN` for multiple string value in static query

SELECT *
FROM #empInfo
WHERE source IN (CASE WHEN #source = 'Mint' THEN 'Mint, Ming' ELSE #source END)
I am trying in the way as above.
CREATE TABLE #empInfo
(
id INT IDENTITY(1, 1),
ename VARCHAR(50),
source VARCHAR(50)
)
INSERT INTO #empInfo
VALUES ('Jon', 'Mint'), ('Jack', 'Ryan'), ('Jackie', 'Ming'),
('Jeny', 'Wing'), ('Jen', 'Wing')
DECLARE #source VARCHAR(50)
SET #source = 'Mint'
From the above what if want is, if the value Mint comes in the #source then it has to search for Mint,Ming else what comes in #source. #source always hold single value.
I have to apply this logic in very large query which is not dynamic nor I can make it, so please provide me the solution for this case if any.
Sorry, if it's not possible without dynamic query.
Thank you very much in advance.
SELECT * FROM #empInfo
WHERE (#source = 'Mint' and source in ('Mint','Ming'))
OR (#source <> 'Mint' and source = #source)
Remember that an IN clause needs separate string values. So you can't use a comma separated list in #source
A shorter version of the above query would be
SELECT * FROM #empInfo
WHERE #source = source
OR (#source = 'Mint' and source = 'Ming')

Removing ' ' from sql query and stored proc

I have a table in which a column named data is of type varbinary . If I do a simple query
select * from tab where data = 1 then it works but if I do select * from tab where data = '1' then it does not return any row. The issue comes when I create a stored proc to retrieve data from this table and it converts the query and adds ' ' in the parameter when querying and so I am not able to retrieve any data. Can some one please tell me how to get around this issue.
Parameters
#ID INT = NULL
,#Data varchar(100) = NULL
CREATE TABLE #Results (
ID INT
,Data varchar(100)
)
BEGIN
INSERT #Results (
ID
,Data
)
SELECT
SK.ID
,SK.Data
FROM dbo.tab SK
where SK.ID = #ID And SK.data = #data
END
SELECT #TotalRows = COUNT(*)
FROM #Results
SELECT #TotalRows TotalRows
Now from the code when I execute this statement
oReader = ExecuteReader(oConn, CommandType.StoredProcedure, "Proc", New SqlParameter("#ID", Request.ID), _
New SqlParameter("#Data", Request.Data))
I see in SQL Profiler that it runs the query as 'data'
which does not return any rows
Thanks
Since you have said that you have written an SP, I think the inpput parameter is specified as NVARCHAR or VARCHAR
Below is one way of doing but i'm guessing that the column called data will only have integer values in the first solution.
DECLARE #X VARCHAR(5)
SET #X = '1 '
SELECT CAST(#X AS INT)
The above is only if the Data column specified above is Integer.
If the same is string (VARCHAR) you can write a User defined function to do the same.
CREATE FUNCTION dbo.TRIM(#string VARCHAR(8000))
RETURNS VARCHAR(8000)
BEGIN
RETURN LTRIM(RTRIM(#string))
END
SELECT dbo.TRIM('1 ')
I hope the above was useful, I did get the idea rather copied the function from here
http://blog.sqlauthority.com/2007/04/24/sql-server-trim-function-udf-trim/

Cascade copy of rows in sql

I found this thread here: http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=16836
I have exactly the same problem. Quote:
Rob Pearmain writes "I have 3 tables that hold questions.
Table 1 : Question
Field : ID (Unique) Field : Name (Text)
Table 2 : Question Text (References Table1-ID)
Field : ID (Unique) Field : QuestionID (integer ref to Table1 ID)
Field : Text
Table 3 : Options
Field : ID (Unique) Field : QuestionTextID (integer ref to Table2 ID)
Field : Text
Say for example, I create a question with 2 Question text records and
5 option records. If I wanted to duplicate that question to a new
question, and copy over the Question Text records to new ID's, and all
the related options, how can I do this easily (As the duplicate
question will have a new ID, each of the duplicated question text's
will have new ID's as will each of the options)."
The suggested solution is:
create procedure CopyQuestion
#idtocopy int
AS
declare #tempquestionid
declare #tempquestiontextid
declare #questiontextid
insert into question (name)
select name from question where id = #idtocopy
select #tempquestionid = ##identity
declare question_cursor cursor for
select id from [question text] where id = #idtocopy
open question_cursor
fetch next from question_cursor into #questiontextid
while ##fetch_status = 0
begin
insert into [question text] (questionid, text)
select #tempquestionid, text from [question text] where id = #questiontextid
select #tempquestiontextid = ##identity
insert into [options] (questiontextid, text)
select #tempquestiontextid, text from [options] where questiontextid = #questiontextid
fetch next from question_cursor into #questiontextid
end
close question_cursor
deallocate question_cursor
Is there a better solution to this problem? I will use an insert trigger.
Thanks!
You can use the merge statement with the output clause to get a match between the old and new id in questionText. This is described in this question Using merge..output to get mapping between source.id and target.id.
In your case the code would look something like this. The code is not tested so there might be any number of typos in there but it shows what you can do.
create procedure CopyQuestion
#idtocopy int
as
declare #QuestionID int
insert into question
select Name
from question
where ID = #idtocopy
select #QuestionID = scope_identity()
declare #IDs table (NewQID int, OldQID int)
merge questionText as T
using (select ID, #QuestionID as QuestionID, Field
from questionText
where QuestionID = #idtocopy) as S
on 0=1
when not matched then
insert (QuestionID, Field) values (QuestionID, Field)
output inserted.ID, S.ID into #IDs;
insert into options
select
I.NewQID,
O.Field
from options O
inner join #IDs as I
on O.QuestionTextID = I.OldQID
This is another way to do the same thing a little bit more set based. In my below example I used a temp table to map the IDs between the two new tables. Also please remove spaces from your table names (just because you can doesn't mean you should).
CREATE PROCEDURE udf_COPY_QUESTION
#ID_TO_COPY int
as
BEGIN TRANSACTION
BEGIN TRY
DECLARE #NEW_QUESTION_ID INT, #MAX_ID INT
insert into question (name)
select name from question where id = #ID_TO_COPY
SET #NEW_QUESTION_ID = SCOPE_IDENTITY()
SET #MAX_ID =IDENT_CURRENT( 'question text' )
select #NEW_QUESTION_ID AS questionid,
Text,
ROW_NUMBER() OVER (ORDER NAME) + #MAX_ID as new_text_id,
id as old_text_id
INTO #TEMP from [question text]
where questionid = #ID_TO_COPY
insert into [question text] (QuestionID,Text)
select questionid,Text from #TEMP
order by new_text_id
insert into Options (questiontextid, text)
select t.new_text_id,o.Text from options o
inner join #temp t on t.old_text_id = o.questiontextid
COMMIT TRANSACTION
END TRY
BEGIN CATCH
RAISERROR('COPY FAILED',10,1)
ROLLBACK TRANSACTION
END CATCH

What's the best way to lock a record while it is being updated?

If I need to SELECT a value from a table column (happens to be the primary key column) based on a relatively complex WHERE clause in the stored procedure, and I then want to update that record without any other concurrent stored procedures SELECTing the same record, is it as simple as just using a transaction? Or do I also need to up the isolation to Repeatable Read?
It looks like this:
Alter Procedure Blah
As
Declare #targetval int
update table1 set field9 = 1, #targetval = field1 where field1 = (
SELECT TOP 1 field1
FROM table1 t
WHERE
(t.field2 = 'this') AND (t.field3 = 'that') AND (t.field4 = 'yep') AND (t.field9 <> 1))
return
I then get my targetval in my program so that I can do work on it, and meanwhile I don't have to worry about other worker threads grabbing the same targetval.
I'm talking SQL 2000, SQL 2005, and SQL 2008 here.
Adding ROWLOCK,UPDLOCK to the sub query should do it.
ALTER PROCEDURE Blah
AS
DECLARE #targetval INT
UPDATE table1
SET field9 = 1,
#targetval = field1
WHERE field1 = (SELECT TOP 1 field1
FROM table1 t WITH (rowlock, updlock)
WHERE ( t.field2 = 'this' )
AND ( t.field3 = 'that' )
AND ( t.field4 = 'yep' )
AND ( t.field9 <> 1 ))
RETURN
Updated
The currently accepted answer to this question does not use updlock. I'm not at all convinced that this will work. As far as I can see from testing in this type of query with a sub query SQL Server will only take S locks for the sub query. Sometimes however the sub query will get optimised out so this approach might appear to work as in Query 2.
Test Script - Setup
CREATE TABLE test_table
(
id int identity(1,1) primary key,
col char(40)
)
INSERT INTO test_table
SELECT NEWID() FROM sys.objects
Query 1
update test_table
set col=NEWID()
where id=(SELECT top (1) id from test_table )
Query 2
update test_table
set col=NEWID()
where id=(SELECT max(id) from test_table)