{"id":1867,"date":"2023-04-24T06:00:08","date_gmt":"2023-04-24T13:00:08","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sql\/?p=1867"},"modified":"2023-04-23T16:02:12","modified_gmt":"2023-04-23T23:02:12","slug":"using-ledger-in-azure-sql-database","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sql\/using-ledger-in-azure-sql-database\/","title":{"rendered":"Using Ledger in Azure SQL Database"},"content":{"rendered":"<p>Transactions have to be protected and you need to know when something is altered or prevent altering of records from the start. Ledger, available in Azure SQL Database and SQL Server 2022, helps to protect your database and establish trust, accuracy and integrity in your data.<\/p>\n<p>Ledger accomplishes this goal in a couple of ways. First, it provides you with a history of records in your database; if a row was updated or deleted in the database, its previous value is maintained and protected in a history table. This provides you with a chronicle of all changes made to the table over time. You can even enable this feature database wide so that all tables can have this historical information.<\/p>\n<p>The second way Ledger helps is by creating insert only tables. Update and deletes cannot be done on these tables making them perfect for scenarios such as security information and event management (SIEM) applications. Both these features incorporate a blockchain that reside in the Azure SQL Database ledger. More on how Azure SQL Database uses blockchain at the end of this post.<\/p>\n<p>For database and application developers, this is a very simple to enable feature that will help to create trust between the data and your stakeholders.<\/p>\n<h2>Create an Updatable Ledger Table<\/h2>\n<p><div class=\"alert alert-info\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Important!<\/strong><\/p>Creating ledger tables requires the ENABLE LEDGER permission.<\/div><\/p>\n<p>Let&#8217;s create some of these tables and see how they work. We will start with an <strong>Updatable Ledger Table<\/strong>. To note, both updatable ledger tables and <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/tables\/temporal-tables?view=sql-server-ver16\" data-linktype=\"relative-path\">temporal tables<\/a> are system-versioned tables; they are created with the SYSTEM_VERSIONING clause. When set, the database engine captures historical row versions in a secondary history table (<span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">SYSTEM_VERSIONING = ON (HISTORY_TABLE = [SCHEMA].[HISTORY_TABLE_NAME]). <\/span>The main difference is that updatable ledger tables make both the current and historical data tamper evident.<\/p>\n<p>The code to create this table is quite simple, just like a regular table, but with <span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\"><strong>LEDGER = ON<\/strong> in the SYSTEM_VERSIONING\u00a0<\/span>clause. For this example, we can use a Video Game high scores table so that we can record who got what score on a particular game and know if someone went and inflated\/changed their score.<\/p>\n<div>\n<pre>CREATE TABLE [dbo].[HighScores]\r\n(\r\n \u00a0  [ScoreID] INT IDENTITY,\r\n  \u00a0 [PlayerID] INT NOT NULL,\r\n  \u00a0 [FirstName] VARCHAR (50) NOT NULL,\r\n  \u00a0 [LastName] VARCHAR (50) NOT NULL,\r\n  \u00a0 [Game] VARCHAR (50) NOT NULL,\r\n  \u00a0 [Score] INT NOT NULL,\r\n \u00a0  [Date] DATE NOT NULL\r\n)\r\nWITH\r\n(\r\n SYSTEM_VERSIONING = ON (HISTORY_TABLE = [dbo].[HighScoresHistory]),\r\n LEDGER = ON\r\n);<\/pre>\n<div>\n<p>The database automatically adds four\u00a0<code><strong>GENERATED ALWAYS<\/strong><\/code> columns that contain metadata noting which transactions made changes to the table and the order of operations by which rows were updated.<\/p>\n<div class=\"has-inner-focus\">\n<table class=\"table table-sm\" style=\"width: 100%\" aria-label=\"Table 1\">\n<thead>\n<tr>\n<th style=\"width: 28.3717%\">Default column name<\/th>\n<th style=\"width: 11.9497%\">Data type<\/th>\n<th style=\"width: 65.5139%\">Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td style=\"width: 28.3717%\">ledger_start_transaction_id<\/td>\n<td style=\"width: 11.9497%\">bigint<\/td>\n<td style=\"width: 65.5139%\">The ID of the transaction that created a row version<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 28.3717%\">ledger_end_transaction_id<\/td>\n<td style=\"width: 11.9497%\">bigint<\/td>\n<td style=\"width: 65.5139%\">The ID of the transaction that deleted a row version<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 28.3717%\">ledger_start_sequence_number<\/td>\n<td style=\"width: 11.9497%\">bigint<\/td>\n<td style=\"width: 65.5139%\">The sequence number of an operation within a transaction that created a row version<\/td>\n<\/tr>\n<tr>\n<td style=\"width: 28.3717%\">ledger_end_sequence_number<\/td>\n<td style=\"width: 11.9497%\">bigint<\/td>\n<td style=\"width: 65.5139%\">The sequence number of an operation within a transaction that deleted a row version<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>And we can see this in the table structure after the create table event (Azure Data Studio even marks the table as an <strong>Updatable Ledger<\/strong> table as well):<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.08.58-PM.png\"><img decoding=\"async\" class=\"alignnone wp-image-1919\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.08.58-PM.png\" alt=\"Image Screenshot 2023 04 20 at 1 08 58 PM\" width=\"422\" height=\"344\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.08.58-PM.png 686w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.08.58-PM-300x245.png 300w\" sizes=\"(max-width: 422px) 100vw, 422px\" \/><\/a><\/p>\n<h3>Working With the Table<\/h3>\n<p>Time to add some data to the table:<\/p>\n<div>\n<pre>INSERT INTO [dbo].[HighScores]\r\nVALUES (1, 'Steve', 'Wobble', 'Horse Monkey', 11099912, GETDATE()),\r\n(2, 'Bill', 'Mickelson', 'Horse Monkey', 11099911, GETDATE()),\r\n(3, 'John', 'Hill', 'Universe Invaders', 218870, GETDATE());<\/pre>\n<\/div>\n<\/div>\n<p>and look at the table with the generated columns:<\/p>\n<div>\n<pre>select\r\n   [ScoreID],\r\n  \u00a0[PlayerID],\r\n  \u00a0[FirstName],\r\n  \u00a0[LastName],\r\n  \u00a0[Game],\r\n  \u00a0[Score],\r\n  \u00a0[Date],\r\n  \u00a0[ledger_start_transaction_id],\r\n  \u00a0[ledger_end_transaction_id],\r\n  \u00a0[ledger_start_sequence_number],\r\n  \u00a0[ledger_end_sequence_number]\r\nfrom [dbo].[HighScores];<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<div>For each updatable ledger table, there is a view created. For our HighScores table, the view <span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">[dbo].[HighScores_Ledger] was created.<\/span><\/div>\n<div><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.10.08-PM.png\"><img decoding=\"async\" class=\"alignnone wp-image-1920\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.10.08-PM.png\" alt=\"Image Screenshot 2023 04 20 at 1 10 08 PM\" width=\"385\" height=\"307\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.10.08-PM.png 706w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.10.08-PM-300x240.png 300w\" sizes=\"(max-width: 385px) 100vw, 385px\" \/><\/a><\/div>\n<div><\/div>\n<div>This table has four special columns as well:<\/div>\n<div>\n<table class=\"table table-sm\" aria-label=\"Table 2\">\n<thead>\n<tr>\n<th>Default column name<\/th>\n<th>Data type<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ledger_transaction_id<\/td>\n<td>bigint<\/td>\n<td>The ID of the transaction that created or deleted a row version.<\/td>\n<\/tr>\n<tr>\n<td>ledger_sequence_number<\/td>\n<td>bigint<\/td>\n<td>The sequence number of a row-level operation within the transaction on the table.<\/td>\n<\/tr>\n<tr>\n<td>ledger_operation_type<\/td>\n<td>tinyint<\/td>\n<td>Contains\u00a0<code>1<\/code>\u00a0(<strong>INSERT<\/strong>) or\u00a0<code>2<\/code>\u00a0(<strong>DELETE<\/strong>). Inserting a row into the ledger table produces a new row in the ledger view that contains\u00a0<code>1<\/code>\u00a0in this column. Deleting a row from the ledger table produces a new row in the ledger view that contains\u00a0<code>2<\/code>\u00a0in this column. Updating a row in the ledger table produces two new rows in the ledger view. One row contains\u00a0<code>2<\/code>\u00a0(<strong>DELETE<\/strong>), and the other row contains\u00a0<code>1<\/code>\u00a0(<strong>INSERT<\/strong>) in this column.<\/td>\n<\/tr>\n<tr>\n<td>ledger_operation_type_desc<\/td>\n<td>nvarchar(128)<\/td>\n<td>Contains\u00a0<code>INSERT<\/code>\u00a0or\u00a0<code>DELETE<\/code>. For more information, see the preceding row.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<div>Now, what if someone went into the database and updated that row? And can the database tell us who? Join the ledger view to the <span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">sys.database_ledger_transactions table and get that &#8220;who&#8221;.<\/span><\/div>\n<div>\n<div>\n<pre>SELECT\r\n t.[commit_time] AS [CommitTime],\r\n t.[principal_name] AS [UserName],\r\n h.[ScoreID],\r\n h.[PlayerID],\r\n h.[FirstName],\r\n h.[LastName],\r\n h.[Game],\r\n h.[Score],\r\n h.[Date],\r\n h.[ledger_operation_type_desc] AS Operation\r\n FROM [dbo].[HighScores_Ledger] h\r\n JOIN sys.database_ledger_transactions t\r\n ON t.transaction_id = h.ledger_transaction_id\r\n ORDER BY t.commit_time DESC;<\/pre>\n<\/div>\n<div><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1923\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM.png\" alt=\"Image Screenshot 2023 04 20 at 1 18 13 PM\" width=\"2020\" height=\"226\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM.png 2020w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM-300x34.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM-1024x115.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM-768x86.png 768w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.18.13-PM-1536x172.png 1536w\" sizes=\"(max-width: 2020px) 100vw, 2020px\" \/><\/a><\/div>\n<div><\/div>\n<\/div>\n<h3>Here Comes Bill<\/h3>\n<div>I am currently logged into the database as sqladmin and it shows because I created those inserts. What if someone else were to do this with another account, maybe Bill?<\/div>\n<div><strong>(Bill is using the new Azure Data Studio Create User feature!)<\/strong><\/div>\n<div><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-110919.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1889\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-110919.png\" alt=\"Image Screenshot 2023 04 19 110919\" width=\"477\" height=\"344\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-110919.png 477w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-110919-300x216.png 300w\" sizes=\"(max-width: 477px) 100vw, 477px\" \/><\/a><\/div>\n<div><\/div>\n<div>Then, Bill does an update to the table:<\/div>\n<div><\/div>\n<div>\n<div>\n<pre>UPDATE [dbo].[HighScores]\r\n  \u00a0 SET Score = 11099913\r\n  \u00a0 WHERE ScoreID = 2;<\/pre>\n<div><\/div>\n<div>What&#8217;s the ledger view look like now?<\/div>\n<\/div>\n<\/div>\n<div><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1925\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM.png\" alt=\"Image Screenshot 2023 04 20 at 1 21 29 PM\" width=\"1986\" height=\"316\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM.png 1986w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM-300x48.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM-1024x163.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM-768x122.png 768w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-1.21.29-PM-1536x244.png 1536w\" sizes=\"(max-width: 1986px) 100vw, 1986px\" \/><\/a><\/div>\n<div><\/div>\n<div>In the blue box, we see that Bill performed the record change. In the green box, we see the old record marked as a delete and in the orange box, the new updated record.<\/div>\n<div>Why a delete? From the docs:<\/div>\n<div>\n<table style=\"border-collapse: collapse;width: 100%\">\n<tbody>\n<tr>\n<td style=\"width: 99.9301%\">Contains\u00a0<code>1<\/code>\u00a0(<strong>INSERT<\/strong>) or\u00a0<code>2<\/code>\u00a0(<strong>DELETE<\/strong>). Inserting a row into the ledger table produces a new row in the ledger view that contains\u00a0<code>1<\/code>\u00a0in this column. Deleting a row from the ledger table produces a new row in the ledger view that contains\u00a0<code>2<\/code>\u00a0in this column. Updating a row in the ledger table produces two new rows in the ledger view. One row contains\u00a0<code>2<\/code>\u00a0(<strong>DELETE<\/strong>), and the other row contains\u00a0<code>1<\/code>\u00a0(<strong>INSERT<\/strong>) in this column.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>&nbsp;<\/p>\n<h2>Create an Append-only Ledger Table<\/h2>\n<p><strong>Append-only ledger tables<\/strong> allow INSERT operations on the table; DELETES and UPDATES cannot be done. This is great for use cases such as recoding entry into a building or room with a key card; any scenario where you need to insert events as they happen.<\/p>\n<p>Creating one is similar to the Updatable table, but with the following clause: <strong><span class=\"hljs-keyword\">WITH<\/span> (LEDGER = <span class=\"hljs-keyword\">ON<\/span> (APPEND_ONLY = <span class=\"hljs-keyword\">ON<\/span>))<\/strong>. For this next example, we can imagine a special room where these arcade games are kept and only by using a special ID card, can you get access to play them for high scores. You need to use a badge to enter and exit this room.<\/p>\n<pre><span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">CREATE TABLE [dbo].[ArcadeRoomAccess]\r\n<\/span>  \u00a0(\r\n \u00a0 \u00a0  [AccessID] INT IDENTITY,\r\n  \u00a0 \u00a0 [PlayerID] INT NOT NULL,\r\n  \u00a0 \u00a0 [AccessEvent] NVARCHAR (100) NOT NULL,\r\n  \u00a0 \u00a0 [Timestamp] Datetime2 NOT NULL\r\n  \u00a0)\r\n  \u00a0WITH (LEDGER = ON (APPEND_ONLY = ON));<\/pre>\n<div>\n<div><span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">Again, just like the Updatable table, the database automatically adds <\/span><code style=\"text-align: var(--bs-body-text-align)\"><strong>GENERATED ALWAYS<\/strong><\/code><span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\"> columns. Here we only have two:<\/span><\/div>\n<\/div>\n<table class=\"table table-sm\" aria-label=\"Table 1\">\n<thead>\n<tr>\n<th>Default column name<\/th>\n<th>Data type<\/th>\n<th>Description<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>ledger_start_transaction_id<\/td>\n<td>bigint<\/td>\n<td>The ID of the transaction that created a row version<\/td>\n<\/tr>\n<tr>\n<td>ledger_start_sequence_number<\/td>\n<td>bigint<\/td>\n<td>The sequence number of an operation within a transaction that created a row version<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Which we can see in Azure Data Studio by looking at the table&#8217;s columns:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123540.png\"><img decoding=\"async\" class=\"alignnone wp-image-1901\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123540.png\" alt=\"Image Screenshot 2023 04 19 123540\" width=\"439\" height=\"222\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123540.png 374w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123540-300x152.png 300w\" sizes=\"(max-width: 439px) 100vw, 439px\" \/><\/a><\/p>\n<p>A view is also created:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123522.png\"><img decoding=\"async\" class=\"alignnone wp-image-1902\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123522.png\" alt=\"Image Screenshot 2023 04 19 123522\" width=\"445\" height=\"264\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123522.png 391w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-19-123522-300x178.png 300w\" sizes=\"(max-width: 445px) 100vw, 445px\" \/><\/a><\/p>\n<p>Now, let&#8217;s insert some data into this table:<\/p>\n<div>\n<pre>INSERT INTO [dbo].[ArcadeRoomAccess]\r\nVALUES(1,'IN',DATEADD(HOUR, -5,getdate())),\r\n(1,'OUT',DATEADD(HOUR, -3,getdate())),\r\n(2,'IN',DATEADD(HOUR, -15,getdate())),\r\n(2,'OUT',DATEADD(HOUR, -10,getdate()));<\/pre>\n<\/div>\n<p>And we can see the ledger information with the views and generated columns with the following SQL query:<\/p>\n<div>\n<pre>SELECT\r\nt.[commit_time] AS [CommitTime],\r\nt.[principal_name] AS [UserName],\r\nh.[AccessID],\r\nh.[PlayerID],\r\nh.[AccessEvent],\r\nh.[Timestamp],\r\nh.[ledger_operation_type_desc] AS Operation\r\nFROM [dbo].[ArcadeRoomAccess_Ledger] h\r\nJOIN sys.database_ledger_transactions t\r\nON t.transaction_id = h.ledger_transaction_id\r\nORDER BY t.commit_time DESC;<\/pre>\n<div><\/div>\n<\/div>\n<h3>Update the Un-Updatable<\/h3>\n<p>Trying an UPDATE or DELETE results in the following:<\/p>\n<div>\n<pre>UPDATE [dbo].[ArcadeRoomAccess]\r\nSET Timestamp = GETDATE()\r\nWHERE AccessID = 4;<\/pre>\n<\/div>\n<div>and we see the following error:<\/div>\n<div>\n<div id=\"list_id_1_0\" class=\"monaco-list-row\" role=\"treeitem\" data-index=\"0\" data-last-element=\"false\" data-parity=\"even\" aria-label=\"Started executing query at Line 118\">\n<div class=\"monaco-tl-row\">\n<div class=\"monaco-tl-contents\">\n<pre class=\"batch-start\"><span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">Msg 37359, Level 16, State 1, Line 1\r\n<\/span>Updates are not allowed for the append only Ledger table 'dbo.ArcadeRoomAccess'.<\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n<h2>Database Ledger<\/h2>\n<p>Taking a step back, how does the database use blockchain with Ledger? To start, any rows modified by a transaction (insert, update, or delete) in a table that is ledger enabled are cryptographically SHA-256 hashed using a Merkle tree data structure. This event also creates a root hash representing all rows in the database transaction. At a set interval, transactions are also SHA-256 hashed together through a Merkle tree data structure resulting in a root hash that forms a block. This sets up a process for the block to be SHA-256 hashed through the root hash of the block, along with the root hash of the previous block thus forming a blockchain.<\/p>\n<p>Putting these two processes together, we have the following flow in the database when a transaction occurs:<\/p>\n<ul>\n<li>Persist the earlier version of the row in the history table.<\/li>\n<li>Assign the transaction ID and generate a new sequence number, persisting them in the appropriate system columns.<\/li>\n<li>Serialize the row content and include it when computing the hash for all rows updated by this transaction.<\/li>\n<\/ul>\n<p>You can view the information about these blocks in two system catalog views:<\/p>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-catalog-views\/sys-database-ledger-transactions-transact-sql?view=sql-server-ver16\" data-linktype=\"relative-path\">sys.database_ledger_transactions<\/a>: Maintains a row with the information of each transaction in the database ledger. T<\/li>\n<li><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/system-catalog-views\/sys-database-ledger-blocks-transact-sql?view=sql-server-ver16\" data-linktype=\"relative-path\">sys.database_ledger_blocks<\/a>: Maintains a row for every block in the ledger, including the root of the Merkle tree over the transactions within the block and the hash of the previous block to form a blockchain.<\/li>\n<\/ul>\n<p>To view the database ledger, run the following statements in your database.<\/p>\n<p><div class=\"alert alert-info\"><p class=\"alert-divider\"><i class=\"fabric-icon fabric-icon--Info\"><\/i><strong>Important!<\/strong><\/p>Querying these views requires the VIEW LEDGER CONTENT permission<\/div><\/p>\n<pre><span class=\"hljs-keyword\">SELECT<\/span> * <span class=\"hljs-keyword\">FROM<\/span> sys.database_ledger_transactions;\r\nGO\r\n\r\n<span class=\"hljs-keyword\">SELECT<\/span> * <span class=\"hljs-keyword\">FROM<\/span> sys.database_ledger_blocks;\r\nGO<\/pre>\n<p>Here is what it looks like in the test database i&#8217;ve been using for this post:<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1910\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM.png\" alt=\"Image Screenshot 2023 04 20 at 12 34 50 PM\" width=\"2016\" height=\"718\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM.png 2016w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM-300x107.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM-1024x365.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM-768x274.png 768w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.34.50-PM-1536x547.png 1536w\" sizes=\"(max-width: 2016px) 100vw, 2016px\" \/><\/a><\/p>\n<h3>Database Digests<\/h3>\n<p>Lastly, the hash of the latest block in a database ledger is called the <a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/ledger\/ledger-digest-management#database-digests\">database digest<\/a>. These digests are JSON docs you can generate from T-SQL via the following command:<\/p>\n<pre><span class=\"hljs-keyword\">EXECUTE<\/span> sp_generate_database_ledger_digest;<\/pre>\n<p>But even better, you have the option of having these JSON files placed in <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/storage\/blobs\/immutable-storage-overview\" data-linktype=\"absolute-path\">immutable storage feature of Azure Blob Storage<\/a> with <a href=\"https:\/\/learn.microsoft.com\/en-us\/azure\/confidential-ledger\/index\" data-linktype=\"absolute-path\">Azure Confidential Ledger<\/a>. This helps to protect these digests from potential tampering. Setting this up is simple because you can configure automatic generation and storage of database digests through the Azure portal, PowerShell, or the Azure CLI.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.42.43-PM.png\"><img decoding=\"async\" class=\"alignnone size-full wp-image-1911\" src=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.42.43-PM.png\" alt=\"Image Screenshot 2023 04 20 at 12 42 43 PM\" width=\"1380\" height=\"264\" srcset=\"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.42.43-PM.png 1380w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.42.43-PM-300x57.png 300w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.42.43-PM-1024x196.png 1024w, https:\/\/devblogs.microsoft.com\/azure-sql\/wp-content\/uploads\/sites\/56\/2023\/04\/Screenshot-2023-04-20-at-12.42.43-PM-768x147.png 768w\" sizes=\"(max-width: 1380px) 100vw, 1380px\" \/><\/a><\/p>\n<p>There is even a built in <span style=\"font-size: 1rem;text-align: var(--bs-body-text-align)\">Database ledger verification process that<\/span> scans all ledger and history tables and discovers evidence of data tampering.<\/p>\n<h2>Summary<\/h2>\n<p>As you can see, Ledger in Azure SQL Database is a very powerful feature that can help you ensure that the data in your database is correct and has not been altered. Enable it via T-SQL or right in the Azure Portal so that all new tables are ledger enabled by default.<\/p>\n<p>Here are some helpful links for more information on Ledger:<\/p>\n<ul>\n<li id=\"what-is-the-database-ledger\"><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/ledger\/ledger-database-ledger\">What is the database ledger?<\/a><\/li>\n<li id=\"migrate-data-from-regular-tables-to-ledger-tables\"><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/ledger\/ledger-how-to-migrate-data-to-ledger-tables\">Migrate data from regular tables to ledger tables<\/a><\/li>\n<li id=\"enable-automatic-digest-storage\"><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/ledger\/ledger-how-to-enable-automatic-digest-storage\">Enable automatic digest storage<\/a><\/li>\n<li id=\"verify-a-ledger-table-to-detect-tampering\"><a href=\"https:\/\/learn.microsoft.com\/en-us\/sql\/relational-databases\/security\/ledger\/ledger-verify-database\">Verify a ledger table to detect tampering<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Transactions have to be protected and you need to know when something is altered or prevent altering of records from the start. Ledger, available in Azure SQL Database and SQL Server 2022, helps to protect your database and establish trust, accuracy and integrity in your data. Ledger accomplishes this goal in a couple of ways. [&hellip;]<\/p>\n","protected":false},"author":95874,"featured_media":1870,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[532,1,563,562],"tags":[510,465,469,565,564,38],"class_list":["post-1867","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-data-studio","category-azure-sql","category-blockchain","category-ledger","tag-azure-sql-database","tag-azuresql","tag-azuresqldb","tag-blockchain","tag-ledger","tag-security"],"acf":[],"blog_post_summary":"<p>Transactions have to be protected and you need to know when something is altered or prevent altering of records from the start. Ledger, available in Azure SQL Database and SQL Server 2022, helps to protect your database and establish trust, accuracy and integrity in your data. Ledger accomplishes this goal in a couple of ways. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/posts\/1867","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=1867"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/posts\/1867\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/media\/1870"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/media?parent=1867"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/categories?post=1867"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sql\/wp-json\/wp\/v2\/tags?post=1867"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}