myBatis3.1.1 XML file about update statement - mybatis

I made a mapper XML file like this:
<update id="feeCalculation" parameterType="map">
declare #cnt int
insert into #tempA
select ... from ... where ...
insert into #tempA
select ... from ... where ...
insert into #tempB
select ... from ... where ...
insert into #tempB
select ... from ... where ...
insert into #tempC ...
select #cnt=count(*) from #tempA where ...
if #cnt > 0
begin
insert into #tempD ...
update #tempA set...
update #tempA set...
update #tempB set...
update #tempD set...
end
update #tempC set...
</update>
Because the calculation involves many steps working with several session temp tables, I put them all in one transaction. The result will be in #tempC and #tempD which will be fetched by next call.
With 'normal' data size, it works well by a single call 'feeCalculation()'. But when the data size increased, I encountered an error -- it failed somewhere in the middle so it got rollback without any exception thrown (I had try {} catch {} with the call). When I splited the single call into two calls, it worked:
<update id="feeCalculation1" parameterType="map">
declare #cnt int
insert into #tempA
select ... from ... where ...
insert into #tempA
select ... from ... where ...
insert into #tempB
select ... from ... where ...
insert into #tempB
select ... from ... where ...
insert into #tempC ...
</update>
<update id="feeCalculation2" parameterType="map">
select #cnt=count(*) from #tempA where ...
if #cnt > 0
begin
insert into #tempD ...
update #tempA set...
update #tempA set...
update #tempB set...
update #tempD set...
end
update #tempC set...
</update>
Somehow it seemed an transaction memory issue.
It's like a guessing for me to work with the XML statements, something that you wouldn't worry about if it's done by a stored procedure, just piling up all the SQLs in right order.
What's the best way to handle it in Java/myBatis? Is there anyway to catch this kind of exception?
Appreciate your time and help very much!
Glander

The problem was solved by adding the settings to myBatisConfig.xml without code change though I still couldn't fully understand why.
<settings>
<setting name="defaultExecutorType" value="BATCH"/>
</settings>
Glander

Related

How to create a view with INSERT rule RETURNING a value - without INSERT in the rule definition

I have the following simplified testcase:
CREATE TABLE test(id serial PRIMARY KEY, data varchar);
CREATE VIEW test_v AS SELECT * from test;
CREATE FUNCTION insert_test_fn(in_data varchar) RETURNS integer
AS $$
DECLARE
my_id integer;
BEGIN
INSERT INTO test (data) VALUES (in_data)RETURNING id INTO my_id;
-- do things with my_id
RETURN my_id;
END;
$$ LANGUAGE plpgsql;
CREATE RULE _INSERT AS
ON INSERT TO test_v DO INSTEAD
SELECT insert_test_fn(new.data);
INSERT INTO test_v (data) VALUES ('testval');
-- I would like to do this:
-- INSERT INTO test_v (data) VALUES ('testval') RETURNING id;
I have some client code which issues the statement
INSERT INTO test_v (data) VALUES ('testval') RETURNING id;.
This used to insert into test (ON INSERT TO test_v DO INSTEAD INSERT INTO test ... RETURNING...).
Now I need to change part of the behavior and instead of inserting directly into test, I want to call a function where this is done. (Parts of the data to be inserted will have to be calculated first).
Is there a way to define the _INSERT rule so that it still works when called with a RETURNING clause? This would allow me to leave the client code unchanged.
That will work just fine, but it is unnecessarily complicated:
You can directly insert into the view if its definition is as simple as that. But I assume that your actual case is more complicated.
You don't need the function, you can directly put INSERT INTO test ... in the rule definition.

how to execute the next T-SQL when a before T-SQL throw an error?

