2009-04-07 12 views
0

実際にはスタッフィーなツリーのセットがあります。上部には、顧客と、その請求書と、請求書の詳細レコードがあります。 (実際には、これらのテーブルはすべて顧客を参照していますが、その原則は3つのテーブルだけに適用する必要があります)共通の祖先を持つ制約のあるレコードのセットをコピーする

私は、顧客とすべてのレコードをコピーしてその顧客はすべてのレコードのすべての単一フィールドを列挙する必要はありません。すべてがそれの上にあるものに制限された外部キーであり、テーブルのほとんどは自動増分IDフィールドを持っています。

以下は、データベースを設定するためのT-SQLスクリプトです。はい、それは面倒ですが、それは完全です。

CREATE TABLE [dbo].[Customer](
    [custID] [int] IDENTITY(1,1) NOT NULL, 
    [name] [varchar](50) NOT NULL, 
CONSTRAINT [PK_Customer] PRIMARY KEY CLUSTERED ([custID] ASC) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] 
GO 
CREATE TABLE [dbo].[Invoice](
    [invoiceNum] [int] IDENTITY(1,1) NOT NULL, 
    [custID] [int] NOT NULL, 
    [Description] [varchar](50) NOT NULL, 
CONSTRAINT [PK_Invoice] PRIMARY KEY CLUSTERED ([invoiceNum] ASC) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] 
GO 
CREATE TABLE [dbo].[InvoiceDetail](
    [invoiceNum] [int] NOT NULL, 
    [sequence] [smallint] NOT NULL, 
    [description] [varchar](50) NOT NULL, 
    [price] [decimal](10, 2) NOT NULL CONSTRAINT [DF_InvoiceDetail_price] DEFAULT ((0.0)), 
CONSTRAINT [PK_InvoiceDetail] PRIMARY KEY CLUSTERED ([invoiceNum] ASC, [sequence] ASC) 
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY] 
GO 
ALTER TABLE [dbo].[Invoice] WITH CHECK ADD CONSTRAINT [FK_Invoice_Customer] 
    FOREIGN KEY([custID]) 
    REFERENCES [dbo].[Customer] ([custID]) 
GO 
ALTER TABLE [dbo].[Invoice] CHECK CONSTRAINT [FK_Invoice_Customer] 
GO 
ALTER TABLE [dbo].[InvoiceDetail] WITH CHECK ADD CONSTRAINT [FK_InvoiceDetail_Invoice] 
    FOREIGN KEY([invoiceNum]) 
    REFERENCES [dbo].[Invoice] ([invoiceNum]) 
GO 
ALTER TABLE [dbo].[InvoiceDetail] CHECK CONSTRAINT [FK_InvoiceDetail_Invoice] 

declare @id int; 
declare @custid int; 
insert into Customer values ('Bob'); 
set @custid = @@IDENTITY; 
insert into Invoice values (@custid, 'Little Purchase'); 
set @id = @@IDENTITY; 
insert into InvoiceDetail values (@id, 1, 'Small Stuff', 1.98); 
insert into InvoiceDetail values (@id, 2, 'More Small Stuff', 0.25); 
insert into Invoice values (@custid, 'Medium Purchase'); 
set @id = @@IDENTITY; 
insert into InvoiceDetail values (@id, 1, 'Stuff', 11.95); 
insert into InvoiceDetail values (@id, 2, 'More Stuff', 10.66); 
insert into Customer values ('Sally'); 
set @custid = @@IDENTITY; 
insert into Invoice values (@custid, 'Big Purchase'); 
set @id = @@IDENTITY; 
insert into InvoiceDetail values (@id, 1, 'BIG Stuff', 100.00); 
insert into InvoiceDetail values (@id, 2, 'Larger Stuff', 99.95); 

だから、私は何をしたい、このデータベースに「ボブ」のコピーを作成し、すべてのテーブルのために、各列を指定するの面倒をすべてずに「BOB2」と呼んでいます。私は可能ですが、実際の世界ではたくさんのコラムがあります。

もう1つの問題は、それぞれの請求書を取得するために明示的なループを作成する必要があるということです。請求書の詳細を記入するには、前の請求書挿入からIDが必要です。

私はC#の "コピー"プログラムを持っていますが、データベース内でこれをすべて実行したいと思います。素朴な実装は、どこでもループとカーソルを持つトランザクションSQLストアドプロシージャです。

は賢いですかこれらの問題のいずれか(両方ではない場合)を回避する方法はありますか?

答えて

1

私はもっと多くのテーブルが関わっているのと同様の問題がありました。実際には、コピーする行ごとにカーソルを作成することを避けることができます。唯一のカーソルは、関連するテーブル名のリストをループするためのものです。このために動的SQLが必要になります。従来のカーソルループソリューションと比較して、操作全体が非常に高速です。

トリックは、関連する行を同じテーブルに挿入することです。そのFK列をその親に更新します。私たちが質量をどのように集めることができるかは、挿入中に 'output'キーワードを使用して、一時テーブル#refTrackに保存することです。その後、FKを更新するために#refTrackに関連するテーブルに参加します。

我々はことを知っている:


create table #refTrack 
(
    tbl sysname, 
    id int, 
    refId int 
) 

insert InvoiceDetail (refId, invoiceNum, sequence, description, price) 
output 'InvoiceDetail', inserted.id, inserted.refId into #refTrack 
select invoiceNum, invoiceNum, sequence, description, price from InvoiceDetail 
where custID = 808 -- denormalized original Bob^s custID 

