{"id":1933,"date":"2023-05-01T05:00:38","date_gmt":"2023-05-01T12:00:38","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sql\/?p=1933"},"modified":"2023-04-28T14:54:33","modified_gmt":"2023-04-28T21:54:33","slug":"change-tracking-in-azure-sql-database","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sql\/change-tracking-in-azure-sql-database\/","title":{"rendered":"Change Tracking in Azure SQL Database"},"content":{"rendered":"<p>The Azure SQL Database has <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/track-changes\/track-data-changes-sql-server?view=sql-server-ver16#feature-differences-between-change-data-capture-and-change-tracking\" target=\"_blank\" rel=\"noreferrer noopener\">two main ways to track changes<\/a> with data (rows\/DML) as well as table changes. One of those methods is <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/track-changes\/about-change-tracking-sql-server\" target=\"_blank\" rel=\"noreferrer noopener\">Change Tracking<\/a> with the other being <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/track-changes\/about-change-data-capture-sql-server\" target=\"_blank\" rel=\"noreferrer noopener\">Change Data Capture<\/a>. Today\u2019s post will be going into depth on Change Tracking.<\/p>\n<h2 class=\"wp-block-heading\">Change Tracking and Change Data Capture<\/h2>\n<p>So, what\u2019s the difference between the two? <strong>Change Data Capture<\/strong> provides historical change information while <strong>Change Tracking<\/strong> captures the fact that rows in a table were changed but doesn\u2019t capture the data that was changed. Here is a quick look up table from the documentation:<\/p>\n<figure class=\"wp-block-table is-style-stripes\">\n<table>\n<thead>\n<tr>\n<th class=\"has-text-align-left\" data-align=\"left\">Feature<\/th>\n<th class=\"has-text-align-left\" data-align=\"left\">Change data capture<\/th>\n<th class=\"has-text-align-left\" data-align=\"left\">Change tracking<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\"><strong>Tracked changes<\/strong><\/td>\n<td class=\"has-text-align-left\" data-align=\"left\"><\/td>\n<td class=\"has-text-align-left\" data-align=\"left\"><\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">DML changes<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\"><strong>Tracked information<\/strong><\/td>\n<td class=\"has-text-align-left\" data-align=\"left\"><\/td>\n<td class=\"has-text-align-left\" data-align=\"left\"><\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">Historical data<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">No<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">Whether column was changed<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">DML type<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Yes<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>So why would I use Change Tracking over Change Data Capture? To start, if you don\u2019t need to know about historical data and just want what has changed over a set time period, Change Tracking is for you. Seeing it tells you what has changed and not the actual data that has changed, storage and processing overhead is going to be much less. As for use cases, say you have a table that you have an ETL pipeline run every night to fill a data warehouse\/lake house for analytics. Using Change Tracking, you can get the final version of all the rows that have changed in the past 24 hours and not pull the entire table over for a truncate\/replace operation. This significantly reduces the amount of data moved as well as processing time.<\/p>\n<h2 class=\"wp-block-heading\">Sample Data<\/h2>\n<p>Let\u2019s take this ETL use case and see this Change Tracking in action. To start, create a new Azure SQL Database called <strong>CTDemo<\/strong> and use the <strong>AdventureWorksLT<\/strong> sample data. You can add this sample data in the Azure Portal when creating a new Azure SQL Database. It\u2019s under the <strong>Additional Settings<\/strong> tab.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/SampleData1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1937\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/SampleData1.png\" alt=\"Image SampleData1\" width=\"732\" height=\"534\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/SampleData1.png 732w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/SampleData1-300x219.png 300w\" sizes=\"(max-width: 732px) 100vw, 732px\" \/><\/a><\/p>\n<p>And once the database is up and running, I can use Azure Data Studio to quickly connect to it. Here I called my database CTDemo.<\/p>\n<figure class=\"wp-block-image size-full is-resized\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/connectADS2.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1938\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/connectADS2.png\" alt=\"Image connectADS2\" width=\"490\" height=\"382\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/connectADS2.png 490w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/connectADS2-300x234.png 300w\" sizes=\"(max-width: 490px) 100vw, 490px\" \/><\/a><\/figure>\n<p>Now that we have connected to the database, we can take a quick look at the sample tables and the data in them.<\/p>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/tables1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1939\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/tables1.png\" alt=\"Image tables1\" width=\"255\" height=\"305\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/tables1.png 255w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/tables1-251x300.png 251w\" sizes=\"(max-width: 255px) 100vw, 255px\" \/><\/a><\/figure>\n<h2>Enable Change Tracking<\/h2>\n<p>The table we are interested in is the SalesLT.SalesOrderDetail table. Here we can turn on change tracking and create this sample ETL extract but only include the rows that have been modified (insert\/update\/delete).<\/p>\n<p>Open a new query window and run the following SQL as the sqladmin user to enable Change Tracking:<\/p>\n<pre class=\"wp-block-code\"><code>ALTER DATABASE CTDemo  \r\nSET CHANGE_TRACKING = ON  \r\n(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON);\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/enableCT.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1940\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/enableCT.png\" alt=\"Image enableCT\" width=\"519\" height=\"204\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/enableCT.png 519w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/enableCT-300x118.png 300w\" sizes=\"(max-width: 519px) 100vw, 519px\" \/><\/a><\/figure>\n<p>Two things to note here. First, the <strong>CHANGE_RETENTION<\/strong> parameter. This is used to tell Change Tracking how long to keep the changes around in the tracking tables. The <strong>AUTO_CLEANUP<\/strong> parameter is used to enable or disable the cleanup task that removes old change tracking information that is older than the CHANGE_RETENTION period.<\/p>\n<p>Next, enable the table to be tracked with Change Tracking:<\/p>\n<pre class=\"wp-block-code\"><code>ALTER TABLE [SalesLT].[SalesOrderDetail] \r\nENABLE CHANGE_TRACKING  \r\nWITH (TRACK_COLUMNS_UPDATED = ON)\r\nGO<\/code><\/pre>\n<p>The parameter TRACK_COLUMNS_UPDATED is used to store extra information about which columns were updated to the internal change tracking table. This can be used later to help with ETL and query jobs to find the exact column with the changes but will add some storage overhead seeing we are keeping more information in the tracking tables.<\/p>\n<p>When you enable Change Tracking on a table, it automatically creates a side table to store the changes with the naming format sys.change_tracking_tableID, with tableID being the ID of the table.<\/p>\n<div class=\"wp-block-genesis-blocks-gb-notice gb-font-size-18 gb-block-notice\" data-id=\"ed650c\">\n<div class=\"gb-notice-title\">\n<p>Change Tracking Version Numbers<\/p>\n<\/div>\n<div class=\"gb-notice-text\">\n<p>To understand what change tracking is doing, we need to understand the version numbers it has and how it uses them as well as the side tables it creates.<\/p>\n<p>As you just read, when you enable change tracking on a table, it creates a side table which contains the information on which rows have changed. There is also a system table called syscommittab that is created when DB is created regardless of if change tracking is on or not that is used. These are the tables that are used in conjunction with the version numbers to know what changes to keep and which changes to remove due to the retention period we set when enabling Change Tracking.<\/p>\n<p>Along with these tables are the version numbers. To start, we have the current version number and the function to get this number, CHANGE_TRACKING_CURRENT_VERSION(). This number is associated with the last committed transaction on the table that is being tracked. You alter the data in the table via an insert, update, or delete, this number will increase by one. Next version number is the minimum valid version with the corresponding function CHANGE_TRACKING_MIN_VALID_VERSION(). This version number is the minimum number that we can use to get change tracking information from our tracked tables. Data in the tracking tables that have a number lower than the minimum valid version will have been removed by the cleanup jobs again based on your retention period.<\/p>\n<p>As we use Change Tracking in practice in this post, these version numbers and how to use them will become clearer.<\/p>\n<\/div>\n<\/div>\n<p>We can use the following SQL to query the Change Tracking DMV that contains information about which tables are enabled. This uses the object_name(object_id) function to take in the table ID that\u2019s in the sys.change_tracking_tables and returns the table name to provide more information than just a number for the table.<\/p>\n<pre class=\"wp-block-code\"><code>select object_name(object_id) [table],* from sys.change_tracking_tables;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct_view1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1941\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct_view1.png\" alt=\"Image ct view1\" width=\"1057\" height=\"191\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct_view1.png 1057w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct_view1-300x54.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct_view1-1024x185.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct_view1-768x139.png 768w\" sizes=\"(max-width: 1057px) 100vw, 1057px\" \/><\/a><\/figure>\n<p>Before we go any further, let\u2019s explain what these columns are:<\/p>\n<p><strong>sys.change_tracking_tables <\/strong>Columns<\/p>\n<table style=\"width: 100%\">\n<thead>\n<tr>\n<th class=\"has-text-align-left\" style=\"width: 14.157%\" data-align=\"left\">Column name<\/th>\n<th class=\"has-text-align-left\" style=\"width: 85.7786%\" data-align=\"left\">Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"has-text-align-left\" style=\"width: 14.157%\" data-align=\"left\">object_id<\/td>\n<td class=\"has-text-align-left\" style=\"width: 85.7786%\" data-align=\"left\">\u00a0the ID of the table<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" style=\"width: 14.157%\" data-align=\"left\">is_track_columns_updated_on<\/td>\n<td class=\"has-text-align-left\" style=\"width: 85.7786%\" data-align=\"left\">\u00a0the current state of change tracking on a table with 1 being on and 0 being off<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" style=\"width: 14.157%\" data-align=\"left\">begin_version<\/td>\n<td class=\"has-text-align-left\" style=\"width: 85.7786%\" data-align=\"left\">\u00a0Version of the database when change tracking started for the table. begin_version starts at 0, when a transaction is committed, it gets incremented by one \u2013 new tables get the current version number<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" style=\"width: 14.157%\" data-align=\"left\">cleanup_version<\/td>\n<td class=\"has-text-align-left\" style=\"width: 85.7786%\" data-align=\"left\">\u00a0Version up to which cleanup might have removed change tracking information.<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" style=\"width: 14.157%\" data-align=\"left\">min_valid_version<\/td>\n<td class=\"has-text-align-left\" style=\"width: 85.7786%\" data-align=\"left\">\u00a0Minimum valid version of change tracking information that is available for the table.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>&nbsp;<\/p>\n<h2 class=\"wp-block-heading\">Working with Change Tracking<\/h2>\n<p>Now that we have the background, time to use this in practice. Start with the following query to pull all the line items of order number 71784 from the SalesLT.SalesOrderDetail table:<\/p>\n<pre class=\"wp-block-code\"><code>select * from [SalesLT].[SalesOrderDetail]\r\nwhere SalesOrderID = 71784 \r\norder by SalesOrderDetailID\r\nGO<\/code><\/pre>\n<p>This oder has 43 lines to it. We can issue a update to this order by giving all the lines with a quantity greater than 8 a 10% discount with the following SQL:<\/p>\n<pre class=\"wp-block-code\"><code>update SalesLT.SalesOrderDetail\r\nset UnitPriceDiscount = '0.10'\r\nwhere SalesOrderID = 71784  \r\nand OrderQty &gt; 8;\r\nGO<\/code><\/pre>\n<p>Ten rows updated, now let\u2019s look at the change tracking information for this table. Now with what we know about version numbers, we can look at the current version and expect to see 1. Why a 1? Because one transaction took place, the update we just performed:<\/p>\n<pre class=\"wp-block-code\"><code>select CHANGE_TRACKING_CURRENT_VERSION() 'Current Version';\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/currentVersion1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1942\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/currentVersion1.png\" alt=\"Image currentVersion1\" width=\"493\" height=\"137\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/currentVersion1.png 493w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/currentVersion1-300x83.png 300w\" sizes=\"(max-width: 493px) 100vw, 493px\" \/><\/a><\/figure>\n<p>To look at the tracking information, we need to use the CHANGETABLE function. The function takes the following:\n<strong>CHANGES\u00a0<em>table_name<\/em>\u00a0,\u00a0<em>last_sync_version<\/em>\n<\/strong>Which returns tracking information for all changes to the table you passed in that have occurred since the version that is specified by\u00a0<em>last_sync_version<\/em>. If you pass in null, it will bring back all changes in the table from the retention period forward. If you pass in a later version number, you will get only a subset of changes.<\/p>\n<div class=\"wp-block-genesis-blocks-gb-notice gb-font-size-18 gb-block-notice\" data-id=\"5e5432\">\n<div class=\"gb-notice-text\">\n<p>This <em>last_sync_version<\/em> will be critical if you have an ETL process that runs every 24 hours, but the retention period is 48 hours. The ETL process should be asking for the CHANGE_TRACKING_CURRENT_VERSION and storing that value for the next run so that all changes from that point on are seen. In our example, we are just starting out so we would pass in null to get the initial set of changes but store the value of CHANGE_TRACKING_CURRENT_VERSION for the next job. Say 4 transactions happen and we start our query again. Passing in 1 would get the changes from 1 and up with the CHANGE_TRACKING_CURRENT_VERSION now being 5. We store the value 5 and use it with the next query.<\/p>\n<\/div>\n<\/div>\n<pre class=\"wp-block-code\"><code>SELECT\r\n *\r\nFROM\r\n CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, null) as CT;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1943\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct1.png\" alt=\"Image ct1\" width=\"1319\" height=\"388\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct1.png 1319w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct1-300x88.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct1-1024x301.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct1-768x226.png 768w\" sizes=\"(max-width: 1319px) 100vw, 1319px\" \/><\/a><\/figure>\n<p>And the columns being the following:<\/p>\n<figure class=\"wp-block-table is-style-stripes\">\n<table>\n<thead>\n<tr>\n<th class=\"has-text-align-left\" data-align=\"left\">Column name<\/th>\n<th class=\"has-text-align-left\" data-align=\"left\">Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">SYS_CHANGE_VERSION<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Version value that is associated with the last change to the row<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">SYS_CHANGE_CREATION_VERSION<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Version values that are associated with the last insert operation.<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">SYS_CHANGE_OPERATION<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Specifies the type of change:\n<strong>U<\/strong>\u00a0= Update\n<strong>I<\/strong>\u00a0= Insert\n<strong>D<\/strong>\u00a0= Delete<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">SYS_CHANGE_COLUMNS<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Lists the columns that have changed since the last_sync_version (the baseline). The column names are masked so use <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-functions\/change-tracking-is-column-in-mask-transact-sql?view=sql-server-ver16\" target=\"_blank\" rel=\"noreferrer noopener\">CHANGE_TRACKING_IS_COLUMN_IN_MASK<\/a> to interpret it.<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">SYS_CHANGE_CONTEXT<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">Change context information that you can optionally specify by using the\u00a0<a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-functions\/with-change-tracking-context-transact-sql?view=sql-server-ver16\">WITH<\/a>\u00a0clause as part of an INSERT, UPDATE, or DELETE statement.<\/td>\n<\/tr>\n<tr>\n<td class=\"has-text-align-left\" data-align=\"left\">&lt;primary key column value&gt;<\/td>\n<td class=\"has-text-align-left\" data-align=\"left\">The primary key values for the tracked table. These values uniquely identify each row in the user table.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n<p>Now that we have our changes, we can create an ETL query that we might use to pull the changed rows and apply them into another database for reporting and analytics:<\/p>\n<pre class=\"wp-block-code\"><code>SELECT D.*\r\nFROM SalesLT.SalesOrderDetail AS D\r\nINNER JOIN CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, 0) AS CT\r\nON D.SalesOrderID = CT.SalesOrderID \r\nAND D.SalesOrderDetailID = CT.SalesOrderDetailID;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1944\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe1.png\" alt=\"Image pipe1\" width=\"1359\" height=\"518\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe1.png 1359w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe1-300x114.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe1-1024x390.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe1-768x293.png 768w\" sizes=\"(max-width: 1359px) 100vw, 1359px\" \/><\/a><\/figure>\n<h1 class=\"wp-block-heading\">Playing with Numbers<\/h2>\n<p>Now that we have an understanding on how change tracking initially works, let\u2019s play with some version numbers. We can take a line of this order and give it a \u201cextra special discount for good customers\u201d of 100%. Now Line total is a computed column so when it updates with this discount or the previous update we did, the column will not trigger a change tracking event. Run the following SQL for our special discount. We are going to use the SYS_CHANGE_CONTEXT function to add some flavor:<\/p>\n<pre class=\"wp-block-code\"><code>declare @ct_context varbinary(128) = convert(varbinary(128), N'extra special discount for good customers');\r\n\r\nwith CHANGE_TRACKING_CONTEXT (@ct_context)\r\nupdate SalesLT.SalesOrderDetail\r\nset UnitPriceDiscount = '1.00'\r\nwhere SalesOrderDetailID = 110762;\r\nGO<\/code><\/pre>\n<p>Look at the change tracking table again. First, let\u2019s pass in null:<\/p>\n<pre class=\"wp-block-code\"><code>SELECT *\r\nFROM CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, null) as CT;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct2.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1945\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct2.png\" alt=\"Image ct2\" width=\"1351\" height=\"396\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct2.png 1351w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct2-300x88.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct2-1024x300.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct2-768x225.png 768w\" sizes=\"(max-width: 1351px) 100vw, 1351px\" \/><\/a><\/figure>\n<p>We see 11 rows: the 10 rows from our first update and the one from our last update. You can see the new row has a SYS_CHANGE_VERSION of 2.<\/p>\n<h2>Limit the results<\/h2>\n<p>Back to that ETL process use case. If the process was run with 0 or null, it would again get the initial 10 updates and the new update but that\u2019s not what we want, we want only the new changes. If the process took and stored the CHANGE_TRACKING_CURRENT_VERSION which was 1, it would now pass it into the query, and we would see the following:<\/p>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct3.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1946\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct3.png\" alt=\"Image ct3\" width=\"1351\" height=\"162\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct3.png 1351w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct3-300x36.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct3-1024x123.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct3-768x92.png 768w\" sizes=\"(max-width: 1351px) 100vw, 1351px\" \/><\/a><\/figure>\n<p>That\u2019s a little better. You could imagine a table with thousands of changes a day and getting just the changes from the last ETL process again will help with ETL size and processing time. If you looked at the SYS_CHANGE_CONTEXT column, you do see it needs to be converted into a varchar. Use the following SQL for that:<\/p>\n<pre class=\"wp-block-code\"><code>SELECT convert(varchar(128),sys_change_context) sys_change_context_text, *\r\nFROM CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, 1) as CT;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct4.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1947\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct4.png\" alt=\"Image ct4\" width=\"1353\" height=\"179\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct4.png 1353w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct4-300x40.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct4-1024x135.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct4-768x102.png 768w\" sizes=\"(max-width: 1353px) 100vw, 1353px\" \/><\/a><\/figure>\n<p>And our ETL pipe query would then use the CHANGE_TRACKING_CURRENT_VERSION and run the following query:<\/p>\n<pre class=\"wp-block-code\"><code>SELECT D.*\r\nFROM SalesLT.SalesOrderDetail AS D\r\nINNER JOIN CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, 1) AS CT\r\nON D.SalesOrderID = CT.SalesOrderID \r\nAND D.SalesOrderDetailID = CT.SalesOrderDetailID;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe2.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1948\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe2.png\" alt=\"Image pipe2\" width=\"1342\" height=\"236\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe2.png 1342w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe2-300x53.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe2-1024x180.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/pipe2-768x135.png 768w\" sizes=\"(max-width: 1342px) 100vw, 1342px\" \/><\/a><\/figure>\n<p>And as expected, we only see the details for that single row update. How about inserts and deletes? We can run some of those now and see what we get. Starting with an insert, we can insert a line into our SalesLT.SalesOrderDetail table:<\/p>\n<pre class=\"wp-block-code\"><code>insert into SalesLT.SalesOrderDetail (SalesOrderID, OrderQty, ProductID, UnitPrice, UnitPriceDiscount)\r\nvalues (71784, 100, 999, '343.6496', '0.50');\r\nGO<\/code><\/pre>\n<p>And view the order with the following SQL:<\/p>\n<pre class=\"wp-block-code\"><code>select * from [SalesLT].[SalesOrderDetail]\r\nwhere SalesOrderID = 71784\r\nand ProductID = 999;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/newOrder1.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1949\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/newOrder1.png\" alt=\"Image newOrder1\" width=\"1345\" height=\"199\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/newOrder1.png 1345w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/newOrder1-300x44.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/newOrder1-1024x152.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/newOrder1-768x114.png 768w\" sizes=\"(max-width: 1345px) 100vw, 1345px\" \/><\/a><\/figure>\n<p>The CHANGE_TRACKING_CURRENT_VERSION was 2 before this transaction happened so we use that in our query of the change tracking table. Remember the flow would be:<\/p>\n<ol>\n<li>use stored value<\/li>\n<li>get changes<\/li>\n<li>get current value<\/li>\n<li>and store it for the next query<\/li>\n<\/ol>\n<pre class=\"wp-block-code\"><code>SELECT *\r\nFROM CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, 2) as CT;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct6.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1950\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct6.png\" alt=\"Image ct6\" width=\"1309\" height=\"164\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct6.png 1309w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct6-300x38.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct6-1024x128.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct6-768x96.png 768w\" sizes=\"(max-width: 1309px) 100vw, 1309px\" \/><\/a><\/figure>\n<p>And our ETL query would show this inserted row as well:<\/p>\n<pre class=\"wp-block-code\"><code>SELECT D.*\r\nFROM SalesLT.SalesOrderDetail AS D\r\nINNER JOIN CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, 2) AS CT\r\nON D.SalesOrderID = CT.SalesOrderID \r\nAND D.SalesOrderDetailID = CT.SalesOrderDetailID;\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct7.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1951\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct7.png\" alt=\"Image ct7\" width=\"1335\" height=\"213\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct7.png 1335w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct7-300x48.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct7-1024x163.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct7-768x123.png 768w\" sizes=\"(max-width: 1335px) 100vw, 1335px\" \/><\/a><\/figure>\n<p>What if I were to delete this row I just inserted? And what if my current version was still at 1? What happens? Seeing it\u2019s another transaction, does passing in 2 show the insert and passing in 3 show the delete? Let\u2019s take a look.<\/p>\n<p>Delete the row using the SalesOrderDetailID. For my query, its 113407:<\/p>\n<pre class=\"wp-block-code\"><code>delete from SalesLT.SalesOrderDetail\r\nwhere SalesOrderDetailID = 113407;<\/code><\/pre>\n<p>So what happens when I pass in the previous query using the 2 as the last_sync_value?<\/p>\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct8.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1952\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct8.png\" alt=\"Image ct8\" width=\"1291\" height=\"178\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct8.png 1291w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct8-300x41.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct8-1024x141.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct8-768x106.png 768w\" sizes=\"(max-width: 1291px) 100vw, 1291px\" \/><\/a><\/figure>\n<p>It shows the row as deleted as it should. Even though this row was created in a previous transaction, it no longer exists so for data consistency, it is marked as deleted no matter how many updates it might of had or when it was inserted. And passing in 3 gives the same results as above with the row marked as deleted. Very nice feature indeed!<\/p>\n<h1 class=\"wp-block-heading\">Clean Up<\/h2>\n<p>Let\u2019s change the change retention period see what happens:<\/p>\n<pre class=\"wp-block-code\"><code>ALTER DATABASE CTDemo SET CHANGE_TRACKING (CHANGE_RETENTION = 1 MINUTES);\r\nGO<\/code><\/pre>\n<p>The change tracking auto-cleanup job run about every 30 minutes so we can force the job after we wait one minute for the rows to expire with the following SQL:<\/p>\n<pre class=\"wp-block-code\"><code>CHECKPOINT 1\r\nGO\r\n\r\nexec sys.sp_flush_CT_internal_table_on_demand\r\nGO<\/code><\/pre>\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct9.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1953\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct9.png\" alt=\"Image ct9\" width=\"612\" height=\"360\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct9.png 612w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/ct9-300x176.png 300w\" sizes=\"(max-width: 612px) 100vw, 612px\" \/><\/a><\/figure>\n<p>Passing in no parameters for sys.sp_flush_CT_internal_table_on_demand will clean up all side tables for all change tracking enabled tables, the syscommittab system table, and will also update the invalid and hardened cleanup versions. You can also pass in a table name to cleanup just that tables\u2019s side table. In the above image, we see all 13 rows have been cleaned up from our side table and if you were to issue the query to get all changes from a side table with the CHANGETABLE function, you would get 0 rows:<\/p>\n<pre class=\"wp-block-code\"><code>SELECT *\r\nFROM CHANGETABLE(CHANGES SalesLT.SalesOrderDetail, 0) as CT<\/code><\/pre>\n<h1 class=\"wp-block-heading\">Summary<\/h2>\n<p>Change Tracking is a very powerful feature of the Azure SQL Database. We can use it for many use cases to extract just the rows that have changed over a set amount of time with very little overhead on the database. You can read more about Change Tracking in the documentation here. There are a few key concepts to remember; one is the version numbers are incremented by transactions. Two, remember to get and store the current version number if you are pulling for say an ETL process. That is the number you will need for the next job so that you get just the changes from that version number forward. And three, the retention period will flush the changes from all the change tracked tables so be sure to set an appropriate time that works with your process that is pulling the changes from the database.<\/p>\n<h3>Resources<\/h3>\n<ul>\n<li><a href=\"https:\/\/github.com\/JetterMcTedder\/AzureSQLDB_ChangeTrackingDemo\">Change Tracking Demo Scripts<\/a><\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/track-changes\/about-change-tracking-sql-server\">Change Tracking Documentation<\/a><\/li>\n<li><a href=\"https:\/\/www.youtube.com\/watch?v=XLMMDtOxDAA\">Data Exposed Video on Change Tracking<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>The Azure SQL Database has two main ways to track changes with data (rows\/DML) as well as table changes. One of those methods is Change Tracking with the other being Change Data Capture. Today\u2019s post will be going into depth on Change Tracking. Change Tracking and Change Data Capture So, what\u2019s the difference between the [&hellip;]<\/p>\n","protected":false},"author":95874,"featured_media":1935,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,566],"tags":[510,465,469,567,30,449,34],"class_list":["post-1933","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sql","category-change-tracking","tag-azure-sql-database","tag-azuresql","tag-azuresqldb","tag-change-tracking","tag-developers","tag-development","tag-t-sql"],"acf":[],"blog_post_summary":"<p>The Azure SQL Database has two main ways to track changes with data (rows\/DML) as well as table changes. One of those methods is Change Tracking with the other being Change Data Capture. Today\u2019s post will be going into depth on Change Tracking. Change Tracking and Change Data Capture So, what\u2019s the difference between the [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/posts\/1933","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/users\/95874"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/comments?post=1933"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/posts\/1933\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/media\/1935"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/media?parent=1933"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/categories?post=1933"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/tags?post=1933"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}