I am working with SQlite and I have many T-SQL that I want to execute all of them in this way:
T-SQL1; T-SQL2, ... T-SQLN
My T-SQL are:
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,1);
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,2);
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,3);
...
With this T.SQLs I want to related records from the table1 with the table2. If any of the relations exist, there are no problem all the T-SQL is execute, but if for exameple there are a problem with the second, the first is execute but the third and the next T-SQL are not executed.
My quiestion it's if there are any way to continue execute the T-SQL and don't take care if some of the T-SQL throw an error, because what I want it's to have the relation, if some relation exists it's because other user created it, so at the end it's what I want, that the relation exists, so I would like to continue with the next T-SQL.
Is it possible?
However, if I try to delete a record that does not exists, the next T-SQL are executed, so SQLite does not take care about the error and continue with the following. Why when I try to add a new record it does not work in the same way?
Thanks.
I would strongly recommend checking if it is OK to perform the T-SQL rather than ignoring errors.
You can do this by:
DECLARE #count int
SET #count = (SELECT COUNT(1) FROM myRelationTable WHERE IDTable1 =1 AND IDTabl2 = 1)
IF #count = 0 OR #count IS NULL
BEGIN
INSERT INTO myRelationTable(IDTable1, IDTabl2) VALUES (1,1)
END
SET #count = (SELECT COUNT(1) FROM myRelationTable WHERE IDTable1 =1 AND IDTabl2 = 2)
IF #count = 0 OR #count IS NULL
BEGIN
INSERT INTO myRelationTable(IDTable1, IDTabl2) VALUES (1,2)
END
SET #count = (SELECT COUNT(1) FROM myRelationTable WHERE IDTable1 =1 AND IDTabl2 = 3)
IF #count = 0 OR #count IS NULL
BEGIN
INSERT INTO myRelationTable(IDTable1, IDTabl2) VALUES (1,3)
END
Which can very easily be wrapped within a stored procedure.
As to your question the answer is:
Sure, easily:
BEGIN TRY
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,1);
END TRY
BEGIN CATCH
--Do nothing
END CATCH
BEGIN TRY
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,2);
END TRY
BEGIN CATCH
--Do nothing
END CATCH
BEGIN TRY
insert into myRelationTable(IDTable1, IDTabl2) VALUES (1,3);
END TRY
BEGIN CATCH
--Do nothing
END CATCH
Really my problem is that I am using SQLite Expert to execute the T-SQL, but this program detect as error syntax the Begin try line.
Other way to solve the problem is to use the igonre keyword in this way:
insert or ignore into my table...
This makes that if exists an error, the ignore it and execute the next statement. But again, the "ignore" keyword is detected as an syntax error by SQLite Expert.
The "ignore" keyword belongs to the on conflict clouse. There are more information in this link.
Thanks.

How to work with more than one output parameter in single stored procedure

I have a SP with an Output parameter that looks like:
ALTER PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
I call that procedure from vb.net to get the value for calculations. My problem is: I have 8 SP's with the following structure:
CREATE PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
CREATE TABLE #TempTable
Begin
Select ...
End
SET #VarName = Result
But the TempTable is always the same. No I am looking for a way to get all 8 values with only one stored procedure. My idea:
CREATE PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT as ...
CREATE TABLE #TempTable
---Get first value
Begin
Select ...
End
SET #VarName1 = Result
---Get second value
Begin
Select ...
End
SET #VarName2 = Result
...
How do i have to rewrite the line: ALTER PROCEDURE [dbo].[SP_Name] #VarName decimal(18,2) OUTPUT ir can I even work with an array?
You can use a single stored procedure with all your queries in it. Following will return a single row result set with eight fields and you can grab them from your code using the specific filed name or index.
CREATE PROCEDURE [dbo].[SP_Name]
#VarName decimal(18,2)
AS
BEGIN
DECLARE #VarName1 Datatype, #VarName2 Datatype, ...#VarName8 Datatype
SELECT #VarName1 = yourCol
FROM --First query
SELECT #VarName2 = yourCol
FROM --Second query
...
SELECT #VarName8 = yourCol
FROM --Eighth query
--Finally Select all the variables
SELECT #VarName1 Col1, #VarName2 Col2, ...,#VarName8 Col8
END
OR if you are looking to return results of your all 8 queries, that is also possible. Simply do your select queries in a single stored procedure and grab the DATASET from your code and you can access individual table using zero based Index (ex DataTable1 = YourDataSet.Tables[0])

Entity framework stored procedure issue

