web-dev-qa-db-fra.com

Stockage NULL contre stockage '' dans une colonne varchar

Je me rends compte que cela peut être marqué comme doublon, mais je demande spécifiquement par rapport à SQL Server 2005

J'ai lu des conseils contradictoires sur Internet, alors je pose la question ici. Plus précisément dans SQL Server 2005, un NULL dans une colonne varchar occupe-t-il le même espace qu'une chaîne vide?

J'ai construit une table "d'attente" sur un autre lecteur et l'ai remplie avec les données de la table source, et partout où les champs étaient vides, j'ai utilisé nullif([field],'') pour insérer des valeurs nulles à la place des blancs.

Ensuite, j'ai construit une nouvelle table avec exactement la même structure que la table d'attente, mais au lieu de remplacer les blancs par null, j'ai simplement inséré les blancs, et jusqu'à présent, il semble pour prendre plus d'espace ( Je n'ai pas encore fini de le remplir et je ne peux pas être sûr qu'il utilise encore plus de données)

Donc, avant de le remplir davantage et de me retrouver avec un tableau plus grand que je ne le pensais, est-il préférable d'insérer des valeurs nulles ou vides?

Modifier:

Après avoir migré les données de la table d'attente vers la nouvelle table, la nouvelle table est environ 4 Go plus grande.

Table sizes differences