が新たに作成された自動走行番号のリストを一時的#refTrack表を移入します。私たちの仕事は、この挿入クエリを動的にすることです。 「ID」の名前で、独自の

  1. 主キー:

    この方法の唯一の欠点は、我々が持っている必要があり、各テーブルの上に、一貫性が必要なことです。この場合、Customer.cidIDという名前を変更してCustomer.idにする必要があります。 Invoice.idになるInvoice.invoiceNum; InvoiceDetailの新しい列 'id int identity(1,1)primary key'が追加されました。

  2. 非正規化された 'custID'列。 'depth'> 1でリストされたテーブルの場合、テーブルは現在のフロントエンドアプリケーションにこの新しいヘルパーカラムを設定する必要があります。 '挿入トリガ'は私たちの仕事を少し複雑にします。
  3. 「refId」と呼ばれる列は、次のように定義されています。この列は、行の関係を 'Bob2'のコピーにするためのものです。撮影した

ステップ:私はちょうど上記の挿入の際に「TBL」列に移入するように抽象的に行くのが大好きです

A.リストをすべてのテーブル名@tListテーブルに変数


declare @tList table 
(
    tbl sysname primary key, 
    fkTbl sysname, 
    fkCol sysname, 
    depth int 
) 
insert @tList select 'Customer', null, null, 0 
insert @tList select 'Invoice', 'Customer', 'custID', 1 
insert @tList select 'InvoiceDetail', 'Invoice', 'invoiceNum', 2 

を。 information_schemaのビューの再帰的なCTEの結果を更新して残りの列を動的に移入します。しかし、それはポイントの横にある可能性があります。テーブル名のリストが含まれていて、テーブルの作成方法が順序付けられていると仮定しましょう。

B. @tListテーブルをカーソルでループします。


declare 
    @depth int, 
    @tbl sysname, 
    @fkTbl sysname, 
    @fkCol sysname, 
    @exec nvarchar(max), 
    @insCols nvarchar(max), 
    @selCols nvarchar(max), 
    @where nvarchar(max), 
    @newId int, 
    @mainTbl sysname, 
    @custId int 


select @custId = 808 -- original Bob^s custID to copy from 

select @mainTbl = tbl from @tList where fkTbl is null 

declare dbCursor cursor local forward_only read_only for 
    select tbl, fkTbl, fkCol, depth from @tlist order by depth 
open dbCursor 
fetch next from dbCursor into @tbl, @fkTbl, @fkCol, @depth 
while @@fetch_status = 0 
begin 
    set @where = case when @depth = 0 then 'Id' else 'custId' end + ' = ' + 
     cast(@custId as nvarchar(20)) 
    set @insCols = dbo.FnGetColumns(@tbl) 
    set @selCols = replace 
    (
     @insCols, 
     'refId', 
     'Id' 
    ) 
    set @exec = 'insert ' + @tbl + ' (' + @insCols + ') ' + 
     'output ''' + @tbl + ''', inserted.id, inserted.refId into #refTrack ' + 
     'select ' + @selCols + ' from ' + @tbl + ' where ' + @where 

    print @exec 
    exec(@exec) 

    -- remap parent 
    if isnull(@fkTbl, @mainTbl) != @mainTbl -- third level onwards 
    begin 
     set @exec = 'update ' + @tbl + ' set ' + @tbl + '.' + @fkCol + ' = rf.Id from ' + 
      @tbl + ' join #refTrack as rf on ' + @tbl + '.' + @fkCol + ' = rf.refId and rf.tbl = ''' + 
      @fkTbl + ''' where ' + @tbl + '.custId = ' + cast(@newId as nvarchar(20)) 

     print @exec 
     exec(@exec) 
    end 

    if @depth = 0 select @newId = Id from #refTrack 
    fetch next from dbCursor into @tbl, @fkTbl, @fkCol, @depth 
end 

close dbCursor 
deallocate dbCursor 

select * from @tList order by depth 
select * from #refTrack 

drop table #refTrack 

C. FnGetColumnsの内容は、():


create function FnGetColumns(@tableName sysname) 
returns nvarchar(max) 
as 
begin 
    declare @cols nvarchar(max) 
    set @cols = '' 
    select @cols = @cols + ', ' + column_name 
     from information_schema.columns 
     where table_name = @tableName 
      and column_name <> 'id' -- non PK 
    return substring(@cols, 3, len(@cols)) 
end 

私たちはさらにはるかにダイナミックであることを、これらのスクリプトを向上させることができます確信しています。しかし、問題を解決するために、これは最低限の必要条件です。

乾杯、

アリ。

0

"ほとんどのテーブルに自動インクリメントIDフィールドがあります"

問題の一部があります。 IDENTITYをPKとして使用すると、(コンピューティングの観点から)これらの操作は困難でコストがかかります。 IDENTITYを使用していなくても、新しい顧客のために新しい請求書番号を生成する必要があります。つまり、一度に1つずつサイクルを進めるか、新しい請求書番号を割り当てるセットベースのメソッド。これを使用して請求書明細行を作成することができます。

私は、あなたがビジネスの観点から理解していることを理解していると想定していますが、今でも正確に「実際」ではないデータを作成していることを指摘しておきます。すべての請求書を含むこれらの顧客の1つをコピーして、その年の売上を報告すると、売上を倍増させることになります。

解決しようとしているビジネス上の問題の詳細については、別の解決策が見つかる可能性があります。

+0

本当に「得意先」と「請求書」はありません。パブリックフォーラムに実際のスキーマを投稿することは不適切です。彼らはしかし、実際のデータベースを模倣する。 –

関連する問題