September 26, 2005 at 8:38 am
Hello,
How can I get the following to work? Transactions are still confusing to me.
Create Table Parent (Id Int Not Null Primary Key)
Create Table Child (Id Int Not Null, ParentId Int Not Null References Parent (Id))
This works:
Begin Tran
Insert Into Parent With (TABLOCKX) (Id) Values (3)
Insert Into Child With (TABLOCKX) (Id, ParentId) Values (1, 3)
Commit Tran
This doesn't work, but I would like it to:
Begin Tran
Insert Into Parent With (TABLOCKX) (Id) Values (3)
Insert Into Child With (TABLOCKX) (Id, ParentId) Values (1, 3)
Commit Tran
The main idea of a transaction is that it is atomic. Why should the order of inserts be relevant, if the end state doesn't violate any constraints? Why should insert hints even have to be considered in this situation?
Atomic? Perhaps this has something more to do with sub-atomic, quarks/neutrinos, and quantum phenomena, and the uncertainty principle.
Richard
September 26, 2005 at 8:59 am
Is it just me or you posted the same code twice??
Assuming you meant to swap the inserts, it will fail because the foreign key will cancel the transaction because no parent is found. It's like trying to have a child before the parent is born... it's just not the way things are supposed to work .
September 26, 2005 at 9:36 am
Hello,
Indeed I did post the same code twice, it should be:
Begin Tran
Insert Into Child With (TABLOCKX) (Id, ParentId) Values (1, 3)
Insert Into Parent With (TABLOCKX) (Id) Values (3)
Commit Tran
Many actions within a database do not have a corresponding action in the real world, but I can't come up with off the top of my head.
The A in Acid is Atomicity. If transactions are atomic, then why should part of this transaction fail? It would be interesting to know Celko's view on this.
Richard.
September 26, 2005 at 9:40 am
Under SQL Server, integrity constraints are checked when the action is performed not when the commit is performed.
Some other RDBMS do support an option that some integrity contstraints can be deferred until a commit, but SQL server does not support this functionallity.
SQL = Scarcely Qualifies as a Language
September 26, 2005 at 11:45 am
This works, but it's not elegant:
Alter Table Child NoCheck Constraint All
Insert Into Child With (Id, ParentId) Values (1, 3)
Insert Into Parent With (Id) Values (3)
Alter Table Child Check Constraint All
Oh well, that's the best I can do.
September 26, 2005 at 11:47 am
Works great... but you now have unvalidated data in the table so you might has well drop the constraint altogether.
September 26, 2005 at 11:55 am
There's no issue here, for three reasons:
1. The data went into Child unvalidated, but I can check it after re-enabling the constraint by issuing an update statement.
2. If you drop the constraint, you must assume that you know what the !@#$ you're doing.
3. The constraint (fk constraint that is) is re-enabled after the inserts, which will maintain the integrity between parent and child (in the future).
Richard
September 26, 2005 at 12:13 pm
1 - Performance killer and also the db can do it for you so why bother??
2 - Yup you must know what you're doing... but even then dropping the constraint would be a stupid move in most cases (not assuming anything here).
3 - Re-enabled doesn't mean it will validate what you just did and what others might have done in the meantime. The bottom line is that you have unvalidated data and that unless this comes from a trusted source (like when you transfer data from server to server, where the data is already validated), then it's just pointless to have a FK in the first place.
September 27, 2005 at 3:39 am
RLSQL, I think that it would be best to decide whether you want to use the foreign key functionalities or not. Either drop the foreign key constraint altogether and leave just an index on the column, and then you can insert in any order you wish, and validate the data with your own means. Or leave the FK in place, but then make use of it and don't drop the constraint before insert.
A mix of both is IMHO the worst possible solution and I wouldn't recommend it.
September 27, 2005 at 7:19 am
i think you missed the point that each insert statement is an atomic transaction;
the begin tran is just a marker so that a group of atomic transactions can be rolled back....it is not a transaction itself, and thus would not be atomic in nature.
so the two insert statements are individually atomic...that's why the parent insert...child insert has no problems, but the reverse does.
from a business perspective, why do you feel you need to insert the child records first? what is the underlying reason why you think it needs to be done in that order?
Lowell
September 27, 2005 at 8:30 am
Good morning,
Insert statements are only an atomic transaction if they run within an implied transaction, IE/no explicit transaction control. In the case where two inserts are executed within an explicit transaction, it would make sense to me, and arguably the ANSI-92 committee, that the constraints are checked after the commit occurs, not after the inserts occur. I wish someone like Celko could verify this for me.
I have developed a script of over 4,000 lines that creates new tables, inserts some test data, and also moves existing data into the new tables. The new tables are created with a multitude of constraints enabled, ie/Primary key, Foreign key, column checks, and even table level constraints. Those constraints are working very well, and it behooves the company I'm doing this for to do this; there are already multiple issues in the existing tables where children have no parents, tables exist without a primary key, and a 1-n relationships are not enforced using a pk/fk pair.
Which brings me to my problem. Everytime I run the script, any new data I programmatically added, after the last time I ran it, is eliminated. So lets say I run the script at 9am. Then I add some data to various new tables in the apps I'm developing. Then a change to the metadata is required, so I alter the script and re-run it. The result? The data inserted between 9am and when I "gen'd" is eliminated. Not very happy about that.
So what have I done? I've written a stored procedure "BackupRestore", which will save the content of all tables to TempDb before all the tables are dropped/re-created. The table rows are saved at the beginning of the script, and restored from the backup tables at the end of the script. It's working fairly well, I have to nail down a couple of issues. The complex part of the proc involves restoring the tables. Lets say you have over 30 tables that you want to restore. In what order are you going to restore them? One way is to analyze the dependencies/relationships, and restore the parent first, the child second, the grandchild third, and so on. This is an elegant way, but also time consuming and complex. You could iterate over sysobjects and sysreferences to create a "dependency map", which would then allow you to insert the parent table before the child. This becomes really tricky in my case because there are some tables that are self-joining. For example:
Create Table ApplicationSettingCategory (
Id Int Not Null Primary Key
, ApplicationId Int Not Null References Application (Id)
, ParentId Int Null References ApplicationSettingCategory (Id)
, Name T_Name Not Null
, Description T_Description Not Null
, Rank Int Not Null
, Constraint UniqueCategoryName Unique (ParentId, Name)
)
go
What this implies is that in addition to determining the order of inserts on a table by table basis, you must also determine the order of inserts in an individual table!'
In the end, the best approach to restore is, IMO:
{For each table to restore, in alphabetical order}
{Disable all constraints}
{Restore the table}
{Re-enable all constraints}
{End}
Check out my proc below, if you wish. Just remember it isn't complete.
Richard
---
Create Procedure BackupRestore
@InOp Char(1) = 'B' -- 'B' for backup, 'R' for restore.
As
Begin
Set NoCount On
Declare
@BackupMsg Varchar(100)
, @BackupTableName Varchar(100)
, @DynCmd NVarchar(2000)
, @IdCount Int
, @MaxCrDate DateTime
, @TableHasIdColumn Bit
, @TableName SysName
, @TableId Int
, @TableRowCount Int
Create Table #BackupTables (
Id Int Not Null
, Name SysName Not Null
, Restored Bit Not Null
)
Select @MaxCrDate = Max(CrDate) From SysObjects Where Type = 'U'
Insert Into #BackupTables (Id, Name, Restored)
Select Id, Name, '0' From SysObjects Where Type = 'U' And DateDiff(dd, CrDate, @MaxCrDate) = 0 Order By Name
Delete From #BackupTables Where (Name = 'Feedback') Or (Name = 'Incident') Or (Name = 'FormLetter') Or (Name = 'IFormLetter')
-- Select @BackupMsg = Cast(Count(0) as Varchar) + ' tables will be backed up and restored. These are: ' From #BackupTables
Select @BackupMsg = Cast(Count(0) as Varchar) + ' tables will be backed up and restored.'
If @InOp = 'B'
Begin
RaisError(@BackupMsg, 0, 1)
-- Select Name From #BackupTables Order By Name
End
If @InOp = 'B'
Begin
Select @TableId = Min(Id) From #BackupTables
While (@TableId Is Not Null)
Begin
Select @TableName = Name From #BackupTables Where Id = @TableId
Set @BackupTableName = 'Backup' + @TableName
Set @DynCmd =
'If Exists(Select * From TempDb..SysObjects Where Name = ' +
QuoteName(@BackupTableName, '''') + ') Drop Table TempDb..' + @BackupTableName
-- Print @DynCmd
Exec sp_ExecuteSql @DynCmd
Set @DynCmd = 'Select * Into TempDb..' + Cast(@BackupTableName as Char(40)) + ' From ' + @TableName
Exec sp_ExecuteSql @DynCmd
-- Print @DynCmd
Select @TableId = Min(Id) From #BackupTables Where Id > @TableId
End
End
Else
If @InOp = 'R'
Begin
Select @TableId = Min(Id) From #BackupTables
While (@TableId Is Not Null)
Begin
Select @TableName = Name From #BackupTables Where Id = @TableId
Set @DynCmd = 'Select @Count = Count(0) From ' + @TableName
Execute sp_ExecuteSql @DynCmd, N'@Count Int Output', @TableRowCount Output
-- If @TableRowCount = 0
Begin
Set @BackupTableName = 'TempDB..Backup' + @TableName
Set @DynCmd = 'Alter Table ' + @TableName + ' NoCheck Constraint All'
Exec sp_ExecuteSql @DynCmd
Set @TableHasIdColumn = 1
Set @DynCmd = 'Select @Count = Count(0) From SysColumns Where Name = ''Id'' And Id = Object_Id(' + QuoteName(@TableName, '''') + ')'
-- RaisError('Query for Id column: %s', 0, 1, @DynCmd)
Exec sp_ExecuteSql @DynCmd, N'@Count Int Output', @IdCount Output
If @IdCount = 0
Set @TableHasIdColumn = 0
If (@TableHasIdColumn = 1)
Begin
Set @DynCmd = 'Insert Into ' + @TableName + ' Select * From ' + @BackupTableName
Set @DynCmd =
'Insert Into ' + @TableName +
' Select * From ' + @BackupTableName + ' B ' +
' Where ' +
'Not Exists(Select Id From ' + @TableName + ' Where Id = B.Id)'
-- Print @DynCmd
Exec sp_ExecuteSql @DynCmd
If @@Error = 0
Update #BackupTables Set Restored = 1 Where Id = @TableId
End
Set @DynCmd = 'Alter Table ' + @TableName + ' Check Constraint All'
Exec sp_ExecuteSql @DynCmd
Set @DynCmd = 'Drop Table ' + @BackupTableName
Exec sp_ExecuteSql @DynCmd
End
Select @TableId = Min(Id) From #BackupTables Where Id > @TableId
End
-- Select @BackupMsg = Cast(Count(0) as Varchar) + ' tables were restored. These are: ' From #BackupTables Where Restored = '1'
Select @BackupMsg = Cast(Count(0) as Varchar) + ' tables were restored.'
RaisError(@BackupMsg, 0, 1)
-- Select Name From #BackupTables Where Restored = '1' Order By Name
End
Return(0)
End
go
September 27, 2005 at 9:08 am
Well certainly for a data load such as you describe, I suppose it is quite ok to disable constraints, etc on your tables because
The large data movement would also run faster I imagine because SQL won't be checking constraints, etc (although with indices I am not sure how much of a speed difference it would make).
So yeah, in your situation, it may well be best. You could always write some simple validation select statements to select any child data in a child table that doesn't have an entry in its parent table - run them after your data has been restored.
September 27, 2005 at 9:09 am
And I guess it is precisely the reason why DTS can fail if you just tell it to "copy data" - it doesn't analyse table relationships, etc. You are effectively doing the same as what DTS does - so disable constraints or do the annoying work of figuring out what needs to be inserted in what order...
September 27, 2005 at 9:19 am
below is a procedure which returns all tables in the foreign key hiearachy order; it's copied and slightly adapted from scripts here at SSC;
i would suggest the following:
use this to get the tables and columns in hierarchy order. if you don't need the columns, search for "hierarchy" or "hierarchy2" here on SSC to get the original.
copy the data to temp the way you are doing it now
drop the tables.
recreate the tables.
move the data in the same order specified by the hierarchy results:
create procedure pr_GetTblColsInHierarcy
as
begin
set nocount on
declare
@level int,
@movedtonewlevel int,
@sql varchar(1024)
create table #hierarchy
(hierarchyorder int identity(1,1) not null primary key,fklevel int,
tblname varchar(517),
id numeric
)
-- populate the table
create table #tuser (type int, oname varchar(517), owner varchar(517), seq int)
insert #tuser exec sp_msdependencies null, 3, null, null, 1
insert into #hierarchy(tblname,fklevel)
select oname,seq from #tuser order by seq
--SELECT * FROM #hierarchy
insert into #hierarchy(tblname,fklevel)
select [name],0 from sysobjects where xtype='U' and [name] not in (select tblname collate latin1_general_cs_as from #hierarchy)
delete from #hierarchy where tblname like 'dt%'
select
sysobjects.name as tblname,
syscolumns.name as colname
from sysobjects
inner join syscolumns on sysobjects.id=syscolumns.id
left outer join #hierarchy on sysobjects.name=#hierarchy.tblname collate latin1_general_cs_as
where sysobjects.xtype='U' and sysobjects.name <> 'dtproperties'
order by hierarchyorder,sysobjects.name,syscolumns.name
drop table #hierarchy
end
Lowell
September 27, 2005 at 9:35 am
this is probably better for you than the long crap i posted:
create table #tuser (type int, oname varchar(517), owner varchar(517), seq int)
insert #tuser exec sp_msdependencies null, 3, null, null, 1
select * from #tuser order by seq
Lowell
Viewing 15 posts - 1 through 15 (of 15 total)
You must be logged in to reply to this topic. Login to reply