多くのdecimal(26,8)列を持つ幅広い非正規化テーブルを作成する必要があります(1024列の制限より少なく、ほとんどの列はnullまたはゼロになります)。行あたり8060バイトの制限があるので、ページ圧縮を使用してテーブルを作成しようとしました。以下のコードはテーブルを作成し、1つの行を挿入し、行サイズをクエリします。行サイズが制限を大幅に下回っていますが、テーブルにdecimal(26,8)列を1つ追加しようとすると、エラーが発生して操作が失敗します。内部オーバーヘッドのバイト。」それだけ多くの列を持つ単一のテーブルを作成する方法はありますか?
drop table t1
GO
create table t1(c1 decimal(26, 8) null)
with (data_compression = page)
GO
declare @i int = 2;
declare @sql varchar(100);
while @i <= 486
begin
set @sql = 'alter table t1 add c' + convert(varchar, @i) + ' decimal(26, 8) null';
execute (@sql);
set @i += 1;
end;
GO
insert into t1(c1) select 0
GO
declare @i int = 2;
declare @sql varchar(100);
while @i <= 486
begin
set @sql = 'update t1 set c' + convert(varchar, @i) + ' = 0';
execute (@sql);
set @i += 1;
end;
GO
select max_record_size_in_bytes from sys.dm_db_index_physical_stats (db_id(), object_id('t1'), NULL, NULL, 'DETAILED')
GO
実行している制限は、ページに保存されているデータとは関係ありません。計算は、列のデータ型に基づいて行われます。そのため、テーブルにデータがない状態でエラーが発生します。圧縮により、この制限はさらに悪化します。オーバーヘッドの背後にある技術的な詳細について読むことができます here 。
[〜#〜] sparse [〜#〜] 列を使用して、この問題を回避できます。つまり、挿入内容によっては挿入が失敗する可能性がありますが、8060バイトの制限を回避できます。次のコードは、1023列を問題なく作成できることを示しています。
_drop table t1
GO
create table t1(c1 decimal(26, 8) null)
GO
declare @i int = 2;
declare @sql varchar(100);
while @i <= 1023
begin
set @sql = 'alter table t1 add c' + convert(varchar, @i) + ' decimal(26, 8) SPARSE null';
execute (@sql);
set @i += 1;
end;
GO
_
ただし、それに関するすべての制限(リンクされた記事を参照)により、これがユースケースに適さなくなる場合があります。具体的には、NULL
値(_0
_ではなく)のみが最適化され、スペースをほとんど消費しません。 1つの行に挿入する_0
_ sが多すぎると、エラーが発生します。 1023 _0
_値を挿入しようとすると、次のようになります。
メッセージ511、レベル16、状態1、行1許容最大行サイズ8060より大きいサイズ17402の行を作成できません。
本当に必死になったら、代わりにVARCHAR(27)
として列を作成できると思います。可変長の列をページ外に移動できるため、テーブル定義の8060バイトの制限を超えることができますが、値の特定の組み合わせを挿入すると失敗します。 SQL Serverは、テーブルを作成するときにこれを警告します。
警告:テーブル "t1"が作成されましたが、その最大行サイズは許可されている最大の8060バイトを超えています。結果の行がサイズ制限を超えると、このテーブルへのINSERTまたはUPDATEは失敗します。
VARCHAR(27)
アプローチを使用する場合は、ページまたは行の圧縮が役立つ場合があります。これにより、_0
_とNULL
の両方で使用されるスペースが最小限になります。 VARCHAR(27)
を使用すると、1023の_0
_値を正常に挿入できます。
@ Joe's answer で説明されている技術的な側面と提案された回避策(VARCHAR(27)
列を使用)以外では、「 need これらのすべての列を1つのテーブルに含める必要があるという奇妙な技術的要件がない限り、[a]広い非正規化テーブルを作成するにはにする必要があります。必要な数の「兄弟」テーブルにまたがる。兄弟テーブルは次のようなテーブルです。
IDENTITY
列を持っています(他のFKはありません)IDENTITY
を持つテーブルのPKを指す外部キー(PK列)があります。ここでは、論理行を2つ以上の物理テーブルに分割しています。しかし、それが本質的に正規化の本質であり、リレーショナルデータベースが処理するように設計されています。
このシナリオでは、PKを複製することによって使用される余分なスペースと、テーブルを一緒に_INNER JOIN
_する必要があるためにクエリがさらに複雑になります(すべてのSELECT
クエリがすべての列を使用しない限り、頻繁ではありませんが、常に通常は発生しません)またはそれらを一緒にINSERT
またはUPDATE
に明示的なトランザクションを作成します(DELETE
は、FKで_ON DELETE CASCADE
_セットを介して処理できます)。
ただし、適切なネイティブデータ型を備えた適切なデータモデルを使用することでメリットが得られ、後で予期しない結果が生じる可能性のある手口がなくなります。 VARCHAR(27)
を使用してこれが技術レベルで機能する場合でも、実用的には、小数を文字列として格納することがプロジェクトの最大の利益になるとは思いません。
したがって、単一の論理エンティティを単一のコンテナで物理的に表現する必要がないことを認識していないために単一のテーブルのみが「必要」である場合、機能するときにこれらすべてを単一のテーブルに強制することを試みないでください。複数のテーブルにわたって優雅に。
以下の例は、基本的な概念を示しています。
[〜#〜]セットアップ[〜#〜]
_CREATE TABLE tempdb.dbo.T1
(
[ID] INT NOT NULL IDENTITY(11, 2) PRIMARY KEY,
[Col1] VARCHAR(25),
[Col2] DATETIME NOT NULL DEFAULT (GETDATE())
);
CREATE TABLE tempdb.dbo.T2
(
[ID] INT NOT NULL PRIMARY KEY
FOREIGN KEY REFERENCES tempdb.dbo.T1([ID]) ON DELETE CASCADE,
[Col3] UNIQUEIDENTIFIER,
[Col4] BIGINT
);
GO
CREATE PROCEDURE #TestInsert
(
@Val1 VARCHAR(25),
@Val4 BIGINT
)
AS
SET NOCOUNT ON;
BEGIN TRY
BEGIN TRAN;
DECLARE @InsertedID INT;
INSERT INTO tempdb.dbo.T1 ([Col1])
VALUES (@Val1);
SET @InsertedID = SCOPE_IDENTITY();
INSERT INTO tempdb.dbo.T2 ([ID], [Col3], [Col4])
VALUES (@InsertedID, NEWID(), @Val4);
COMMIT TRAN;
END TRY
BEGIN CATCH
IF (@@TRANCOUNT > 0)
BEGIN
ROLLBACK TRAN;
END;
THROW;
END CATCH;
SELECT @InsertedID AS [ID];
GO
_
[〜#〜]テスト[〜#〜]
_EXEC #TestInsert 'aa', 454567678989;
EXEC #TestInsert 'bb', 12312312312234;
SELECT *
FROM tempdb.dbo.T1
INNER JOIN tempdb.dbo.T2
ON T2.[ID] = T1.[ID];
_
戻り値:
_ID Col1 Col2 ID Col3 Col4
11 aa 2017-07-04 10:39:32.660 11 44465676-E8A1-4F38-B5B8-F50C63A947A4 454567678989
13 bb 2017-07-04 10:41:38.180 13 BFE43379-559F-4DAD-880B-B09D7ECA4914 12312312312234
_