Il n'y a que deux petites différences dans la conception de la table - Le champ 'serial_number' est char (15) dans la table d'attente mais varchar (15) dans la table de destination. (La longueur maximale d'un numéro de série est de 14 et il existe de nombreuses valeurs vides - je pense environ 30 millions si je me souviens), et l'index cluster pour la table d'attente a une colonne supplémentaire - nom_programme.

Table d'attente

USE [Temp_holding_EWS]
GO
/****** Object:  Table [dbo].[AmtoteAccountActivity_holding]    
 Script Date: 02/17/2017 20:41:32 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[AmtoteAccountActivity_holding](
    [_Date] [char](8) NULL,[Community] [varchar](10) NULL,
    [AccountNumber] [varchar](50) NULL,
    [Branch] [varchar](10) NULL,
    [Window] [varchar](3) NULL,
    [Time] [char](8) NULL,[Balance_Forward] [varchar](10) NULL,
    [Transaction_Type] [varchar](10) NULL,
    [Program_Name] [varchar](10) NULL,
    [Race] [varchar](10) NULL,[Pool_Type] [varchar](10) NULL,
    [Amount] [money] NULL,[Runners] [varchar](60) NULL,
    [Total_Bet_Amount] [varchar](10) NULL,
    [Debit_Amount] [varchar](10) NULL,
    [Credit_Amount] [varchar](10) NULL,
    [Tx_Date] [char](8) NULL,
    [Check_Clear_Date] [varchar](10) NULL,
    [Refund_Amt] [varchar](10) NULL,
    [Bet_Pool_Modifier] [varchar](5) NULL,
    [RecordID] [int] IDENTITY(1,1) NOT NULL,
    [serial_number] [char](15) NULL,
    [handle]  AS 
       (CONVERT([money],[total_bet_amount],(0))-CONVERT([money],[refund_amt],(0))),
    [txdatetime]  AS (CONVERT([datetime],([tx_date]+' ')+[time],(11))),
    [dbdate]  AS (CONVERT([datetime],[_date],(11))),
    [Audit_Trail] [varchar](20) NULL,
 CONSTRAINT [PK_AmtoteAccountActivity_holding] PRIMARY KEY NONCLUSTERED 
(
    [RecordID] 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
SET ANSI_PADDING OFF

(Index clusterisé)

USE [Temp_holding_EWS]
GO
/****** Object:  Index [IX_AmtoteAccountActivity_holding] 
    Script Date: 02/17/2017 21:08:44 ******/
CREATE CLUSTERED INDEX [IX_AmtoteAccountActivity_holding] ON 
    [dbo].[AmtoteAccountActivity_holding] 
(
    [AccountNumber] ASC,
    [_Date] ASC,
    [Program_Name] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
    SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF,
ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

Table de destination

USE [EWS]
GO
/****** Object:  Table [dbo].[AmtoteAccountActivity]    
Script Date: 02/17/2017 20:48:16 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[AmtoteAccountActivity](
    [_Date] [char](8) NULL,     [Community] [varchar](10) NULL,
    [AccountNumber] [varchar](50) NULL,
    [Branch] [varchar](10) NULL,[Window] [varchar](3) NULL,
    [Time] [char](8) NULL,  [Balance_Forward] [varchar](10) NULL,
    [Transaction_Type] [varchar](10) NULL,
    [Program_Name] [varchar](10) NULL,
    [Race] [varchar](10) NULL,
    [Pool_Type] [varchar](10) NULL,
    [Amount] [money] NULL,[Runners] [varchar](60) NULL,
    [Total_Bet_Amount] [varchar](10) NULL,
    [Debit_Amount] [varchar](10) NULL,
    [Credit_Amount] [varchar](10) NULL,
    [Tx_Date] [char](8) NULL,
    [Check_Clear_Date] [varchar](10) NULL,
    [Refund_Amt] [varchar](10) NULL,
    [Bet_Pool_Modifier] [varchar](5) NULL,
    [RecordID] [int] IDENTITY(1,1) NOT NULL,
    [serial_number] [varchar](15) NULL,
    [handle]  AS 
       (CONVERT([money],[total_bet_amount],(0))-CONVERT([money],[refund_amt],(0))),
    [txdatetime]  AS (CONVERT([datetime],([tx_date]+' ')+[time],(11))),
    [dbdate]  AS (CONVERT([datetime],[_date],(11))),
    [Audit_Trail] [varchar](20) NULL,
 CONSTRAINT [PK_AmtoteAccountActivity2] PRIMARY KEY NONCLUSTERED 
(
    [RecordID] 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
SET ANSI_PADDING OFF

(Index clusterisé)

USE [EWS]
GO
/****** Object:  Index [IX_AmtoteAccountActivity2]  Script Date: 02/17/2017 21:06:29 ******/
CREATE CLUSTERED INDEX [IX_AmtoteAccountActivity2] ON [dbo].[AmtoteAccountActivity] 
(
    [AccountNumber] ASC,
    [_Date] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, 
ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]

( Remarque: Pour tous ceux qui se demandent pourquoi il y a apparemment des valeurs financières et numériques stockées dans des champs de caractères: c'était la conception originale du tableau il y a 17 ans (pas par moi ) et il y a maintenant des centaines de requêtes SQL qui s'exécutent sur cette base de données, il est moins difficile de les conserver en tant que varchar et les requêtes conservent leur casting, que de les changer en argent, int ou décimal et de changer des centaines de requêtes)

7
MrVimes

Créons trois tables avec une colonne varchar, deux d'entre elles autorisant NULL, une non.

CREATE TABLE dbo.x1(id int IDENTITY(1,1) PRIMARY KEY, field varchar(5) null);
CREATE TABLE dbo.x2(id int IDENTITY(1,1) PRIMARY KEY, field varchar(5) null);
CREATE TABLE dbo.x3(id int IDENTITY(1,1) PRIMARY KEY, field varchar(5) not null);

Remplissez-les avec 1000000 lignes:

;WITH x(x) AS (SELECT 0 UNION ALL SELECT x+1 FROM x WHERE x < 1000000)
INSERT dbo.x1(field) SELECT NULL FROM x OPTION (MAXRECURSION 0);
INSERT dbo.x2(field) SELECT '' FROM dbo.x1;
INSERT dbo.x3(field) SELECT '' FROM dbo.x1;

Vérifions la taille:

SELECT COUNT(*)*8192/1024. FROM sys.dm_db_database_page_allocations(DB_ID(), 
  OBJECT_ID(N'dbo.x1'), 1, NULL, 'DETAILED');
SELECT COUNT(*)*8192/1024. FROM sys.dm_db_database_page_allocations(DB_ID(), 
  OBJECT_ID(N'dbo.x2'), 1, NULL, 'DETAILED');
SELECT COUNT(*)*8192/1024. FROM sys.dm_db_database_page_allocations(DB_ID(), 
  OBJECT_ID(N'dbo.x3'), 1, NULL, 'DETAILED');

Résultats:

12,928 KB
12,936 KB
12,936 KB

Il ressemble donc à 1 000 000 de lignes, en choisissant NULL plutôt que '' enregistre un énorme 8 Ko (et cela ne se reflète même pas dans sp_spaceused, car cette page que vous avez enregistrée est toujours réservée, mais pas allouée).

Répété pour un tas (encore une fois, nous devons faire plusieurs tests car nous devinons votre structure de table réelle):

12,872 KB
12,872 KB
12,928 KB

Donc, négligeable, comme je l'ai suggéré, même en extrapolant plus de 120 000 000 lignes, la plus grande différence possible (une fois de plus, selon votre schéma) serait de 960 Ko sur une table appropriée et de 6,7 Mo sur un tas. Si votre serveur est si restreint en espace disque que 6,7 Mo vont prendre des décisions, vous pourriez envisager combien coûterait un disque supplémentaire par rapport au temps que vous passez à enquêter sur cela.

À mon humble avis, il existe des raisons beaucoup plus importantes entre la décision d'utiliser des valeurs NULL ou de ne pas représenter "aucune donnée". Une bonne question avec beaucoup d'opinions et de commentaires est ici:

9
Aaron Bertrand

Consultez cet article, qui explique comment SQL stocke les valeurs NULL .

Fondamentalement, une colonne de largeur variable (varchar) stocke un bitmap qui indique null ou non null. S'il est nul, zéro octet est alloué pour le champ varchar et le bit est retourné.

Pour les colonnes de largeur fixe (char), le champ entier est toujours alloué, sans aucune donnée stockée dedans. Un champ de 10 octets allouera donc 10 octets, NULL ou non.

Cet article fait une insertion avec des données, avec NULL et avec une chaîne vide. Il interroge ensuite la taille de la page pour voir ce qui se passe en interne.

Pour les chaînes Null et Empty, 0 octet est alloué aux champs varchar.

3
CaM

Pour au moins le format d'enregistrement standard ( FixedVar ), cela ne fait aucune différence sur la quantité d'espace consommée par la table (cela peut faire une différence marginale pour les index, comme expliqué plus loin).

Un varchar nul et une chaîne vide sont stockés exactement de la même manière. La seule façon de les distinguer est de savoir s'il y avait un 1 ou 0 dans le bitmap nul. Ils prennent tous les deux une longueur nulle dans la section des données de colonne de longueur variable et les deux peuvent également éviter de prendre deux octets dans le tableau de décalage de colonne variable s'ils ne sont suivis par aucune colonne contenant des données.

Un des commentaires dit

Pour la table où les valeurs NULL ne sont pas autorisées, vous souhaitez définir la colonne comme NON NULL, pour un certain nombre de raisons, dont la plus importante est que cela supprime l'exigence d'un bitmap nul pour cette colonne.

Ce n'est pas vrai pour les pages de données. Voir Mythe # 6b: Le bitmap nul ne contient que des bits pour les colonnes nullables .

Il existe une légère différence pour les index dans la mesure où si toutes les colonnes participant à un index ne peuvent pas être annulées, le bitmap nul est omis.

Cependant, la différence est négligeable et vous devriez choisir l'option qui vous donne la sémantique souhaitée.

2
Martin Smith