{"id":1363,"date":"2012-07-10T00:00:00","date_gmt":"2012-07-10T00:00:00","guid":{"rendered":"https:\/\/test.simple-talk.com\/uncategorized\/null-friendly-using-sparse-columns-and-column-sets-in-sql-server\/"},"modified":"2021-09-29T16:21:48","modified_gmt":"2021-09-29T16:21:48","slug":"null-friendly-using-sparse-columns-and-column-sets-in-sql-server","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/databases\/sql-server\/t-sql-programming-sql-server\/null-friendly-using-sparse-columns-and-column-sets-in-sql-server\/","title":{"rendered":"NULL-Friendly: Using Sparse Columns and Column Sets in SQL Server"},"content":{"rendered":"<div id=\"pretty\">\n<p class=\"start\">Sparse columns and column sets can be used in conjunction, and are ideal for fields that contain mostly NULL values. Sparse columns handle NULL values extremely efficiently; column sets combine all sparse columns into an XML representation as a new column. <\/p>\n<p>Sparse columns are designed to allow a relational database to store and process relatively unstructured data, where any individual&#160;entity may have a modest selection from a very wide set of attributes. This sort of data can be found frequently in scientific, pharmaceutical, and medical applications. <a href=\"http:\/\/en.wikipedia.org\/wiki\/Entity-attribute-value_model\">EAV<\/a> (Entity\/Attribute\/Value) storage techniques were engineered for such data; however, EAV data storage has proved to be somewhat inefficient in such areas as query speed and storage cost. <\/p>\n<p>Sparse columns work well where a high proportion of the data in a column is NULL, because negligible storage space is used for the NULL representation. A table that is wide in theory can be made narrow in practice by implementing sparse columns, and the addition of filtered indexes provides effective searches on the sparse data. Sparse columns support the use of check constraints, as long as the constraints allow NULL values (a sparse column requirement). Constraints introduce basic data type checking, and thus allow for a good range of validation and integrity-checking techniques. <\/p>\n<p>Column sets provide a solution to the difficulties of efficient imports and exports of sparse data. The untyped XML representation that is generated for each row interacts directly with each sparse field. The XML field accepts inserts, and its data can be extracted and modified easily. This feature effectively supports certain types of <a href=\"http:\/\/en.wikipedia.org\/wiki\/Serialization\">serialized<\/a> object data from various applications. <\/p>\n<h2>Sparse Columns <\/h2>\n<p>The SPARSE column property is a special, NULL-friendly column option &#8211; introduced with SQL Server 2008. &#160;Sparse column data storage is extremely efficient for NULL values. In fact, a NULL value requires <strong>no space at all<\/strong> in a sparse column &#8211; <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc280604.aspx\">MSDN<\/a> states that &#8220;&#8230;when the column value is NULL for any row in the table, the values require no storage.&#8221; However, the storage requirement for a non-NULL value is increased by up to 4 bytes when the SPARSE column property is used. Given that trade-off, Microsoft recommends not using sparse columns unless the percentage of NULL values in a column is high enough that a 20 percent to 40 percent storage savings gain would result. The ratio of NULLs to real values that would warrant implementing a sparse column differs for each data type. For example, when using the <strong>datetime<\/strong> type, 52% of the values must be NULL in order to save 40% in storage, but for the <strong>bit<\/strong> data type, 98% must be NULL. Fortunately, Microsoft has compiled a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc280604.aspx\">chart<\/a> to easily find the percentage information for each data type. <\/p>\n<p>Sparse columns are ordinary columns, with the addition of the SPARSE property. To create a table with sparse columns, simply use the SPARSE keyword: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE Royalty\n(&#160;&#160;\n&#160;&#160; Chronology INT PRIMARY KEY,\n&#160;&#160; FirstName VARCHAR(50) NULL,\n&#160;&#160; LastName VARCHAR(50) SPARSE,\n&#160;&#160; CrownYear INT SPARSE,\n&#160;&#160; Region VARCHAR(100) SPARSE,\n&#160;&#160; Motto VARCHAR(100) SPARSE,\n)\nGO<\/pre>\n<p>In order to make a column sparse after-the-fact, use an ALTER TABLE statement: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER TABLE Royalty\nALTER COLUMN FirstName VARCHAR(50) SPARSE\nGO <\/pre>\n<p>This can also be accomplished by changing the &#8216;<strong>Is Sparse<\/strong>&#8216; column property to &#8216;<strong>Yes<\/strong>&#8216; in table design view in the SQL Server Management Studio GUI: <\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image001.png\" alt=\"1519-image001.png\" \/><\/p>\n<p>A sparse column must be NULLABLE, so the NULL keyword is optional. A sparse column is populated in the same manner as a normal column: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT Royalty(Chronology, FirstName, CrownYear, Region, Motto)\nVALUES (1, 'Catherine', 1509, 'Aragon', 'Humble and Loyal')\nGO\n\nINSERT Royalty(Chronology, FirstName, LastName, CrownYear, Motto)\nVALUES (2, 'Anne', 'Boleyn', 1533, 'La Plus Heureuse');\nGO\n\nSELECT * FROM Royalty\nGO<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image002.png\" alt=\"1519-image002.png\" \/><\/p>\n<h2>Comparing NULL Storage <\/h2>\n<p>To demonstrate the space savings gained when using sparse columns, we will create two new tables that contain many NULL values, and then examine the amount of space used for storage in each table. First, we&#8217;ll create an Employees_sparse table with three sparse columns: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE Employees_sparse (\n&#160;&#160; EMP_ID INT IDENTITY(5001,1) PRIMARY KEY, \n&#160;&#160; SSN CHAR(9) NOT NULL, \n&#160;&#160; TITLE CHAR(10) SPARSE NULL, \n&#160;&#160; FIRSTNAME VARCHAR(50) NOT NULL, \n&#160;&#160; MIDDLEINIT CHAR(1) SPARSE NULL, \n&#160;&#160; LASTNAME VARCHAR(50) NOT NULL, \n&#160;&#160; EMAIL CHAR(50) SPARSE NULL)\nGO <\/pre>\n<p>Next, an identical table <strong>without<\/strong> the SPARSE property enabled: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE TABLE Employees (\n&#160;&#160; EMP_ID INT IDENTITY(5001,1) PRIMARY KEY, \n&#160;&#160; SSN CHAR(9) NOT NULL, \n&#160;&#160; TITLE CHAR(10) NULL, \n&#160;&#160; FIRSTNAME VARCHAR(50) NOT NULL, \n&#160;&#160; MIDDLEINIT CHAR(1) NULL, \n&#160;&#160; LASTNAME VARCHAR(50) NOT NULL, \n&#160;&#160; EMAIL CHAR(50) NULL)\nGO <\/pre>\n<p>Before we populate these tables, let&#8217;s add a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms188258(v=sql.105).aspx\">CHECK<\/a> constraint to the EMAIL field. Sparse columns DO support CHECK constraints. We want to ensure that each email address in the table will contain the &#8216;@&#8217; symbol, and that the &#8216;@&#8217; symbol appears <strong>only<\/strong> <strong>once<\/strong> in each email address: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER TABLE Employees_sparse\nADD CONSTRAINT chkEmailEmpSparse CHECK \n(\n&#160;&#160; LEN(REPLACE(EMAIL,'@',''))+1=LEN(EMAIL)\n)\nGO\n\nALTER TABLE Employees\nADD CONSTRAINT chkEmailEmp CHECK \n(\n&#160;&#160; LEN(REPLACE(EMAIL,'@',''))+1=LEN(EMAIL)\n)\nGO<\/pre>\n<p>To save time, we&#8217;ll use <a href=\"http:\/\/www.red-gate.com\/products\/sql-development\/sql-data-generator\/?utm_source=simpletalk&amp;utm_medium=weblink&amp;utm_content=nullfriendly&amp;utm_campaign=sqldatagenerator\">RedGate&#8217;s SQL Data Generator<\/a> to insert <strong>50<\/strong><strong>000<\/strong> Employee records into the Employees_sparse table. <\/p>\n<p>Now that we have test data in the table, let&#8217;s take a look at portion of it to verify that the distribution of data in the sparse columns is realistic: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT TOP 10 * \nFROM Employees_sparse\nGO <\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image003.png\" alt=\"1519-image003.png\" \/><\/p>\n<p>We see a good sampling of the data, where there are very few values in the columns that have the SPARSE property enabled. The Data Generator recognized that TITLE, MIDDLEINIT, and EMAIL are sparse columns, and should contain very few values. <\/p>\n<p>Because we want to compare data storage size between the two tables, we need to insert <strong>identical<\/strong> data into the non-sparse table (Employees). We&#8217;ll copy over all 50000 records from the sparse table: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT INTO Employees (SSN, TITLE, FIRSTNAME, MIDDLEINIT, LASTNAME, EMAIL)\nSELECT SSN, TITLE, FIRSTNAME, MIDDLEINIT, LASTNAME, EMAIL \nFROM Employees_sparse\nGO <\/pre>\n<p>To view the sparse table&#8217;s storage savings, we&#8217;ll run the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms188776.aspx\"><strong>sp_spaceused<\/strong><\/a> system stored procedure on both tables: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">sp_spaceused 'Employees'\nGO\n\nsp_spaceused 'Employees_sparse'\nGO <\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image004.png\" alt=\"1519-image004.png\" \/><\/p>\n<p>We see that the storage difference between the two tables is quite notable, including index sizes, <strong>when the data is very sparse<\/strong>. However, the space savings that were gained can be lost quickly in the event that many NULL values are replaced with real values. <\/p>\n<h2>Sparse Column Limitations <\/h2>\n<p>A few things to keep in mind when considering using sparse columns: <\/p>\n<ul>\n<li>Sparse columns cannot have default values, and must accept NULL values  <\/li>\n<li>A computed column cannot be SPARSE  <\/li>\n<li>Sparse columns do not support data compression  <\/li>\n<li>A sparse column cannot be a primary key <\/li>\n<\/ul>\n<h2>Locating Sparse Columns <\/h2>\n<p>A list of all columns that use the SPARSE property can be obtained via the <strong>is_sparse<\/strong> field in <strong>sys.columns<\/strong>: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT so.name [Table Name], sc.name [Column Name] \nFROM sys.columns sc\nJOIN sys.objects so\nON so.OBJECT_ID = sc.OBJECT_ID\nWHERE is_sparse = 1\nGO <\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image005.png\" alt=\"1519-image005.png\" \/><\/p>\n<h3>Column Sets <\/h3>\n<p>An interesting enhancement to a table that uses sparse columns is a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc280521.aspx\">column set<\/a>. A <strong>sparse column set<\/strong> gathers all sparse columns into a <strong>new<\/strong><strong> column<\/strong> that is similar to a derived or computed column, but with additional functionality &#8211; its data can be updated and selected from directly. A column set is calculated based on the sparse columns in a table, and it generates an <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/ms184277.aspx\">untyped<\/a> XML representation of all sparse columns and values (NULL or otherwise). Keep in mind that a <em>column set is optional<\/em>. Sparse columns may be used without the implementation of a sparse column set. <\/p>\n<p>A column set may be included in a table definition at the time of creation, or added later, if no sparse columns exist yet. A column set may <strong>not<\/strong> be added to a table that contains existing sparse columns: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">ALTER TABLE Royalty\nADD DetailSet XML COLUMN_SET FOR ALL_SPARSE_COLUMNS\n\nMsg 1734, Level 16, State 1, Line 1\nCannot create the sparse column set 'DetailSet' in the table 'Royalty' because the table already contains one or more sparse columns. A sparse column set cannot be added to a table if the table contains a sparse column. <\/pre>\n<p>To include the sparse column set in the Royalty table, we must first drop and then recreate the table: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DROP TABLE Royalty\nGO\n\nCREATE TABLE Royalty\n(&#160;&#160;\n&#160;&#160;&#160;Chronology INT PRIMARY KEY,\n&#160;&#160;&#160;FirstName VARCHAR(50) SPARSE,\n&#160;&#160; LastName VARCHAR(50) SPARSE,\n&#160;&#160; CrownYear INT SPARSE,\n&#160;&#160;&#160;Region VARCHAR(100) SPARSE,\n&#160;&#160; Motto VARCHAR(100) SPARSE,\n&#160;&#160; DetailSet XML COLUMN_SET FOR ALL_SPARSE_COLUMNS \n)\nGO <\/pre>\n<p>We are now able to populate the table in order to demonstrate the behavior of the column set: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT Royalty(Chronology, FirstName, CrownYear, Region, Motto)\nVALUES (1, 'Catherine', 1509, 'Aragon', 'Humble and Loyal');\nGO\n\nINSERT Royalty(Chronology, FirstName, LastName, CrownYear, Motto)\nVALUES (2, 'Anne', 'Boleyn', 1533, 'La Plus Heureuse');\nGO\n\nSELECT * FROM Royalty\nGO <\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-replace1.png\" alt=\"1519-replace1.png\" \/><\/p>\n<p>We can see that the results of a SELECT * FROM Royalty are completely different after the introduction of the column set. Every one of the sparse columns is now gathered into an XML string. Only the non-sparse Chronology field is <strong>not<\/strong> included in the column set. However, each and every sparse column is still accessible individually when invoked by name: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT Chronology, FirstName, LastName, CrownYear, Region, Motto, DetailSet\nFROM Royalty\nGO<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-replace2.png\" alt=\"1519-replace2.png\" \/><\/p>\n<p>Almost every column in the Royalty table makes use of the SPARSE property. Keep in mind that for a column to make good use of the SPARSE property, <strong>it is expected that most of the values will<\/strong><strong> be NULL<\/strong>. <\/p>\n<h3>Column Set Advantages <\/h3>\n<p>What are the benefits of using a sparse column set? For one,<em> the column set itself is directly updatable<\/em>. Because of this, operations performed directly on the column set may be more efficient than when they are performed on individual columns. After an update or an insert, the values that were added to the column set are immediately accessible via the singular sparse column(s): <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">INSERT Royalty(Chronology, DetailSet)\nVALUES (3, '&lt;FirstName&gt;Jane&lt;\/FirstName&gt;&lt;LastName&gt;Seymour&lt;\/LastName&gt;&lt;CrownYear&gt;1536&lt;\/CrownYear&gt;');\nGO\n\nINSERT Royalty(Chronology, DetailSet)\nVALUES (4, '&lt;FirstName&gt;Anne&lt;\/FirstName&gt;&lt;Region&gt;Cleves&lt;\/Region&gt;');\nGO\n\nSELECT Chronology, FirstName, LastName, CrownYear, Region, Motto, DetailSet\nFROM Royalty\nWHERE Chronology IN (3,4)\nGO <\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image008.jpg\" alt=\"1519-image008.jpg\" \/><\/p>\n<p>To demonstrate the efficiency of a column set operation, consider the following situation: the Royalty table is empty, and we have an XML string that contains all of the records that we want in the table. We can insert the data directly to the DetailSet (xml column set) column all at once, with the help of a bit of XQuery. Let&#8217;s remove the existing records in the Royalty table to prepare for the XML import: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DELETE FROM Royalty\nGO <\/pre>\n<p>We have an XML string that contains all the data that we want to put in the empty table: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DECLARE @Royalty XML = '\n&lt;Royalty&gt;\n&#160;&#160;&#160; &lt;Person Chronology=\"1\"&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;FirstName&gt;Catherine&lt;\/FirstName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;CrownYear&gt;1509&lt;\/CrownYear&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;Region&gt;Aragon&lt;\/Region&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;Motto&gt;Humble and Loyal&lt;\/Motto&gt;\n&#160;&#160;&#160; &lt;\/Person&gt;\n&#160;&#160;&#160; &lt;Person Chronology=\"2\"&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;FirstName&gt;Anne&lt;\/FirstName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;LastName&gt;Boleyn&lt;\/LastName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;CrownYear&gt;1533&lt;\/CrownYear&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;Motto&gt;La Plus Heureuse&lt;\/Motto&gt;\n&#160;&#160;&#160; &lt;\/Person&gt;\n&#160;&#160;&#160; &lt;Person Chronology=\"3\"&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;FirstName&gt;Jane&lt;\/FirstName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;LastName&gt;Seymour&lt;\/LastName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;CrownYear&gt;1536&lt;\/CrownYear&gt;\n&#160;&#160;&#160; &lt;\/Person&gt;\n&#160;&#160;&#160; &lt;Person Chronology=\"4\"&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;FirstName&gt;Anne&lt;\/FirstName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;Region&gt;Cleves&lt;\/Region&gt;\n&#160;&#160;&#160; &lt;\/Person&gt;\n&#160;&#160;&#160; &lt;Person Chronology=\"5\"&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;FirstName&gt;Catherine&lt;\/FirstName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;LastName&gt;Howard&lt;\/LastName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;CrownYear&gt;1540&lt;\/CrownYear&gt;\n&#160;&#160;&#160; &lt;\/Person&gt;\n&#160;&#160;&#160; &lt;Person Chronology = \"6\"&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;FirstName&gt;Catherine&lt;\/FirstName&gt;\n&#160;&#160;&#160;&#160;&#160;&#160;&#160; &lt;LastName&gt;Parr&lt;\/LastName&gt;\n&#160;&#160;&#160; &lt;\/Person&gt;\n&lt;\/Royalty&gt;\n' <\/pre>\n<p>Because we have a built-in identifier for each Person node (the Chronology attribute), we can use a SQL variable in a small XQuery script that will serve as a counter for a loop, and will also delineate each Person record for the insert operation: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">DECLARE @i INT = 1\nWHILE @i &lt;= 6\nBEGIN\n&#160;&#160;&#160;&#160;&#160;&#160; INSERT Royalty(Chronology, DetailSet)\n&#160;&#160;&#160;&#160;&#160;&#160; SELECT @i, @Royalty.query('\/Royalty\/Person[@Chronology=sql:variable(\"@i\")]\/child::node()')\n&#160;&#160;&#160;&#160;&#160;&#160; SET @i = @i + 1\n&#160;&#160; END\nGO\n\nSELECT Chronology, FirstName, LastName, CrownYear, Region, Motto, DetailSet\nFROM Royalty\nGO<\/pre>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image009.png\" alt=\"1519-image009.png\" \/><\/p>\n<p>This can streamline otherwise time-consuming data imports. Even if the XML schema changes frequently, or is structured poorly, directly importing it to a customized column set can be relatively easy &#8211; only one representative record of the XML data would have to be examined. A table structure that included appropriate sparse columns could then be constructed, based on the XML schema. <\/p>\n<p>Additionally, the maximum sparse column count per table far outdistances the maximum column count limit for traditional columns &#8211; but only when used in conjunction with a column set. The column count limit without the use of a column set is the same for sparse columns as it is for non-sparse columns &#8211; 1024. If a script were generated to create an all-sparse column table of 2000 columns, but without a column set defined, the following error would result: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">Msg 1702, Level 16, State 1, Line 1\nCREATE TABLE failed because column 'Col_2000' in table 'SPARSE_TBL' exceeds the maximum of 1024 columns. <\/pre>\n<p>Although only one column set per table is permitted, a sparse column set can contain up to <strong>30,000 sparse columns<\/strong> &#8211; a wide table solution for a table that has reached the normal column limit of 1024 columns, and still needs to grow in width. Even though no more than 1024 columns may be returned in a result set at once, the entire XML column set may be returned together. In addition, all columns, sparse or non-sparse, may be reverted to an XML representation (without a root element) by using the FOR XML AUTO clause: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT *\nFROM Royalty\nFOR XML AUTO\nGO<\/pre>\n<p>The first two elements of the XML results of the above query are as follows: <\/p>\n<p class=\"illustration\"><img decoding=\"async\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/imported\/1519-image010.png\" alt=\"1519-image010.png\" \/><\/p>\n<p>Notice that the column set is represented as a node containing child nodes for all sparse columns, while any non-sparse columns (the Chronology column) are depicted as node attributes. <\/p>\n<h3>Column Set Disadvantages <\/h3>\n<p>There are a number of disadvantages to using a column set. As mentioned earlier, only one column set may be used per table, and a column set cannot be added to a table that already contains sparse columns. Replication, distributed queries, and change data capture (CDC) do not support using column sets. Also, a column set cannot be indexed in any way. <\/p>\n<h2>What&#8217;s next? Introducing Filtered Indexes <\/h2>\n<p>Another option to consider for sparse columns is a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/cc280372.aspx\">filtered index<\/a>. A filtered index, when designed properly, can be much smaller and faster than a normal index, because it only stores data that meet certain criteria. This is accomplished by using a WHERE clause in the index definition. The WHERE clause feature makes filtered indexes ideal for sparse columns, as an index can be designed to store only the non-NULL values from a column. A filtered index must be created as a <strong>nonclustered<\/strong> index: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">CREATE NONCLUSTERED INDEX FI_Region\nON Royalty(FirstName, Region)\nWHERE Region IS NOT NULL\nGO <\/pre>\n<p>The above index will be small in size, and should return faster, more efficient results for queries similar to the following: <\/p>\n<pre class=\"theme:ssms2012 lang:tsql\">SELECT FI_Region , Region\nFROM Royalty\nWHERE Region IS NOT NULL\nGO <\/pre>\n<p>Filtered indexes will not store the NULL values on the heap when the WHERE criteria is configured correctly, unlike unfiltered nonclustered indexes. <\/p>\n<h2>Summary <\/h2>\n<p>Sparse columns store NULL values very efficiently for tables that contain a high percentage of NULLs. Sparse columns accommodate drastically improved query speeds when used in conjunction with filtered indexes. When used with column sets, sparse columns extend the traditional column count limit for tables, providing a much better solution for storing unstructured data than older, inefficient EAV methods. Column sets also represent sparse data in an automatically generated XML format that allows easy data imports, exports, and manipulation. <\/p>\n<p>Sparse columns <strong>do not<\/strong> support primary keys, data compression, NOT NULL constraints, or default values. Non-sparse columns may not be altered to have the SPARSE property, but instead must be created with it. Sparse columns implicitly allow NULLs upon table creation. Column sets are not compatible with data replication, distributed queries, or indexes. Sparse columns <strong>are<\/strong> compatible with CHECK constraints and nonclustered indexes. <\/p>\n<p>We&#8217;ve looked at some detailed examples that use sparse columns and column sets. Sparse column solutions offer many benefits when applied correctly, but may be unnecessary if the ratio of NULL values to non-NULL values is not high enough, or if the table is not wide enough to warrant the use of a column set. <\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Sparse columns and column sets in SQL Server 2012 provide a way of accomodating the less-structured data that has always proved to be tricky for the relational model. They can be used very effectively where the attributes are sparse for any given entity and very numerous across all entities. Seth Delconte shows how to use them.&hellip;<\/p>\n","protected":false},"author":221920,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[143531],"tags":[5677,5678,5676,4150,4151,4252,4217],"coauthors":[],"class_list":["post-1363","post","type-post","status-publish","format-standard","hentry","category-t-sql-programming-sql-server","tag-column-sets","tag-null-friendly","tag-sparse-columns","tag-sql","tag-sql-server","tag-t-sql-programming","tag-xml"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1363","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221920"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=1363"}],"version-history":[{"count":4,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1363\/revisions"}],"predecessor-version":[{"id":92534,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/1363\/revisions\/92534"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=1363"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=1363"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=1363"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=1363"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}