<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
  version="2.0">
  <channel>
    <title>Robin Lester</title>
    <atom:link
      href="https://docs.microsoft.com/archive/blogs/robinlester/feed.xml"
      rel="self"
      type="application/rss+xml" />
    <link>https://docs.microsoft.com/archive/blogs/robinlester/feed.xml</link>
    <description>A blog about data</description>
    <lastBuildDate>Thu, 21 Sep 2017 10:07:09 GMT</lastBuildDate>
    <language>en-US</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
    <item>
      <title>Creating a Document's Keyword Wordcloud with Azure Cognitive Services and R</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/creating-a-documents-keyword-wordcloud-with-azure-cognitive-services-and-r</link>
      <pubDate>Thu, 21 Sep 2017 10:07:09 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=586</guid>
      <description><![CDATA[In this post we will look at how to utilize the Azure Cognitive Services APIs from R to add this...]]></description>
      <content:encoded><![CDATA[In this post we will look at how to utilize the Azure Cognitive Services APIs from R to add this extend to take advantage of the pretrained deep neural networks implemented as part of Azure Cognitive Services.

Specifically we will look at the KeyPhrases API and how we can use it to give a visual understanding of the contents of a document. This will give an instant understanding of a large body of text without needing to scan though the document itself. Alternatively, this can also be used to categorise the document contents for statistical purposes or for document retrieval.

In the following example we are taking letters of complaints and understanding the complaint at a glance. It should also be noted that if you were receiving letter as a physical written correspondence you could use handwriting recognition as part of the Computer Vision API <a href="https://azure.microsoft.com/en-gb/services/cognitive-services/computer-vision/">https://azure.microsoft.com/en-gb/services/cognitive-services/computer-vision/</a> to turn this into text.

To begin with we will need to go to Azure and create a Cognitive Services Text Analytics API


<img width="280" height="308" class="alignnone  wp-image-605" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/09/CogText.png" />


(There is a free tier you may use that allows 5000 calls per month which will be more than enough for a proof of concept)

The Text Analytics API can also be trialed via the following website <a href="https://azure.microsoft.com/en-gb/services/cognitive-services/text-analytics/">https://azure.microsoft.com/en-gb/services/cognitive-services/text-analytics/</a>

The following R code will take the output from the Text Analytics API and produce a word cloud. All you need to do is replace the text "[Cognitive API Key]" with your key. This can be found in the Azure portal

<img width="401" height="172" class="alignnone  wp-image-606" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/09/CogTextKey.png" />
<pre><code>library(httr)
library(jsonlite)

subscription.key &lt;- "[Cognitive API Key]"

#For this demo we will load just one record of text into a data frame but we could submit 
#multiple lines in a batch as well (complaint text from citizensadvice.org.uk)
request_body &lt;- data.frame(
language = c("en"),
id = c("1"),
text = c("Re: Unsatisfactory Holiday at Hotel Balfour, Torrevieja on 12 August 2014 to 19 August 2014
Booking ref: 123456789

I have just returned from a holiday at Hotel Balfour, Torrevieja with my wife and children, which was most disappointing.

Please find below a list of our complaints:

1) There was no shower in the hotel as specified in the brochure
2) The kitchens were closed for the whole of our stay
3) The hotel was 5 miles from the beach and not 1 mile as it said in the brochure

We contacted your representative at the resort on 14 August 2015, but they were unable to resolve the matter and advised us to complain upon our return home.

Under The Package Travel, Package Holidays and Package Tours Regulations 1992 you have a responsibility to provide all the elements of the package contracted for as they were described. We are legally entitled to receive compensation from you for loss of value, consequential losses and for the disappointment and loss of enjoyment we suffered. As you failed to provide us with the holiday we booked we are seeking £150 compensation from you for the problems we encountered, and for the distress and disappointment we suffered as a result.

I have also sent a copy of this letter and enclosure to ABTA (of which I note you are a member).

I look forward to receiving a response from you within 14 days of receipt of this letter.
"))

#Convert to JSON for the API call
request_body_json &lt;- toJSON(list(documents = request_body), auto_unbox = TRUE)

#Call the key phrases API
result &lt;- POST("https://westus.api.cognitive.microsoft.com/text/analytics/v2.0/keyPhrases",
body = request_body_json,
add_headers(.headers = c("Content-Type" = "application/json", "Ocp-Apim-Subscription-Key" = subscription.key)))
Output &lt;- content(result)

#We now have the data in a list so convert to a vector for the word cloud
result &lt;- unlist(Output$documents[[1]]$keyPhrases)

library(wordcloud)

wordcloud(result,
    c(length(result):1), #reverse order for the weighting for the words
    scale = c(1.5, .1),
    random.order = TRUE,
    random.color = TRUE,
    colors = rainbow(length(unlist(Output$documents[[1]]$keyPhrases))))

</code></pre>

Running this code will produce the following word cloud


<img width="568" height="502" class="alignnone size-full wp-image-596" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/09/RWordCloud.png" />


Without reading the totality of the document we can now get a good understanding of the complaint that was made streamlining process and giving a faster resolution to the problem.]]></content:encoded>
    </item>
    <item>
      <title>SQL Server to Azure SQL Database data movement options and considerations with changing schemas and triggered refreshes</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/sql-server-to-azure-sql-database-data-movement-options-and-considerations-with-changing-schemas-and-triggered-refreshes</link>
      <pubDate>Fri, 04 Aug 2017 23:18:00 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=555</guid>
      <description><![CDATA[In this post we will look at the problem where we need to move the data at irregular intervals from...]]></description>
      <content:encoded><![CDATA[In this post we will look at the problem where we need to move the data at irregular intervals from an on-premise SQL instance with a constantly changing schema to an Azure SQL database. While there many different options in terms of performance and complexity we will look at some of the more common options and the performance characteristics of each of these.

(Azure Data Factory has been excluded from the comparison principle as currently it does not allow triggers to initiate a pipeline and although the SDK does allows schema changes, without a trigger this will produce problems in timing schema changes with the regular schedule. For completeness information on Data Factory performance can be found here <a href="https://docs.microsoft.com/en-us/azure/data-factory/data-factory-copy-activity-performance">https://docs.microsoft.com/en-us/azure/data-factory/data-factory-copy-activity-performance</a>)

The four methods that will be discussed in this post are
<ul>
 	<li>Linked server</li>
 	<li>SSIS</li>
 	<li>BCP</li>
 	<li>Azure SQL Data Sync 2.0</li>
</ul>
First the results then we will explain the strengths and weaknesses of each as well as the environment we used.

Below we see the speeds of all the methods. For the performance testing process we took processing times for a data move of 10.000, 100,000 and 1,000,000 records. Data was moved into empty heaps on the Azure SQL Database side. The data showed generally linear performance within an SKU so can be used as a rough reference for different workloads.

Note: The performance stats for linked servers are not shown in these results as running an import of just 10,000 records took around 780 seconds so was considered too slow a mechanism compared to the other methods.

Here we can see the speed of all the data transfer methods from on-premise to Azure SQL Database. The SKUs used were S0 = DTU10 and S3 = DTU100. On the S0 the DTUs were at 100% for all of the larger moves while the S3 was never maxed out suggesting we may have been able to have pushed more data in in parallel for even greater performance.

<img width="1251" height="455" class="alignnone size-full wp-image-556" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SQLMoveAllPerf.png" />

In the following we have stripped down the results to just show BCP and SSIS

<img width="1182" height="253" class="alignnone size-full wp-image-565" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SQLMoveFastestPerf.png" />

Now we have the results we will go through the strengths and weakness of each approach and how they can be set up.

<strong>Linked server</strong>

To set up a linked server an ODBC connection to the Azure SQL Database was created on the on-premise server. Then within the on-premise SQL Server a linked server referencing the ODBC connection as the remote server name needs to be created. Once created it is possible to talk to the Azure SQL Database from the on-premise SQL database via the four part naming convention or OPENQUERY.

Data may be uploaded as follows
<pre><code>INSERT INTO [SQLAzureODBC64].SQLTest.[dbo].Table1
SELECT * FROM Table1</code></pre>
Or
<pre><code>INSERT INTO OPENQUERY (SQLAzureODBC64, 'SELECT * FROM [dbo].Table1')
SELECT TOP 1 * FROM Table1</code></pre>
While comparatively a very slow way of moving data up to Azure SQL Database the linked server method does not consume much in the way of DTUs so will allow concurrent workloads during a load. Another advantage is that after creating the ODBC connection a solution can be developed just in TSQL without any need for access to the file system or the installations of any agents. Data movement may be triggered by ad hoc SQL or a stored procedure so any scheduling or trigger can be used to initiate a data movement.

<strong>BCP</strong>

BCP will run on any version of SQL including SQL Express. It is a command line tool so may be called via a .bat file from either Task Scheduler, SQL Agent or anything that has execute permissions on the file system. As it requires access to the source data file system it needs a lower level of security than using a linked server.

Schema changes are not possible with BCP. BCP expects a destination to exist so to script a schema change and a data move will require a combination of technologies. One way of doing this from the on-premise SQL side is to use a linked server via an ODBC connection to Azure SQL Database. Once the linked server is in place then OPENQUERY can be used to create or alter the destination schemas via a create statement. For example:
<pre><code>SELECT * FROM
OPENQUERY (SQLAzureODBC64, 'BEGIN TRY; CREATE TABLE tab1234 (ID INT); END TRY BEGIN CATCH; SELECT ERROR_MESSAGE(); END CATCH;')</code></pre>
So to make a schema change a stored procedure or application or PowerShell script would need to run an OPENQUERY statement first that will update or create a table. Then programmatically create a BCP batch file based on the new structure and then finally execute it.

The following example batch file will take the contents of a table and load into an Azure SQL database while recording start and end times to a log file.
<pre><code>
@echo off
set logfile=E:\PoliceBCP\time.log
echo starting run at %date% %time% &gt;&gt; %logfile%

bcp PoliceSD.dbo.PoliceRecords out "E:\PoliceBCP\Policetxt.txt" -c -T 

bcp SQLTest.dbo.PoliceBCP in "E:\PoliceBCP\Policetxt.txt" -c -U rob@sqlservername -S sqlservername .database.windows.net -P MyPassword

echo finishing run at %date% %time% &gt;&gt; %logfile%</code></pre>
BCP is a standard component of a SQL install and can be run on SQL Express.

As seen above in the sample script BCP is a two part process. The data is exported from SQL to a file and then pushed into Azure SQL Database from the file. Disk speed should not be too much of a concern as usually network or DTU level will be the bottleneck in a BCP.
Security may be a consideration as the BCP command will need the connection details of the Azure SQL Database every time it is run.

<strong>SSIS</strong>

SSIS requires at least a SQL Standard license to use so not a consideration where SQL Express is the only Edition. SSIS allows for better manageability and maintainability of data movement tasks and can be initiated either on a schedule or ad hoc.

With changing schemas SSIS packages may be programmatically generated. Tools such as BIML may be used for this <a href="https://www.varigence.com/biml">https://www.varigence.com/biml</a>

SSIS is generally more performant when running in parallel (as long as there are no bottlenecks such as limited server memory) so two tests were undertaken. One with a single process flow and one with two parallel process flows. In the latter process a final step of a table partition move would be required to bring all the data back together into a single table.

Single process flow

<img width="238" height="184" class="alignnone wp-image-566" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SSISSingleMove.png" />

Dual process flow

<img width="593" height="189" class="alignnone wp-image-575" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SSISDoubleMove.png" />

<strong>Azure SQL Data Sync 2.0</strong>

Azure SQL Data Sync 2.0 recently released into public preview allows synchronization between Azure SQL Database and either another Azure SQL Database or an on-premise SQL database. It functions in either one way data movement from hub to member or member to hub; or bi directional data movement with conflict resolution. While this service allows data replication it can be used for initial batch data movement without the need to define the schema on the destination database.

It can be scheduled or can be triggered through the Azure SDK and PowerShell with the following command
<pre><code>Start-AzureRmSqlSyncGroupSync -ResourceGroupName $ResourceGroupName `
-ServerName $ServerName `
-DatabaseName $DatabaseName `
-SyncGroupName $SyncGroupName</code></pre>
Examples may be found here <a href="https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/sql-database/scripts/sql-database-sync-data-between-azure-onprem.md">https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/sql-database/scripts/sql-database-sync-data-between-azure-onprem.md</a>

Azure SQL Data Sync requires an agent to be installed on-premise but after this any data movement may be done via the Azure portal, SDK or PowerShell.

The agent running on an on-premise server facilitating data movement from a SQL Server database:
<img width="589" height="463" class="alignnone wp-image-576" alt="" src="https://msdnshared.blob.core.windows.net/media/2017/08/SyncAgent.png" />

<strong>Results and Conclusions</strong>

Using a linked server is slow to move data into Azure SQL Database. It does however provide a mechanism to initiate schema changes from the on-premise side that can be used in combination with BCP. (Linked server to create/alter a table and the BCP to do the data flow heavy lifting.)

SSIS came out on top of performance and showed improvements when running in parallel so more scope here for tuning with additional parallel data flows to get ever better performance.

Azure SQL Data Sync 2.0 was not quite as fast for a bulk load of data but its real power comes in the ability to discern which records to move with the bonus of a conflict resolution engine. Great for scenarios where records within a table are being updated.

SSIS is the fasted method but cannot be run on just a SQL Express license. Also for changing schemas some development work is needed to create a package factory.

BCP is also extremely fast and works well when used with the linked server techniques, requires less work to create a custom solution based on changing schemas, however it is less elegant than an SSIS solution.

Finally here is all the collected data in a Power BI interactive report

<iframe width="800" height="600" allowfullscreen="allowfullscreen" frameborder="0" src="https://msit.powerbi.com/view?r=eyJrIjoiMmI1NGRhMzYtODI4Zi00ZmJjLWI5ZjYtYzQxMTQzNjhjZDFkIiwidCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NyIsImMiOjV9"></iframe>]]></content:encoded>
    </item>
    <item>
      <title>Data Factory Scheduling</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/data-factory-scheduling</link>
      <pubDate>Sat, 29 Apr 2017 21:11:18 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=545</guid>
      <description><![CDATA[Often questions arise on how to correctly schedule Azure Data Factory pipelines. In this post we...]]></description>
      <content:encoded><![CDATA[Often questions arise on how to correctly schedule Azure Data Factory pipelines. In this post we will look at some of the possible options and how they can affect your scheduling.

The first two options revolve around changing when the datasets will become available for a pipeline to run. A pipeline may be set to run daily but this will only be able to process when the source dataset lets the pipeline know its data is ready. The third option is to delay the pipeline to only run at a specific time within the day.

<strong>Option 1 – Set the “offset” on the dataset</strong>

<pre><code>        "availability": {
            "frequency": "Day",
            "interval": 1,
            "offset": "06:00:00"
        },
</code></pre>

Here the system is being instructed to that the data will be available every day at 6 in the morning.
This would give a slice pattern as follows:
<pre><code>29‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC - ‎30‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC
28‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC - ‎29‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC
27‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC - ‎28/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC
</code></pre>
As the interval is set to daily the slice will also have an end time of 6 in the morning. 

<strong>Option 2 – Set the “anchorDateTime” and "interval" on the dataset</strong>

This option on the dataset lets the start time of the slice be set and then the interval between slices
<pre><code>        "availability": {
            "frequency": "Hour",
            "interval": 18,
            "anchorDateTime": "2001-01-01T06:00:00Z"
        },
</code></pre>
This is often misunderstood to give a daily slice starting at 6 in the morning. However, the reality is that two slices will be created for each day. The slice running from 6 in the morning to midnight and then another slice from midnight to 6 in the morning. As in the following slice timings:
‎<pre><code>29‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎PM UTC - ‎30‎/‎04‎/‎2017‎ ‎12‎:‎00‎ ‎PM UTC
‎29‎/‎04‎/‎2017‎ ‎12‎:‎00‎ ‎AM UTC -‎ 29‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎PM UTC
‎28‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC - ‎29‎/‎04‎/‎2017‎ ‎12‎:‎00‎ ‎AM UTC 
‎27‎/‎04‎/‎2017‎ ‎12‎:‎00‎ ‎PM UTC -‎ 28‎/‎04‎/‎2017‎ ‎06‎:‎00‎ ‎AM UTC</code></pre>

<strong>Option 3 set the “delay” on the pipeline</strong>

With a dataset and pipeline set to daily (and starting and ending at midnight) a policy may be applied to an action that delays the processing by an amount of time. In this example the pipeline will pause execution of the data movement until 6 in the morning. Providing the dataset is available at this time (which it will be if it has been set to a daily slice).
<pre><code>        "policy": {
                    "timeout": "1.00:00:00",
                    "delay": "06:00:00",
                    "concurrency": 1,
                    "executionPriorityOrder": "NewestFirst",
                    "style": "StartOfInterval",
                    "retry": 3,
                    "longRetry": 0,
                    "longRetryInterval": "00:00:00"
                },</code></pre>
This way the slice will be a full day slice but only execute at 6 in the morning.

Using these three option you can chose when to “Offset” the slices so the whole slice is shifted by a number of hours. You can choose to “anchorDateTime” the start time and then cut up the slices between this and the next slice time with an “interval”. Finally you can choose to “delay” the processing of a pipeline. These three options used independently or in combination where appropriate can give a very tuneable way to manage when and how often your data is processed

]]></content:encoded>
    </item>
    <item>
      <title>Azure Data Factory - using a Stored Procedure as a destination</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/azure-data-factory-using-a-stored-procedure-as-a-destination</link>
      <pubDate>Mon, 03 Apr 2017 22:48:42 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=526</guid>
      <description><![CDATA[Azure Data Factory allows data to move from a multitude of sources to a multitude of destinations. A...]]></description>
      <content:encoded><![CDATA[Azure Data Factory allows data to move from a multitude of sources to a multitude of destinations. A common usage case is to take on premise data and place it into a cloud database such as Azure SQL Database.

Often this is done as a two-stage operation. Firstly, load the data into a data lake or database table then perform a transformation operation on that data.

Today we will look at performing this in one atomic action. This allows a smaller, simpler and faster data move and transformation pipeline. However, this does come at the cost that the data lineage is held in an on premise system and not in the cloud architecture.

To reduce a data move and transformation to one operation we will use a stored procedure instead of a table. This way data can be transformed as it loads.

In this simple example we are using an on premise SQL database as the source of the data. We have already set up a Data Management Gateway to facilitate the communication between the on premise resource and Azure. Now we need to create a simple table as the data source and populate it with some data.

<pre><code>/*Run on the On Prem data source*/
CREATE TABLE TabOnPremSQLSource
( ID INT
, String VARCHAR(250)
)
GO
INSERT INTO TabOnPremSQLSource VALUES (1,'a')
INSERT INTO TabOnPremSQLSource VALUES (2,'b')
GO

SELECT * FROM TabOnPremSQLSource

</code></pre>

Once this has been run we need to create a stored procedure at the destination to perform transformations. In this example, we are taking the data, performing an aggregation and storing that aggregation.

<pre><code>/*Run on the Azure SQL Database*/
/*A table to hold the transformed data*/
CREATE TABLE tabCountFromOnPremSQL
(ID INT,
TimeUploaded DateTime2 DEFAULT(GetDate()))

GO

/*A table type that will be used by the stored procedure to receive the data*/
CREATE TYPE tabTypeUploadData AS TABLE
( ID INT
, String VARCHAR(250)
)

GO

/*Stored proc to receive the data, transform and place into a table*/
CREATE PROCEDURE spDFTableProc (@inDataFromOnprem [dbo].tabTypeUploadData READONLY) AS

DECLARE @ID INT

SELECT @ID = COUNT(*) FROM @inDataFromOnprem

INSERT INTO tabCountFromOnPremSQL (ID) VALUES (@ID)

GO

SELECT * FROM tabCountFromOnPremSQL</code></pre>

Now we have the source and destination set up we can create the data factory

Here is a diagram of the finished pipeline

<img width="1033" height="217" class="alignnone size-full wp-image-535" alt="datafactorypipeline" src="https://msdnshared.blob.core.windows.net/media/2017/04/DataFactoryPipeline.png" />

In creating the data factory components first are the linked services

Here we are setting up the on premise SQL Server lined service (data source)

<pre><code>{
    "name": "SqlServerOnPrem",
    "properties": {
        "description": "",
        "hubName": "dfral01_hub",
        "type": "OnPremisesSqlServer",
        "typeProperties": {
            "connectionString": "Data Source=onpremSQLBox\\SQL2016;Initial Catalog=SQL2016;Integrated Security=False;User ID=SQL;Password=**********",
            "gatewayName": "DataManagementGateway",
            "userName": "SQL",
            "password": "**********"
        }
    }
}
</code></pre>

Then the linked service for the destination (Azure SQL Database)
<pre><code>
{
    "name": "AzureSqlLinkedService",
    "properties": {
        "description": "",
        "hubName": "dfral01_hub",
        "type": "AzureSqlDatabase",
        "typeProperties": {
            "connectionString": "Data Source=tcp:AZURESQLSERVERNAME.database.windows.net,1433;Initial Catalog=SQLTest;Integrated Security=False;User ID=Rob@AZURESQLSERVERNAME;Password=**********;Connect Timeout=30;Encrypt=True"
        }
    }
}
</code></pre>

Now we define the dataset for the source data
<pre><code>
{
    "name": "SQLServerSource",
    "properties": {
        "published": false,
        "type": "SqlServerTable",
        "linkedServiceName": "SqlServerOnPrem",
        "typeProperties": {
            "tableName": "TabOnPremSQLSource"
        },
        "availability": {
            "frequency": "Hour",
            "interval": 1
        },
        "external": true,
        "policy": {}
    }
}
</code></pre>

And the dataset for the destination. Note the table name is the table variable in the destination stored procedure
<pre><code>
{
    "name": "SqlOutput",
    "properties": {
        "published": false,
        "type": "AzureSqlTable",
        "linkedServiceName": "AzureSQLLinkedService",
        "typeProperties": {
            "tableName": "inDataFromOnprem"
        },
        "availability": {
            "frequency": "Hour",
            "interval": 1
        }
    }
}
</code></pre>

Finally we construct the pipeline. We have to define the stored procedure as well as the table type it is expecting
<pre><code>
{
    "name": "CallStroedProc",
    "properties": {
        "activities": [
            {
                "type": "Copy",
                "typeProperties": {
                    "source": {
                        "type": "SqlSource"
                    },
                    "sink": {
                        "type": "SqlSink",
                        "sqlWriterStoredProcedureName": "spDFTableProc",
                        "sqlWriterTableType": "tabTypeUploadData",
                        "writeBatchSize": 0,
                        "writeBatchTimeout": "00:00:00"
                    }
                },
                "inputs": [
                    {
                        "name": "SQLServerSource"
                    }
                ],
                "outputs": [
                    {
                        "name": "SqlOutput"
                    }
                ],
                "policy": {
                    "timeout": "02:00:00",
                    "concurrency": 1,
                    "executionPriorityOrder": "NewestFirst",
                    "style": "StartOfInterval",
                    "longRetry": 0,
                    "longRetryInterval": "00:00:00"
                },
                "scheduler": {
                    "frequency": "Hour",
                    "interval": 1
                },
                "name": "CopyToStoredProcedure"
            }
        ],
        "start": "2017-03-31T00:09:00.273Z",
        "end": "2017-04-02T03:00:00Z",
        "isPaused": false,
        "hubName": "dfral01_hub",
        "pipelineMode": "Scheduled"
    }
}
</code></pre>

We would now need to consider setting up slices and only moving the data from the source system that corresponded to the relevant slice.

&nbsp;]]></content:encoded>
    </item>
    <item>
      <title>Improving query performance with OPTION (RECOMPILE), Constant Folding and avoiding Parameter Sniffing issues</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/improving-query-performance-with-option-recompile-constant-folding-and-avoiding-parameter-sniffing-issues</link>
      <pubDate>Wed, 10 Aug 2016 12:23:21 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=415</guid>
      <description><![CDATA[OPTION (RECOMPILE) when used in the correct manner can give a performance boost though Constant...]]></description>
      <content:encoded><![CDATA[OPTION (RECOMPILE) when used in the correct manner can give a performance boost though Constant Folding and also help deal with parameter sniffing issues. However, before going into OPTION (RECOMPILE) and how Constant Folding works first a bit of background.

<strong>Plan Caching Issues</strong>

The first time a stored procedure is run it will compile into the plan cache. This allows the SQL engine to reuse the plan in the cache multiple times without needing to spend time and CPU cycles on recompilation. This works great most of the time, however with stored procedures that may deal with different amounts of data depending on the parameters passed in, then storing the plan in the cache can be problematic. This is because the plan stored will be the plan that was generated with the parameter values on first run. This information may be seen in the properties of the execution plan. In this example the plan was first run with the parameter set to 'A Value' so this plan was put into the plan cache, and then subsequently run with the parameter set to 'A Different Value'.

<img width="267" height="121" class="wp-image-495 alignnone" alt="Params" src="https://msdnshared.blob.core.windows.net/media/2016/08/Params.png" />

The first parameter used to compile the plan into the plan cache may not be the best parameter to compile the plan with. Other parameters that might be passed in in subsequent runs may not run well with the compiled plan. This is known as a parameter sniffing issue.

For example, if a company has one very large customer and several smaller customers they will likely have a database table with a large number of records for that one customer and a much smaller set for the other customers.

<img width="534" height="252" class="alignnone  wp-image-506" alt="Uniformity" src="https://msdnshared.blob.core.windows.net/media/2016/08/Uniformity.png" />

If the databased were to query based on a small customer it would likely do a LOOP JOIN and the plan would be cached meaning any subsequent runs of the plan will also perform a LOOP JOIN. Although a LOOP JOIN is performant on smaller data sets it is not performant for larger data sets. In this scenario any searches on the larger customer would then be non performant (as the query should be using a MERGE JOIN or HASH JOIN)

To demonstrate here is a demo set of tables with some random data and a parametrized stored procedure for querying.

Within the random data 100 records have been added with the marker 'Marker' so there are records that can be queried for demo purposes to return a small dataset. This is equal to having a database table with poor uniformity of data.
<pre><code>IF EXISTS (SELECT * FROM sysObjects WHERE Name = 'tabdemo1')
DROP TABLE tabdemo1
IF EXISTS (SELECT * FROM sysObjects WHERE Name = 'tabdemo2')
DROP TABLE tabdemo2
IF EXISTS (SELECT * FROM sysObjects WHERE Name = 'sptabdemoproc')
DROP PROCEDURE sptabdemoproc
GO
CREATE TABLE tabdemo1 (ID INT PRIMARY KEY NOT NULL IDENTITY (1,1), String Varchar(150), ID2 INT)
CREATE TABLE tabdemo2 (ID INT PRIMARY KEY NOT NULL IDENTITY (1,1), String Varchar(150), ID2 INT)
GO
SET NOCOUNT ON
GO
INSERT INTO tabdemo1 (String, ID2) VALUES (NEWID(), RIGHT(CHECKSUM(NEWID()),3))
INSERT INTO tabdemo2 (String, ID2) VALUES (NEWID(), RIGHT(CHECKSUM(NEWID()),3))
GO 50000
INSERT INTO tabdemo2 (String, ID2) VALUES ('Marker', RIGHT(CHECKSUM(NEWID()),3))
GO 100
SET NOCOUNT OFF
GO

CREATE NONCLUSTERED INDEX [IXtabdemo1ID2] ON tabdemo1(ID2)
CREATE NONCLUSTERED INDEX [IXtabdemo2ID2] ON tabdemo2(ID2)
CREATE NONCLUSTERED INDEX [IXtabdemo2String] ON [dbo].[tabdemo2] ([String])

GO

CREATE PROCEDURE sptabdemoproc (@string varchar(150)) AS

SELECT	COUNT(*)
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2
WHERE	(B.String = @string OR @string IS NULL)

GO</code></pre>
To demonstrate parameter sniffing issues first run the stored procedure with the parameter set to 'Marker'. This will define a query pattern stored in the cache that is expecting a small amount of data to be processed.

<pre><code>EXEC sptabdemoproc 'Marker'

<img width="1281" height="278" class="alignnone size-full wp-image-496" alt="011Plan" src="https://msdnshared.blob.core.windows.net/media/2016/08/011Plan.png" />
</code></pre>

As the predicate is set to use ‘Marker’ as the filter the optimizer has rightly decided to use a LOOP JOIN as this is the most performant way to return the data.

However now this plan is cached calling the stored procedure with 'Null' (returning a count all the records) the cached plan will still perform a LOOP JOIN. Looking at the time it takes to execute by using 'SET STATISTICS TIME ON' the plan takes around 50 milliseconds to run.

To get to a plan that would work better with 'Null' as the parameter the procedure will need to be recompiled.

Recompiling (using EXEC sp_Recompile sptabdemoproc) and running again using 'Null' will give an optimal plan again bringing the run time from around 50 milliseconds to around 13 milliseconds
(The plan is now performing a HASH MATCH for the aggregation which is more optimal for larger data sets.)

<img width="1293" height="284" class="alignnone size-full wp-image-445" alt="03HashPlanpng" src="https://msdnshared.blob.core.windows.net/media/2016/08/03HashPlanpng.png" />

Recompiling the stored procedure again and running for a single value the execution (once compiled) will take around 6 milliseconds
<pre><code>EXEC sp_Recompile sptabdemoproc 
SET STATISTICS TIME ON
EXEC sptabdemoproc 'Marker'</code></pre>

From this it can be seen that one plan works best when the parameter is set to 'Null' and one works best when the parameter is set to 'Marker'. Under normal conditions both can not be in a compiled state and held in the cache. To resolve this issues of different plans needed for different parameters there are some solutions.

<strong>OPTIMIZE FOR</strong>

The OPTIMIZE FOR option may be used on a statement to tell it to compile with a specific variable.

<pre><code>ALTER PROCEDURE sptabdemoproc (@string varchar(150)) AS

SELECT	COUNT(*)
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2
WHERE	(B.String = @string OR @string IS NULL)
OPTION	(OPTIMIZE FOR (@string = 'Marker')) 

GO</code></pre>

This will help if the plan should always favor a particular parameter such as 'Marker', but in that case this will always run slow when passing in 'Null'.

An UNKNOWN value may also be supplied for the OPTION

<pre><code>ALTER PROCEDURE sptabdemoproc (@string varchar(150)) AS

SELECT	COUNT(*)
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2
WHERE	(B.String = @string OR @string IS NULL)
OPTION	(OPTIMIZE FOR UNKNOWN) 

GO</code></pre>

This will give a plan that will work OK with any parameter but not necessarily the most performant plan

<strong>WITH RECOMPILE</strong>

This is a procedure level option to set the procedure to recompile after each run. This means that the plan will always recompile and so avoid cached plans being reused. Using this method will take up CPU cycles though compilation but a better plan will likely be generated if there are parameter sniffing issues.

<pre><code>ALTER PROCEDURE sptabdemoproc (@string varchar(150)) WITH RECOMPILE AS 

SELECT	COUNT(*)
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2
WHERE	(B.String = @string OR @string IS NULL)

GO</code></pre>

Looking at the time statistics with SET STATISTISCS TIME ON  and using the 'Marker' parameter this procedure will take around 2 to 3 milliseconds longer for the recompile, this equates to a total of around 15 milliseconds.

Looking at how the plan is forming it is still talking into consideration the fact that several parameters may be used. This can be seen by looking at the predicate in the execution plan. The predicate is still considering the two execution modes, either a value being passed in or a 'Null' value. While a good plan will be chosen the influence of considering these two execution modes in the plan will have an impact.


<img width="1270" height="466" class="alignnone size-full wp-image-455" alt="04Pred" src="https://msdnshared.blob.core.windows.net/media/2016/08/04Pred.png" />

<strong>OPTION (RECOMPILE)</strong>

OPTION (RECOMPILE) is a statement level command that has some very distinct advantages over WITH RECOMPILE. It does not require the whole stored procedure to be recompiled so in a large stored procedure time and CPU cycles are not spent on unnecessary compiles. It uses Constant Folding which can have generate a far superior plan.

<pre><code>ALTER PROCEDURE sptabdemoproc (@string varchar(150)) AS

SELECT	COUNT(*)
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2
WHERE	(B.String = @string OR @string IS NULL)
OPTION	(RECOMPILE)

GO</code></pre>

Constant folding is a technique the optimizer uses to remove any unnecessary code to help improve performance. Constant Folding does this by removing unnecessary variables and simplifying the query before compiling the plan. In the example in the demo when the parameter comes into the plan it is either set to a string or a 'Null'. Constant Folding will remove whichever is not used. So if 'Marker' is passed in as the parameter then Constant folding will simply remove the ‘OR String IS Null’ from the predicate:

<img width="1470" height="494" class="alignnone size-full wp-image-465" alt="05CFolding" src="https://msdnshared.blob.core.windows.net/media/2016/08/05CFolding.png" />

Using OPTION (RECOMPILE) in this case gives by far the best execution. The plan compile time is reduced to around 1 millisecond and execution time is reduced to around 2 milliseconds, making the total execution time of the stored procedure around 3 milliseconds. This is achieved because without the need to think about the ‘IS NULL’ a plan is produced that no longer needs to scan the index but can seek, which is a much more performant operation.

Running the stored procedure with 'Null' also benefits taking the total plan time down from around 13 milliseconds to around 7 milliseconds.

Here are the two plans for comparison, before OPTION (RECOMPILE) and after OPTION (RECOMPILE) was set

Before OPTION (RECOMPILE)

<img width="1300" height="465" class="alignnone size-full wp-image-475" alt="06PreCFolding" src="https://msdnshared.blob.core.windows.net/media/2016/08/06PreCFolding.png" />

After OPTION (RECOMPILE)

<img width="1454" height="266" class="alignnone size-full wp-image-485" alt="07PostCFolding" src="https://msdnshared.blob.core.windows.net/media/2016/08/07PostCFolding.png" />

In the OPTION (RECOMPILE) plan the optimizer has Constant Folded out the predicate (as the ‘B.String = @String’ is unnecessary). Due to this it is able to calculate the COUNT aggregation in stream, (rather than making a hash table of the aggregates) making this a much more performant plan.

OPTION (RECOMPILE) is not a silver bullet for performance. It can be an extremely powerful performance optimizing technique but it will not always give a faster plan in all cases due to the compile overhead. If there are no parameters then there will be no benefits from Constant Folding and there will be no parameter sniffing issues so it will just add a compilation overhead.

<strong>OPTION (RECOMPILE) with temporary tables</strong>

One important side effect when using OPTION (RECOMPILE) is the impact it will have on temporary tables. If a temporary table is created with an OPTION (RECOMPILE) statement then the optimizer will need to recompile every statement it touches.

The following query takes  around 50 milliseconds and has  no SQL Server parse and compile time after first run (as the plan is cached)

<pre><code>CREATE PROCEDURE sptabdemoprocB AS

SELECT	A.*
INTO	#TMP
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2

SELECT COUNT(*) FROM #TMP AS A
INNER JOIN tabdemo2 AS t ON A.ID = t.ID

SELECT MIN(A.ID) FROM #TMP AS A
INNER JOIN tabdemo2 AS t ON A.ID = t.ID

SELECT MAX(A.ID) FROM #TMP AS A
INNER JOIN tabdemo2 AS t ON A.ID = t.ID

GO</code></pre>

Adding an OPTION (RECOMPILE) to the procedure adds an overhead of 15 milliseconds compile time as everything that touches the temporary table has to be recompile. Importantly it will not implement Constant Folding on any statements that are recompiled without an explicit OPTION (RECOMPILE) statement.

When using OPTION (RECOMPILE) on a temporary table it is best to make sure it is also added on all the other statements that are using it in the same execution to benefit from Constant Folding, as follows:

<pre><code>CREATE PROCEDURE sptabdemoprocB AS

SELECT	A.*
INTO	#TMP
FROM	tabdemo1 AS A
		INNER JOIN tabdemo2 AS B ON A.ID = B.ID2
OPTION	(RECOMPILE)

SELECT COUNT(*) FROM #TMP AS A
INNER JOIN tabdemo2 AS t ON A.ID = t.ID
OPTION	(RECOMPILE)

SELECT MIN(A.ID) FROM #TMP AS A
INNER JOIN tabdemo2 AS t ON A.ID = t.ID
OPTION	(RECOMPILE)

SELECT MAX(A.ID) FROM #TMP AS A
INNER JOIN tabdemo2 AS t ON A.ID = t.ID
OPTION	(RECOMPILE)

GO

</code></pre>

To sum up OPTION (RECOMPILE) is a tool that can give some massive performance benefits in parameterized stored procedures though Constant Folding and is also able to combat parameter sniffing. For very small and simple OLTP style stored procedures it will add a recompile burden.]]></content:encoded>
    </item>
    <item>
      <title>Index Maintenance - Fragmentation should not be your only concern</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/index-maintenance-fragmentation-should-not-be-your-only-concern</link>
      <pubDate>Thu, 04 Aug 2016 16:05:41 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=325</guid>
      <description><![CDATA[Index fragmentation is not the only consideration when constructing an index maintenance plan. This...]]></description>
      <content:encoded><![CDATA[Index fragmentation is not the only consideration when constructing an index maintenance plan. This article will look at other considerations that should be taken into account such as:
<ul>
 	<li>Page fullness</li>
 	<li>FILLFACTOR and PAD_INDEX</li>
 	<li>The impact of Snapshot Isolation</li>
</ul>
The less fragmented an index is, the more performant it will be. Inserting records inside an index will cause fragmentation as will updating a variable length cell with a greater amount of data. Setting an index fill factor will help offset the chance of fragmentation from a page split but this comes at the cost of a larger number of pages in the index.

Choosing which indexes to rebuild as part of a maintenance plan is commonly done by looking for indexes over a certain overall fragmentation level, however, when writing maintenance plans page fullness should also be considered as well as intermediate level page fragmentation.

<strong>Page fullness</strong>

An index may be defragmented but still may not offer the greatest amount of performance if it is wasteful in how it is utilizing its pages.
To demonstrate the following script will create a narrow table and populate it.

It should be noted that in the examples given the default fillfactor is 100%. The number of pages will vary depending on the fill factor set.
<pre><code>CREATE TABLE RowDemo (ID INT NOT NULL IDENTITY (1,1), ID2 INT, CONSTRAINT [PKRowDemo] PRIMARY KEY (ID,ID2))
GO
INSERT INTO RowDemo (ID2) VALUES (1)
GO 10000</code></pre>
This table has 2 integers in it. An integer takes up 4 bytes so 2 integers equate to 8 bytes per row.
<pre><code>SELECT datalength(ID) + datalength(ID2) FROM RowDemo</code></pre>
Looking at the avg_record_size_in_bytes from SYS.DM_DB_INDEX_PHYSICAL_STATS we can see a size of 15 bytes

<img width="242" height="86" class="alignnone wp-image-335" alt="AvgPageSize" src="https://msdnshared.blob.core.windows.net/media/2016/08/AvgPageSize.png" />

The true size of the row is 15 bytes as there is a 7 byte metadata overhead on each row. (4 bytes for the first integer column + 4 bytes for the second integer column + 7 bytes row metadata = 15 bytes per row.)

With 15 bytes per row we can fit on the leaf level of the clustered index around 454 records per page
<pre><code>SELECT  index_level,
record_count /
page_count [Number of records per page]
FROM	SYS.DM_DB_INDEX_PHYSICAL_STATS(DB_ID(),OBJECT_ID('RowDemo'),NULL,NULL,'DETAILED')</code></pre>
<img width="245" height="81" class="alignnone wp-image-345" alt="Page2" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page2.png" />

The number of rows in a page can be confirmed by looking at the first data page in detail
<pre><code>DECLARE @DBID INT = DB_ID()
DECLARE @ObjID INT = object_ID('RowDemo') 
DECLARE @PageID INT
SELECT TOP 1 @PageID = allocated_page_page_id FROM sys.dm_db_database_page_allocations(@DBID, @ObjID, 1, 1, 'detailed')
WHERE page_type = 1 /*Data pages*/
DBCC TRACEON (3604)
DBCC PAGE (@DBID,1,@PageID, 2)
DBCC TRACEOFF (3604)</code></pre>
This shows the slot count (rows on a page) as 476

<img width="855" height="332" class="alignnone wp-image-355" alt="Page3" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page3.png" />

And the offset array confirms this

<img width="210" height="315" class="alignnone wp-image-365" alt="Page4" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page4.png" />

Looking at fragmentation it can be seen that the table is less than 5% fragmented
<pre><code>SELECT	index_level,
	page_count,
	avg_fragmentation_in_percent,
	avg_page_space_used_in_percent,
	avg_record_size_in_bytes,
	record_count
FROM	SYS.DM_DB_INDEX_PHYSICAL_STATS(DB_ID(),OBJECT_ID('RowDemo'),NULL,NULL,'DETAILED')</code></pre>
<img width="826" height="97" class="alignnone wp-image-375" alt="Page5" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page5.png" />

Running conventional rebuild index scripts that look for a percentage of fragmentation over a reasonable threshold will not touch this index as there is no need to rebuild.

However, in a production environment data may be both added and removed from a table. The following example demonstrates the impact of deleting rows on the table and index by removing every other row from the table
<pre><code>DELETE FROM RowDemo WHERE ID % 2 = 1</code></pre>
The fragmentation is still less than 5 percent but the avg_page_space_used_in_percent has dropped to less than 50%.
<pre><code>SELECT	index_level,
	page_count,
	avg_fragmentation_in_percent,
	avg_page_space_used_in_percent,
	avg_record_size_in_bytes,
	record_count
FROM	SYS.DM_DB_INDEX_PHYSICAL_STATS(DB_ID(),OBJECT_ID('RowDemo'),NULL,NULL,'DETAILED')</code></pre>
<img width="778" height="97" class="alignnone wp-image-385" alt="Page6" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page6.png" />

This index is now less performant than it could be as it is 22 pages in size but only using half the page space. A maintenance plan looking to rebuild indexes over a certain threshold of fragmentation would not pick this up as the fragmentation has not changed.

Rebuilding the index
<pre><code>ALTER INDEX [PKRowDemo] ON RowDemo REBUILD</code></pre>
The pages are now compacted again and the number of pages used is halved
<pre><code>SELECT	index_level,
	page_count,
	avg_fragmentation_in_percent,
	avg_page_space_used_in_percent,
	avg_record_size_in_bytes,
	record_count
FROM	SYS.DM_DB_INDEX_PHYSICAL_STATS(DB_ID(),OBJECT_ID('RowDemo'),NULL,NULL,'DETAILED')</code></pre>
<img width="797" height="96" class="alignnone wp-image-395" alt="Page7" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page7.png" />

This then becomes an optimal index again as it is taking up less space on disk, and using less memory. Any scans needed will use fewer pages to attain the same result as before.

The following script identifies indexes that have a fragmentation greater than 20% and less than 90% space per page of available (after taking into account the fill factor):
<pre><code>SELECT	OBJECT_NAME(I.object_id) Tablename,
	I.name,
	I.type_desc,
	P.partition_number,
	P.page_count,
	P.avg_page_space_used_in_percent,
	P.index_depth,
	P.index_level,
	P.avg_fragmentation_in_percent,
	P.fragment_count,
	P.avg_fragment_size_in_pages,
	P.avg_record_size_in_bytes,
	P.record_count
FROM	SYS.DM_DB_INDEX_PHYSICAL_STATS(DB_ID(),NULL,NULL,NULL,'DETAILED') AS P
	INNER JOIN sys.indexes AS I ON P.object_id = I.object_id AND P.index_id = I.index_id
WHERE	P.index_level &lt; P.index_depth -1 /*Ignore all root level pages*/
AND	(P.avg_page_space_used_in_percent &lt;(ISNULL(NULLIF(I.fill_factor, 0), 100) * 0.85)
/*Where space used is less 85% of total after fill factor*/
OR avg_fragmentation_in_percent &gt; 20) /*Where fragmentation is greater than 20%*/
</code></pre>
<strong>FILLFACTOR and PAD_INDEX</strong>

The FILLFACTOR of an index allows records to be inserted or enlarged inside a page without a page split occurring. There is a trade off in so far as the lower the fill factor the larger the number of pages so the more pages are accessed in a scan.

Unless PAD_INDEX has been set space is left for additional data only at the leaf level. Without PAD_INDEX an index fragmentation may be acceptable at the leaf level but still be fragmented in the intermediate levels.

<strong>The impact of Snapshot Isolation</strong>

Enabling a system to use Snapshot isolation or setting up a readable secondary can cause fragmentation issues and reduce the performance of some indexes, especially on narrow tables.

When a database is enabled to use snapshot isolation SQL Server will add more bytes at the end of each row to allow the row versioning. This consists of 6 bytes to store the transaction sequence and 8 bytes to store the row ID. This will add an additional 14 bytes to each row, which on a narrow table will increase the size considerably. With a table of 15 bytes (4 bytes for the first integer column + 4 bytes for the second integer column + 7 bytes row metadata) each row will become almost double at 29 bytes per row. When this happens the pages will not be able to fit as many rows so fragmentation will occur.

After the last delete and index rebuild from the previous example table the table had 11 pages. This code will enable snapshot isolation and as it is an existing table perform an update to see the effect on the pages.
<pre><code>ALTER DATABASE PageDemo SET ALLOW_SNAPSHOT_ISOLATION ON

UPDATE	R
SET	ID2 = ID2 
FROM	RowDemo AS R

SELECT	page_count,
	avg_page_space_used_in_percent,
	index_depth,
	index_level,
	avg_fragmentation_in_percent,
	fragment_count,
	avg_fragment_size_in_pages,
	avg_record_size_in_bytes,
	record_count
FROM	SYS.DM_DB_INDEX_PHYSICAL_STATS(DB_ID(),OBJECT_ID('RowDemo'),NULL,NULL,'DETAILED')</code></pre>
<img width="1328" height="112" class="alignnone size-full wp-image-405" alt="Page8" src="https://msdnshared.blob.core.windows.net/media/2016/08/Page8.png" />

It can be seen here that the table has now gone from 11 pages to 21 and the table is now 95% fragmented. Of course the index can be rebuilt to remove the fragmentation, but our narrow table has now doubled in size potentially affecting performance of queries that need to scan it.

When a readable secondary is used in an Always On Availability Group the secondary will run all queries in snapshot isolation mode to avoid any locking issues. In an Availability Group the schemas must match. Hence the additional bytes at the end of each row must exist on both the primary and the secondary. When enabling a readable secondary make sure to take the fragmentation into consideration.]]></content:encoded>
    </item>
    <item>
      <title>Getting started with Hortonworks 2.4 in Hyper-V</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/getting-started-with-hortonworks-2-4-in-hyper-v</link>
      <pubDate>Tue, 26 Jul 2016 21:47:35 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=215</guid>
      <description><![CDATA[Currently there is no 2.4 Hortonworks sandbox available for Hyper-V. If like me you spend a lot of...]]></description>
      <content:encoded><![CDATA[Currently there is no 2.4 Hortonworks sandbox available for Hyper-V. If like me you spend a lot of time travelling with an intermittent network connection and would like to run locally then this article will show you how to set one up:

Firstly, an image of the Hortonworks 2.4 sandbox is needed. There are two options to attain an image, either create a Hortonworks Sandbox VM in Azure or download a Sandbox from the Hortonworks website.

&nbsp;

<img width="875" height="352" class="alignnone wp-image-295" alt="HortonworksAzure2" src="https://msdnshared.blob.core.windows.net/media/2016/07/HortonworksAzure2.png" />

&nbsp;

Hortonworks Sandbox in Azure is a great way to quickly and easily develop Big Data projects. However, as stated previously this article is about getting a Sandbox to play with that does not require an internet connection. Although the Azure Sandbox VM can be downloaded from blob storage and run in HyperV, it is a reasonably large image at 48.8GB.

&nbsp;

<img width="692" height="255" class="alignnone wp-image-316" alt="AzureHWVM2" src="https://msdnshared.blob.core.windows.net/media/2016/07/AzureHWVM2.png" />

&nbsp;

We are going to take the smaller VirtualBox image from the Hortonworks website and convert it to run in Hyper V

<img width="895" height="284" class="alignnone wp-image-297" alt="HortonworksVBSandbox2" src="https://msdnshared.blob.core.windows.net/media/2016/07/HortonworksVBSandbox2.png" />

Once download, the VirtualBox image from the Hortonworks website comes in at around 15GB. You will then need to use VirtualBox (a free download) to extract and convert the file to a VHD.

The downloaded file is a .ova. This can be converted to an .vmdx as documented on the Hortonworks website, or you may alternatively use 7Zip to extract the .vmdx file from the .ova.

Once extracted the .vmdx can then be converted to a .vhd with the following command (amend for you own folder structure)
<pre><code>"%programfiles%\Oracle\VirtualBox\vboxmanage" clonehd "E:\VirtualBoxVMs\Hortonworks Sandbox with HDP 2.4\Hortonworks Sandbox with HDP 2.4-disk1.vmdk" "E:\VirtualBoxVMs\Hortonworks Sandbox with HDP 2.4\Hortonworks Sandbox with HDP 2.4-disk1.vhd" -format VHD

</code></pre>
<img width="754" height="269" class="alignnone wp-image-306" alt="CreateVHD2" src="https://msdnshared.blob.core.windows.net/media/2016/07/CreateVHD2.png" />

&nbsp;

Once this is completed, uninstall virtualbox as it is no longer required, then mount the image in HyperV.

Some configuration will be required to set up the network correctly. First set up an internal network switch, we will use this for the static IP.

&nbsp;

<img width="632" height="602" class="alignnone wp-image-275" alt="Network Switch" src="https://msdnshared.blob.core.windows.net/media/2016/07/Network-Switch.png" />

&nbsp;

Then alter the Hadoop network interface to give it a static IP so your local PC and the VM will be able to communicate

&nbsp;

<img width="370" height="421" class="alignnone wp-image-285" alt="networksettings" src="https://msdnshared.blob.core.windows.net/media/2016/07/networksettings.png" />

&nbsp;

In Hyper V update the VM to use this connection then start the VM

Once the Sandbox has booted, log on as root and change the IP to a static IP with the following command
<pre><code>ifconfig eth0 192.168.56.101 netmask 255.255.255.0 broadcast 192.168.56.255</code></pre>
Now you will be able to communicate on 192.168.56.101. However, this will reset on next reboot.

To configure for a permanent static IP you will need to change the configuration file in linux, which is easiest to do with an FTP client, such as WinSCP. Using the FTP client open the file

/etc/sysconfig/network-scripts/ifcfg-eth0

and alter as below to persist the static IP
<pre><code>DEVICE="eth0"
BOOTPROTO="static"
IPADDR=192.168.56.101
NETMASK=255.255.255.0
GATEWAY=192.168.56.1
DHCP_HOSTNAME="sandbox.hortonworks.com"
DNS1="8.8.8.8"

IPV6INIT="no"
ONBOOT="yes"
TYPE="Ethernet"

NM_CONTROLLED=no
PEERDNS=no</code></pre>
&nbsp;

Once saved then reboot the VM and you will be able to connect via putty on this IP address and also connect to Ambari on http://192.168.56.101:8080/. (Please note there is a onetime setup for Ambai in this version. Configuring is well documented in the Hortonworks Sandbox documentation)]]></content:encoded>
    </item>
    <item>
      <title>Page Compression and the 4038 Length Limitation</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/page-compression-and-the-4038-length-limitation</link>
      <pubDate>Tue, 07 Jun 2016 22:24:08 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=155</guid>
      <description><![CDATA[While it is well documented that when applying page compression to a table only in-row data will be...]]></description>
      <content:encoded><![CDATA[While it is well documented that when applying page compression to a table only in-row data will be compressed, it is not so well known that only strings up a maximum length of 4038 can be compressed with page compression. If you have ever turned on page compression for large blobs of data and wondered why you are not getting a good compression ratio this post will explain why.

There are several prerequisites to page compression. One well documented stipulation is that compression will only work on in-row data. If a varchar(MAX) is used the string data will only be held in-row if it is 8000 characters or less than in length. Varchar(MAX) data can be compressed with page compression but it has to be of a small enough string length. However, although 8000 characters and under will allow a record to fit in-row, it would need to be at least 4038 characters or less for page compression.

The following example shows page compression not working for in-row data. Here we create a table, add 1000 records to it with a length of 8000 characters, apply page compression and then return the number of pages associated with that table.
<pre><code>CREATE TABLE MSCompressionDemoTestTable (stringdata varchar(8000))
GO
INSERT INTO MSCompressionDemoTestTable (stringData) VALUES (REPLICATE('a', 8000))
GO 1000
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);   
SELECT in_row_data_page_count FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('MSCompressionDemoTestTable')
GO
DROP TABLE MSCompressionDemoTestTable</code></pre>
1000 pages are still being used by this table. No compression has been performed.

<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes1.png"><img class="alignnone  wp-image-156" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes1.png" alt="PageRes1" width="179" height="86" /></a>

To investigate why this is not working we can look at the extended event sqlserver.page_compression_attempt_failed.
<pre><code>CREATE EVENT SESSION [PageCompressionErrors] ON SERVER 
ADD EVENT sqlserver.page_compression_attempt_failed
WITH (MAX_MEMORY=4096 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=30 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=NONE,TRACK_CAUSALITY=OFF,STARTUP_STATE=OFF)
GO</code></pre>
<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageResEE.png"><img class="alignnone  wp-image-205" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageResEE.png" alt="PageResEE" width="360" height="347" /></a>

When attempting to compress the data the extended event is returning the failure_reason of OnlyOneRecordFound.

The algorithm that performs the page compression will look at each page it is compressing to see if there is more than one record on that page. If there is then it will attempt compression, if not then it will skip over that page. The algorithm sees no value in creating a dictionary and compressing just one record so ignores pages with one record in them.

If we half the size of the string data we will get two records per page and so the page compression will be successful.
<pre><code>CREATE TABLE MSCompressionDemoTestTable (stringdata varchar(8000))
GO
INSERT INTO MSCompressionDemoTestTable (stringData) VALUES (REPLICATE('a', 4000))
GO 1000
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);   
SELECT in_row_data_page_count FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('MSCompressionDemoTestTable')
SELECT sys.fn_PhysLocFormatter (%%PHYSLOC%%) LocationOfTheRecord FROM MSCompressionDemoTestTable
GO
DROP TABLE MSCompressionDemoTestTable</code></pre>
<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes2.png"><img class="alignnone  wp-image-165" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes2.png" alt="PageRes2" width="233" height="341" /></a>

We can see from the results that each page has two slots (or records). After we turned on page compression we have compressed from 1000 records down to 3 pages.

Up until now we have been looking at non unicode data. The following example uses an NVarchar (unicode) type.
<pre><code>CREATE TABLE MSCompressionDemoTestTable (stringdata Nvarchar(4000))
GO
INSERT INTO MSCompressionDemoTestTable (stringData) VALUES (REPLICATE(N'a', 4000))
GO 1000
SELECT sys.fn_PhysLocFormatter (%%PHYSLOC%%) LocationOfTheRecord FROM MSCompressionDemoTestTable
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);   
SELECT in_row_data_page_count FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('MSCompressionDemoTestTable')
GO
DROP TABLE MSCompressionDemoTestTable</code></pre>
<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes3.png"><img class="alignnone  wp-image-175" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes3.png" alt="PageRes3" width="210" height="328" /></a>

From the results we can see we have filled up the page as we have one page per record, but the page compression still compresses to 3 pages.

This is because before page compression is run row compression is first run. Row compression take any data types down to the smallest data type it can be. In this case NVarchar(4000) can be converted down to Varchar(4000) and Varchar(4000) as we have seen before will allows 2 records to held onto a page, so page compression succeeds.

In the following example we are explicitly turning on row compression so the impact can be demonstrated.
<pre><code>CREATE TABLE MSCompressionDemoTestTable (stringdata Nvarchar(4000))
GO
INSERT INTO MSCompressionDemoTestTable (stringData) VALUES (REPLICATE(N'a', 4000))
GO 1000
SELECT sys.fn_PhysLocFormatter (%%PHYSLOC%%) LocationOfTheRecordBeforeRowcompression FROM MSCompressionDemoTestTable
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = ROW);   
SELECT sys.fn_PhysLocFormatter (%%PHYSLOC%%) LocationOfTheRecordAfterRowCompression FROM MSCompressionDemoTestTable
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);   
SELECT in_row_data_page_count FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('MSCompressionDemoTestTable')
GO
DROP TABLE MSCompressionDemoTestTable</code></pre>
<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes4.png"><img class="alignnone  wp-image-185" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes4.png" alt="PageRes4" width="268" height="289" /></a>

The results show after row compression we have two rows per page so page compression will be able to compress and take us down to 3 pages

If we run the same example but this time with unicode data, the page compression will fail as the first step of row compression cannot compress enough to fit more than one record on a page.
<pre><code>CREATE TABLE MSCompressionDemoTestTable (stringdata Nvarchar(4000))
GO
INSERT INTO MSCompressionDemoTestTable (stringData) VALUES (REPLICATE(NCHAR(1234) + NCHAR(1299), 2000))
GO 1000
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);   
SELECT in_row_data_page_count FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('MSCompressionDemoTestTable')
GO
DROP TABLE MSCompressionDemoTestTable</code></pre>
<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes1.png"><img class="alignnone  wp-image-156" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes1.png" alt="PageRes1" width="204" height="98" /></a>

The largest string we can compress is 4038 characters of non unicode data when we are using a heap with no other columns. This example show this in action where the row compression stage is first managing to shrink the data into two records so they can fit on one page.
<pre><code>CREATE TABLE MSCompressionDemoTestTable (stringdata varchar(8000))
GO
INSERT INTO MSCompressionDemoTestTable (stringData) VALUES (REPLICATE('a', 4038))
GO 1000
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = ROW);  
SELECT sys.fn_PhysLocFormatter (%%PHYSLOC%%) LocationOfTheRecord FROM MSCompressionDemoTestTable
ALTER TABLE MSCompressionDemoTestTable REBUILD PARTITION = ALL WITH (DATA_COMPRESSION = PAGE);   
SELECT in_row_data_page_count FROM sys.dm_db_partition_stats WHERE OBJECT_ID = OBJECT_ID('MSCompressionDemoTestTable')
SELECT sys.fn_PhysLocFormatter (%%PHYSLOC%%) LocationOfTheRecord FROM MSCompressionDemoTestTable
GO
DROP TABLE MSCompressionDemoTestTable</code></pre>
<a href="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes5.png"><img class="alignnone  wp-image-195" src="https://msdnshared.blob.core.windows.net/media/2016/06/PageRes5.png" alt="PageRes5" width="192" height="265" /></a>

All these examples have been using a single heap with one column to give the maximum row size we can compress. Every column we add to the table will reduce the amount of string data we can compress as it will limit how many rows can fit on a page.

So when using page compression, you may be getting a much worst compression then expected if you have wide rows and long strings. It is always best to check before implementing page compression how many rows you can fit on each page after row compression has been performed. If that number is one then page compression will have no impact.]]></content:encoded>
    </item>
    <item>
      <title>CLRs, Web Services and JSON – Design Decisions</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/clrs-web-services-and-json-design-decisions</link>
      <pubDate>Thu, 26 May 2016 16:19:25 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=135</guid>
      <description><![CDATA[Recently I was working on a CLR that needed to talk to a web service and return a tabular dataset....]]></description>
      <content:encoded><![CDATA[Recently I was working on a CLR that needed to talk to a web service and return a tabular dataset. Within the development and deployment process there were several considerations concerning security and design. This post will documented these to help understand the challenges and options available.

Security problems should be considered early as they will colour how the CLR is developed.

If the CLR uses a DLL that is not installed on the SQL Server then the DLL will need to be installed. (A list of supported libraries in SQL can be found here <a href="https://msdn.microsoft.com/en-us/library/ms403279.aspx">https://msdn.microsoft.com/en-us/library/ms403279.aspx</a>)

For an unsafe assembly the database has to be set to Trustworthy, this is a non-default security setting so some companies are reluctant to change.

If the database cannot be made Trustworthy then the CLRs may be deployed to MSDB as MSDB is Trustworthy by default. The extended function or procedure can be referenced from the user database with the syntax msdb.dbo.&lt;CLRProcName&gt;. However, bear in mind if using Always On the MSDB database will not move across in a failover event. To achieve high availability you will need to deploy to all replicas that might be failed over to.

Using third party DLLs can sometimes be worked around by refactoring the code.

This example CLR needs to call the free movie database webservice <a href="http://www.omdbapi.com/">http://www.omdbapi.com/</a>. When called this websevice returns JSON in the following format

<a href="http://www.omdbapi.com/?t=Simply+Irresistible&amp;y=&amp;plot=short&amp;r=json">http://www.omdbapi.com/?t=Simply+Irresistible&amp;y=&amp;plot=short&amp;r=json</a>
<pre><code>{"Title":"Simply Irresistible","Year":"1999","Rated":"PG-13","Released":"05 Feb 1999","Runtime":"96 min","Genre":"Comedy, Drama, Fantasy","Director":"Mark Tarlov","Writer":"Judith Roberts","Actors":"Sarah Michelle Gellar, Sean Patrick Flanery, Patricia Clarkson, Dylan Baker","Plot":"A magical crab works wonders for a terrible chef's culinary skills, leading her towards the man of her dreams.","Language":"French, English","Country":"Germany, USA","Awards":"N/A","Poster":"http://ia.media-imdb.com/images/M/MV5BMTYyNTg3Mzg2M15BMl5BanBnXkFtZTcwNzczNjUyMQ@@._V1_SX300.jpg","Metascore":"27","imdbRating":"5.3","imdbVotes":"11,290","imdbID":"tt0145893","Type":"movie","Response":"True"}</code></pre>
Usually when dealing with JSON it is common to use Json.NET from Newtonsoft. Json.Net provides quick and easy methods to convert JSON into an object that can then easily be converted to a tabular format for SQL. However, this could not be used in this case due to the security policy as it would require the deployment of a DLL on the SQL Server.

SQL 2016 has new functions for handling JSON. The CLR would only need to return the JSON string and then OPENJSON() could be used to convert the data. As shown in the following diagram:

&nbsp;

<a href="https://msdnshared.blob.core.windows.net/media/2016/05/JSONSQL.png"><img class="alignnone  wp-image-147" src="https://msdnshared.blob.core.windows.net/media/2016/05/JSONSQL.png" alt="JSONSQL" width="1117" height="617" /></a>

However, in this case we were using a previous version of SQL.

Without SQL 2016 and without deploying Json.Net to the server the only option was to code in a way as to convert the JSON either within the CLR or within SQL Server with TSQL. Returning a tabled valued function from a CLR was a cleaner solution and as the JSON format returning from the web service was simple. This was achieved with simple string manipulation. (A regular expression could have been used but looking at the JSON we could break up into two columns using a few simple string replacements and Splits)

The resulting solution is as follows:
<pre><code>using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Collections;

public partial class StoredProcedures
{
    [SqlFunction(FillRowMethodName = "OMDSearch", TableDefinition = "Name nvarchar(400), Value nvarchar(4000)")]
    public static IEnumerable tvfnOMDSearch(String title, String year)
    {
        System.Net.WebRequest req = System.Net.WebRequest.Create(String.Concat(@"http://www.omdbapi.com/?t=", title, @"&amp;y=", year, @"&amp;plot=short&amp;r=json"));

        System.Net.WebResponse resp = req.GetResponse();
        System.IO.StreamReader sr = new System.IO.StreamReader(resp.GetResponseStream());

        string jres = sr.ReadToEnd().Trim();
        /*Remove JSON brackets and mark the columns with a not(¬) sign*/
        jres = jres.Replace("{\"", "").Replace("\"}", "").Replace("\":\"", "¬");
        /*Split out the lines into an array*/
        string[] movieinfo = jres.Split(new string[] { "\",\"" }, StringSplitOptions.RemoveEmptyEntries);

        return movieinfo;
    }
    public static void OMDSearch(Object obj, out SqlString Name, out SqlString Value)
    {
        string[] resarr = obj.ToString().Split('¬');
        Name = resarr[0].ToString();
        Value = resarr[1].ToString();
    }
}</code></pre>

This may be called as follows:
<pre><code>CREATE TABLE #tbMovies (MovieYear varchar(150), MovieName varchar(150))
GO

INSERT INTO #tbMovies VALUES
('2014',	'God''s Pocket'),
('1972',	'Malcolm X'),
('2012',	'Alter Egos'),
('2008',	'Step Up 2: The Streets'),
('2015',	'LEGO Friends'),
('2012',	'Nate &amp; Margaret'),
('2014',	'Castle'),
('2012',	'Jack and Jill'),
('2016',	'Carol'),
('2012',	'Dragons'),
('2011',	'Beastly'),
('2015',	'April 9th'),
('2015',	'A Girl Walks Home Alone At Night'), 
('2012',	'People Like Us')
GO
SELECT * FROM #tbMovies AS T
CROSS APPLY MSDB.dbo.tvfnOMDSearch(T.MovieName, T.MovieYear)</code></pre>

To give the following result:

<a href="https://msdnshared.blob.core.windows.net/media/2016/05/TVFResults.png"><img class="alignnone  wp-image-148" src="https://msdnshared.blob.core.windows.net/media/2016/05/TVFResults.png" alt="TVFResults" width="458" height="480" /></a>]]></content:encoded>
    </item>
    <item>
      <title>An Introduction to U-SQL in Azure Data Lake - Part 2</title>
      <link>https://docs.microsoft.com/archive/blogs/robinlester/an-introduction-to-u-sql-in-azure-data-lake-part-2</link>
      <pubDate>Wed, 25 May 2016 20:46:49 GMT</pubDate>
      <dc:creator><![CDATA[RobinLester]]></dc:creator>
      <guid
        isPermaLink="false">http://blogs.msdn.microsoft.com/robinlester/?p=125</guid>
      <description><![CDATA[There has been a certain amount of speculation on what the U in U-SQL stands for. Some have...]]></description>
      <content:encoded><![CDATA[There has been a certain amount of speculation on what the U in U-SQL stands for. Some have suggested it is because U comes after T in the alphabet. This would suggest U-SQL is a successor to T-SQL which is not the case, you will not be writing your SQL Server stored procedures in U-SQL any time soon, if ever. Some have suggested U stands for ubiquitous (“present, appearing, or found everywhere“ - Oxford Dictionaries) which for the moment at least is not the case. The best explanation for the U is that it stands for Unterseeboot, more commonly abbreviated to U-boat. A U-Boat being a German submarine. Why is this relevant? Because as a U-Boat can dive deep into water so U-SQL lets you dive deep into your data lake.

Within this post some more examples of how U-SQL may be used.

This first example is looking for the keyword XBOX from a twitter stream. It performs aggregations on that data to provide a list of topics where XBOX is mentioned along with a sentiment for the tweet.

Note that the C# regular expression is returning a bool so we have to convert to a Int32 before we can perform a SUM. Also note the use of the “quoting: true” command. As this twitter data is free text we add a quote identifier to the string data which allows us to read in the data even if the text has commas in it. (If we had this set to ‘false’ any commas in the text would cause an error.)
<pre><code>/*Read tweets that have already been serialized to JSON and saved to Azure Data Lake Store*/
@Twitter =
    EXTRACT CreatedAt DateTime,
    Topic String,
    SentimentScore float?,
    Text String,
    filename String
    FROM "/datalake/output/Twitter/{filename:*}"
    USING Extractors.Csv(quoting: true);

/*Group By topic*/
@XBOX = SELECT Topic,
       AVG(SentimentScore) AS AvgSentimentScore,
       SUM(RegExResult) AS XBox
    FROM
    (
    SELECT Topic.ToUpper() AS Topic,
    SentimentScore,
    Convert.ToInt32(System.Text.RegularExpressions.Regex.IsMatch(Text, "XBOX", System.Text.RegularExpressions.RegexOptions.IgnoreCase)) AS RegExResult
    FROM @Twitter) AS A
GROUP BY Topic;

/*Ouput the result*/
OUTPUT @XBOX
    TO "/SQLBits/TwitterXBOX.csv"
USING Outputters.Csv();</code></pre>
&nbsp;

This example demonstrates how to select the top records from a query. In SQL Server it is common to use TOP n to return a limited set of rows. In Hive LIMIT is used. In U-SQL FETCH as part of an order statement is the standard used to limit a return from a record set.
<pre><code>DECLARE @From string = "/DemoFolder/2014-05-cumbria-street.csv";
DECLARE @To string = "/DemoFolderResult/outputtop10.csv";

@policefiles =
EXTRACT CrimeID String,
Month String,
Reportedby String,
Fallswithin String,
Longitude String,
Latitude String,
Location String,
LSOAcode String,
LSOAname String,
Crimetype String,
Lastoutcomecategory String,
Context String
    FROM @From
    USING Extractors.Csv()
    ORDER BY Crimetype FETCH 10 ROWS;

OUTPUT @policefiles
    TO @To
USING Outputters.Csv();
</code></pre>
&nbsp;

As with Hive, complex data types may be used to manipulate the data in U-SQL. In this example we are looking for the keyword ‘Violence’ in our police crime data. We could do this with a regular expression, or a simple LIKE predicate, but to demonstrate the use of an array we will split the text data into individual words and then explode the array into a table so we may perform an equality select. We will then group by a location to find the locations where most violent crimes have taken place.
<pre><code>DECLARE @From string = "/DemoFolder/2014-05-cumbria-street.csv";
DECLARE @To string = "/DemoFolderResult/outputsplit.csv";

/*Return the files and tidy up the data by removing "On or near" from the location*/
@policefiles =
    SELECT *,
           Location.ToString().Replace("On or near ", "") AS TrimLocation
    FROM
    (
    EXTRACT CrimeID String,
            Month String,
            Reportedby String,
            Fallswithin String,
            Longitude String,
            Latitude String,
            Location String,
            LSOAcode String,
            LSOAname String,
            Crimetype String,
            Lastoutcomecategory String,
            Context String
    FROM @From
    USING Extractors.Csv()
ORDER BY Crimetype
FETCH 1000000 ROWS
    ) AS A;

//Turn the crime type into a SQL string array 
@SplitCrimeTypes =
    SELECT TrimLocation,
           new SQL.ARRAY(Crimetype.Split(' ')) AS split
    FROM @policefiles;

/*Search though the split out crime type to just look for crimes that have the word Violent in them
Then return the number of locations where a violent crime took place more than once*/
@Res =
    SELECT TrimLocation,
           b,
           COUNT( * ) AS Incidents
    FROM @SplitCrimeTypes
         CROSS APPLY
             EXPLODE(split) AS a(b)
    WHERE b == "Violence"
    GROUP BY TrimLocation,
             b
    HAVING COUNT( * ) &gt; 1;

OUTPUT @Res
TO @To
USING Outputters.Csv();</code></pre>
&nbsp;

Within Azure Data Lake tables may be created to allow data to be stored in a manner for faster data retrieval. Streaming data will generally enter the system in many small files, these can give poor performance when querying. With Hive data might be placed into an ORC file for faster data retrieval and in Spark Parquet files are generally used. In Azure Data Lake data may be transformed with U-SQL and placed into a table for faster data retrieval and better usage of parallel threads.

Tables in Azure Data Lake are clustered indexes that may be partitioned in one of several ways:

RANGE distribution will keep ranges of values together

ROUND ROBIN distribution will equally distribute the data. This should be used where other distributions skew the data too much. Skew can cause issues in Azure Data Lake as a vertex could end up with too much work to do and time out (vertexes time out after 5 hours)

HASH distribution use an automatic hash function to separate the data

DIRECT HASH distribution is for times when you want more control over how the data is hashed.

The larger the table the more extents it will be placed into. The more extents a file have the greater the ability Data Lake will have to run in parallel. This will improve performance and in addition queries will be able to use partition elimination where the table is partitioned by a relevant key.

The following demonstrates the different ways of hashing data:
<pre><code>//Create a new table with an index for storing police data 
DROP TABLE IF EXISTS PoliceTab;
CREATE TABLE PoliceTab(
CrimeID String,
Month DateTime?,
Reportedby String,
Fallswithin String,
Longitude double?,
Latitude double?,
Location String,
LSOAcode String,
LSOAname String,
Crimetype String,
Lastoutcomecategory String,
Context String,
DirectHASH long
//, INDEX idxLocation CLUSTERED (Longitude ASC, Latitude ASC) PARTITIONED BY RANGE(Longitude, Latitude) INTO 10);
//, INDEX idxLocation CLUSTERED (Longitude ASC, Latitude ASC) PARTITIONED BY ROUND ROBIN INTO 10);
//, INDEX idxLocation CLUSTERED (Longitude ASC, Latitude ASC) PARTITIONED BY HASH(CrimeID) INTO 10);
, INDEX idxLocation CLUSTERED (Longitude ASC, Latitude ASC) PARTITIONED BY DIRECT HASH(DirectHASH) INTO 10);

//Insert our police data into it
INSERT INTO PoliceTab
SELECT  CrimeID,
        Month,
        Reportedby,
        Fallswithin,
        Longitude,
        Latitude,
        Location,
        LSOAcode,
        LSOAname,
        Crimetype,
        Lastoutcomecategory,
        Context,
        Convert.ToInt64(DENSE_RANK() OVER (ORDER BY Crimetype)) AS DirectHASH
FROM (EXTRACT CrimeID String,
    Month DateTime?,
    Reportedby String,
    Fallswithin String,
    Longitude double?,
    Latitude double?,
    Location String,
    LSOAcode String,
    LSOAname String,
    Crimetype String,
    Lastoutcomecategory String,
    Context String
    FROM "/DemoFolder/2014-05-cumbria-street.csv"
    USING Extractors.Csv(silent: true)) AS A;</code></pre>
&nbsp;

In this final example we are taking a table and inserting adhoc data into it. This method may be used for creating lookup tables.
<pre><code>DROP TABLE IF EXISTS LookupTable;
CREATE TABLE LookupTable
(
    id int,
    Description String,
INDEX ixLookuptable CLUSTERED (id) PARTITIONED BY ROUND ROBIN);

INSERT INTO LookupTable
    SELECT * FROM 
        (VALUES
            (1,    "Red"),
            (2,    "Green"), 
            (3,    "Blue"),
            (4,    "Yellow")
        ) AS 
              A(id, Description);</code></pre>
&nbsp;

(In the last post on U-SQL it was noted that running multiple files in local mode “Run Local Plan” did not work – this limitation has now been removed)]]></content:encoded>
    </item>
  </channel>
</rss>