I have following stored procedure defined:
USE [BcmMetrice]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[ActivityAdd_proc] #Name nvarchar(max),#Description nvarchar(max) =null ,#Users nvarchar(max),#Object_id nvarchar (15) =null, #Source nvarchar(10) =null, #TemplateId bigint =null, #UserID bigint =null
AS
DECLARE activityUsers_cursor CURSOR FOR
select s from dbo.SplitString(#Users, ';')
DECLARE
#new_ActivityId bigint,
#new_CommentId bigint,
#activityUser_l bigint
BEGIN TRY
INSERT INTO [BcmMetrice].[dbo].[Activity]
([Name]
,[Description]
,[Type]
,[Created])
VALUES
(#Name
,#Description
,ISNULL(#TemplateId,0)
,GETDATE())
SET #new_ActivityId = (SELECT SCOPE_IDENTITY())
INSERT INTO [BcmMetrice].[dbo].[Comment] ([UserID],[CommentText],[Timestamp])
VALUES (ISNULL(#UserID,151),'Activity has been created',GETDATE())
SET #new_CommentId = (SELECT SCOPE_IDENTITY())
INSERT INTO [BcmMetrice].[dbo].[ActivityComment] ([ActivityID],[CommentID])
VALUES (#new_ActivityId, #new_CommentId)
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','users='+ISNULL(CAST(#Users as varchar(max)),'empty'),null)
OPEN activityUsers_cursor
FETCH NEXT FROM activityUsers_cursor INTO #activityUser_l
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','Inserting users='+ISNULL(CAST(#activityUser_l as varchar(max)),'empty'),null)
INSERT INTO [BcmMetrice].[dbo].[ActivityUser]
([ActivityId]
,[UserId]
,[Role])
VALUES
(#new_ActivityId
,#activityUser_l
,1)
FETCH NEXT FROM activityUsers_cursor INTO #activityUser_l
END
CLOSE activityUsers_cursor
DEALLOCATE activityUsers_cursor
END TRY
BEGIN CATCH
PRINT 'ERROR'
INSERT INTO [BcmMetrice].[dbo].[Log]([Timestamp],[Type],[Data],[StackTrace]) VALUES (GETDATE(),'SQL.ActivityAdd_proc','ERROR CATCHED!'+ERROR_MESSAGE(),null)
END CATCH
select #new_ActivityId
The thing I would like to do is to return from the procedure the id of a newly added activity. That is why at the very and I use line:
select #new_ActivityId
When testing this procedure in SQL Management Studio everything seems to be working fine. Problem starts when I try to use this procedure in my .NET project. I updated my edmx model form database, but when I execute this procedure the return value is invalid.
Procedure execution looks like this:
int ret = dc.Db.ActivityAdd_proc(name, description, users, object_id, source, templateId, userId);
Does anyone have an idea what I might be doing wrong?
I found solution to my problem. When you try to return data from stored procedure via select statement you must create Function Import in model browser of edmx. In a wizard you choose a type of returned collection (in my case it was scalar of type int64).

Call stored proc from after insert trigger

Perhaps a stupid question!
If I call a stored proc from an After Insert trigger (T-SQL) - then how do I get the values of the "just inserted" data?
e.g.
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
EXEC createAuditSproc 'I NEED VALUES HERE!'
I don't have any identity columns to worry about - I just want to use some of the "just inserted" values to pass into my sproc.
Edit: For clarification - I need this to call a sproc and not do a direct insert to the table, since the sproc does more than one thing. I'm working with some legacy tables I can't currently amend to do things 'properly' (time/resource/legacy code), so I have to work with what I have :(
You get to the newly 'changed' data by using the INSERTED and DELETED pseudo-tables:
CREATE TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
INSERT INTO myTableAudit(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
END
Given the example tables
create table myTable
(
ID INT identity(1,1),
Name varchar(10)
)
GO
create table myTableAudit
(
ID INT,
Name varchar(10),
TimeChanged datetime default CURRENT_TIMESTAMP
)
GO
Edit : Apologies, I didn't address the bit about calling a Stored Proc. As per marc_s's comment, note that inserted / deleted can contain multiple rows, which complicates matters with a SPROC. Personally, I would leave the trigger inserting directly into the audit table without the encapsulation of a SPROC. However, if you have SQL 2008, you can use table valued parameters, like so:
CREATE TYPE MyTableType AS TABLE
(
ID INT,
Name varchar(10)
);
GO
CREATE PROC dbo.MyAuditProc #MyTableTypeTVP MyTableType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO myTableAudit(ID, Name)
SELECT mtt.ID, mtt.Name
FROM #MyTableTypeTVP mtt;
END
GO
And then your trigger would be altered as like so:
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MyTableTypeTVP AS MyTableType;
INSERT INTO #MyTableTypeTVP(ID, Name)
SELECT i.ID, i.Name
FROM inserted i;
EXEC dbo.MyAuditProc #MyTableTypeTVP;
END
you can then test that this works for both a single and multiple inserts
insert into dbo.MyTable values ('single');
insert into dbo.MyTable
select 'double'
union
select 'insert';
However, if you are using SQL 2005 or lower, you would probably need to use a cursor to loop through inserted passing rows to your SPROC, something too horrible to contemplate.
As a side note, if you have SQL 2008, you might look at Change Data Capture
Edit #2 : Since you need to call the proc, and if you are certain that you only insert one row ...
ALTER TRIGGER dbo.MyTrigger
ON dbo.MyTable
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #SomeInt INT;
DECLARE #SomeName VARCHAR(10);
SELECT TOP 1 #SomeInt = i.ID, #SomeName = i.Name
FROM INSERTED i;
EXEC dbo.MyAuditProc #SomeInt, #SomeName;
END;