@@rowcount and print

by Allan Svelmøe Hansen June 07, 2010 19:45

Using @@rowcount is nice at times to get the number of rows affected by the last SQL statement. However sometimes you get an unexpected result.
One such situation is with the print statement which is often used as debugging statement in a query.
If looking at the SQL statement:
SELECT 1
UNION
SELECT 
2
SELECT @@ROWCOUNT
  


Then the @@rowcount naturally will return the value 2.
However if we insert a print statement just before the rowcount like this:

SELECT 1
UNION
SELECT 
2
PRINT 
'test'
SELECT @@ROWCOUNT
  

Rowcount now suddenly is 0.

That means the print statement changes the rowcount despite it being just a print to the console window.
Something to be aware of if relying on @@rowcount.

Bookmark and Share DotnetKicks dotnetshoutout

The MERGE syntax - syntax and performance

by Allan Svelmøe Hansen May 21, 2010 15:40

Normally when you have rows from one table you want to move over into the other, you'll have to run both an update query to get your existing rows updated with the new values and insert query to get new rows over to your target. That means you have to write two queries. With the merge, you only need one as it performs both the update and the insert.
So let's take a look at it.

First, I'll create 2 tables with dummy data; a tblSource and a tblTarget.
The scripts for creating the tables and data can be found here merge_setup_20100521.sql (3.58 kb), but basically it's just a target and source table with an ID, ValA, ValB and ValC with a clustered index on ID and some dummy data.
So to do the update/insert it would look like this:
UPDATE MyTarget SET 
   
MyTarget.ValA MySource.ValA

   
MyTarget.ValB MySource.ValB

   
MyTarget.ValC 
MySource.ValC 
FROM dbo.tblTargetInUp AS 
MyTarget  
INNER JOIN dbo.tblSourceInUp AS MySource ON MyTarget.ID 
MySource.ID 

INSERT INTO 
dbo.tblTargetInUp 
SELECT IDValAValB
ValC 
FROM dbo.tblSourceInUp AS 
MySource  
WHERE NOT EXISTS (SELECT FROM dbo.tblTargetInUp AS MyTarget WHERE MyTarget.ID MySource.ID
)  
 

If we look at the execution plan, I get an estimated cost of 9.1754 for the update and 2.48725 for the insert, meaning a combined cost of 11.66265 for 33.333 rows updated and 33.3334 rows inserted, into a target of 66.666 rows.

Now do remember, the estimated cost is just a number for how the query runs in my environment, it can't be taken as a direct number and transferred to another system - I'm only interested in the relative comparison with the merge. More data, more indexes, more variations will all affect the actual numbers. Also if looking at the execution plan, it is clear it is two queries we fire, meaning that all the overhead which goes into running one query will be doubled for this. But it was how you'd have to do merges in the past.

Now, with the merge syntax we can do it like this:
MERGE dbo.TblTargetMerge AS MyTarget USING 
(   
   
SELECT FROM dbo.TblSourceMerge AS 
TS 
AS 
MySource 
ON MyTarget.ID 
MySource.ID 
WHEN MATCHED 
THEN  
   UPDATE SET 
       
MyTarget.ValA MySource.ValA

       
MyTarget.ValB MySource.ValB

       
MyTarget.ValC 
MySource.ValC 
WHEN NOT MATCHED 
THEN 
   INSERT  
   
(IDValAValBValC

   
VALUES 
   
(MySource.IDMySource.ValAMySource.ValBMySource.ValC

  

Note that the syntax takes both the update and insert in the WHEN MATCHED and WHEN NOT MATCHED.
For the complete overview over the syntax, I'll refer you to the documentation by microsoft: MERGE (Transact-SQL).
But basically – you MERGE into a table using a source, and then define the ON clause (as you would a join), and then specify the WHEN MATCHED and the WHEN NOT MATCHED clauses.

One thing I'll expand on myself though, is the OUTPUT clause which can also be coupled on to the merge. I mentioned the OUTPUT clause myself recently.
The important thing is that you can couple the $action to the output clause and get information about whether you merged the data or you inserted the data, meaning whether the row was matched or not matched.
Like this:

MERGE dbo.TblTargetMerge AS MyTarget USING 
<...snipped FOR being brief...

OUTPUT $ACTION
rest_of_select_here 
;  
  

Nifty.
Anyways - once we've build this query, we can look at the execution plan, and here we clearly see it is handled as one query.
And in my case, the estimated cost is 8.407 for the same number of rows as above, meaning we’ve saved about 27.9% just by changing syntax.

Now, this structure I used to compare is very simple - with a simple matching of ID to ID and then just insert/overwrite everything so the actual result may vary (naturally), however the merge syntax does appear to be faster and with the added bonus of keeping the query combined into one syntax rather than divided into two different queries.
 
I must admit, I do like the merge syntax myself once I learned to read and write them.

Bookmark and Share DotnetKicks dotnetshoutout

A STR issue

by Allan Svelmøe Hansen May 20, 2010 17:49

I've seen many uses, and some misuses, of the STR function over time.

One of the more "easy to spot problems" is that the STR function returns char datatype, meaning it'll pad the result with spaces and people need to trim the result.

Today I saw an even worse issue. The STR function takes a float argument. This means if you feed it a string (yes, I've seen it done) it will implicit convert that string to a float if it can. That in itself can cause an error, but even worse, it can cause a difficult to find bug.
Suppose you have STR('0001') and run that, you'll after trimming end up with the result of '1'. Why?
Because '0001' is converted to a float, which - as we all know - is 1, which then will be cast to a string.

Bookmark and Share DotnetKicks dotnetshoutout

Tags: , , ,

SQL

Changing domain

by Allan Svelmøe Hansen May 19, 2010 16:11

I've decided to change my previous domain "www.sqlstuff.dk" to this new one "www.execsql.com" - I apologize for any inconvenience which might happen as a result, but it is better done now then later :)

All access to sqlstuff should get a 301 redirect to the new www.execsql.com domain and hopefully the transition should be smooth.

So welcome to EXEC(@sql) and bye bye to SQLStuff.dk :)

 

 

Bookmark and Share DotnetKicks dotnetshoutout

Tags:

SQL XML Bulk Load - more XSD syntax

by Allan Svelmøe Hansen April 17, 2010 13:43

I have posted a couple of times about the XML/XSD syntax used for SQL XML Bulk Load but mostly the syntax have been pretty basic, so I wanted to expand a little on the syntax with a more complex example.

So this time I’ll have an XML file structured like this. As can be seen this XML file consists of a couple of nested tags and is a structure that’s realistic when it comes to data imports. In fact it is a minimization of an existing one I have used for work, where I have removed duplicated structures to provide a cleaner example.
The file consists of repeated details tags with content and attributes used, a product files section containing a repeated tag and a nesting of Product Lists. It should cover most of the situations which one would encounter.
The only thing I do not use in this instance is composite keys, but they’re no different compared to using a singular one. I’ve written about composite keys and SQL XML Bulk Load here.
Anyways, let’s tackle the XSD for this file. (If new to the SQL XML Bulk Load, please read my SQL XML Bulk Load - basic XSD syntax post first)

The first tag, ProductID, is simple enough and covered in the basic syntax blog post.
It requires a database table containing this field. Any subsequent values placed on the same level of the XML file would be included into the same table.
So we’ll make a Product table:
CREATE TABLE Product(
 
ProductID INT 
NOT NULL
)


The XSD field will then be mapped to this table. And because I’ve named the field the same as the tag in the XML file I do not need to map the field.
This gives me the segment:
<xs:element Name="Product" sql:relation="Product" maxOccurs="unbounded">
          <
xs:complexType
>
            <
xs:sequence
>
              <
xs:element Name="ProductID" />
  


The details section is next. For this we need to set up a relationship because the section can be repeated several times, so we need to map it out into a related table.
For this section we’ll need the table:
CREATE TABLE Details(
 
Name VARCHAR(MAX
) NULL,
 
[Description] VARCHAR(MAX
) NULL, 
 
LanguageCode VARCHAR(5
) NOT NULL,
 
ProductID INT 
NOT NULL
)
  


This can then hold the Name and Description tags from the section, and the attribute “LanguageCode” and the key ProductID to map it to the Product table/Product section.
Because we need a relationship we’ll have to map it out:
<sql:relationship Name="Detail"
            
parent=
"Product"
            
parent-key=
"ProductID"
            
child=
"Details"
            
child-key=
"ProductID"
            
/> 
  


And this then provides the following section or the XSD
<xs:element Name="Details" sql:relation="Details" sql:relationship="Detail"
   <
xs:complexType

       <
xs:sequence
>
            <
xs:element Name="Name" 
/>
            <
xs:element Name="Description" 
/> 
       </
xs:sequence

       <
xs:attribute Name="LanguageCode" Type="xs:string" use="required" 
/> 
   </
xs:complexType
>
</
xs:element
> 


As can be seen we map the section to the relationship and the table relation.
For the section of Files, we’ll need to expand a little more. Again we need a relationship to tell the bulk load which keys to use.
First the table to import into:
CREATE TABLE Files 
  
ProductID INT 
NOT NULL, 
  
[Type] VARCHAR(255
) NULL, 
  
[FileName] VARCHAR(255
) NULL
)
  

Then the relationship in the XSD file
<sql:relationship Name="ProductFiles" 
        
parent=
"Product" 
        
parent-key=
"ProductID" 
        
child=
"Files" 
        
child-key=
"ProductID" 
        
/> 
  


Just like the previous one.

The XSD section will then look like the following:
<xs:element Name="ProductFiles" sql:is-constant="1">
  <
xs:complexType
>
    <
xs:sequence
>
      <
xs:element maxOccurs="unbounded" Name="FileName" 
          
sql:relationship="ProductFiles" sql:relation="Files">
        <
xs:complexType
>
          <
xs:simpleContent
>
            <
xs:extension base="xs:string"
>
              <
xs:attribute Name="type" Type="xs:string" use="required" 
/>
            </
xs:extension
>
          </
xs:simpleContent
>
        </
xs:complexType
>
      </
xs:element
>
    </
xs:sequence
>
  </
xs:complexType
>
</
xs:element>


We have the is-constant=”1” because this section can only exists once. The relationship and relation is put on the “FileName” element. The “tricky” part here is the xs:extension which is needed because otherwise the Bulk Load will throw an error that you’re trying to map multiple FileName elements. <ERROR>

On to the List section.
Now this is a more difficult one, because here we’ll need a double relationship. We need to map the ProductID to the Type and then the Type to the multiple ProductID’s within that section. So we need the following tables:
CREATE TABLE ProductList (
                
[ID] INT IDENTITY(1,1
) NOT NULL,
                
ProductID INT 
NOT NULL,
                
Header VARCHAR(255
) NULL
    )
CREATE TABLE ProductListProduct
(
                
ProductListID INT 
NOT NULL,
                
ProductID 
INT
    
  


Here we make an Identity ID in the ProductList which will be the foreign key in the ProductListProduct:ProductListID column.
So we need the two following relationships in our XSD
<sql:relationship Name="ProductToList"
                       
parent=
"Product"
                       
parent-key=
"ProductID"
                       
child=
"ProductList"
                       
child-key=
"ProductID"
                        
/>


<
sql:relationship Name=
"ListToProduct"
                  
parent=
"ProductList"
                  
parent-key=
"ID"
                  
child=
"ProductListProduct"
                  
child-key=
"ProductListID"
                        
/>


To map the relationship. The only difference here is that the parent-key for ListToProduct is the identity column from the ProductList table, and it will be used as the child-key in the column ProductListID.
And this gives the following XSD section

<xs:element Name="ProductList" sql:is-constant="1">
  <
xs:complexType
>
    <
xs:sequence
>
      <
xs:element Name="List" sql:relationship="ProductToList" sql:relation="ProductList"
>
        <
xs:complexType
>
          <
xs:sequence
>
            <
xs:element maxOccurs="unbounded" Name="ProductID" Type="xs:integer" 
             
sql:relationship="ListToProduct" sql:relation="ProductListProduct" 
/>
          </
xs:sequence
>
          <
xs:attribute Name="header" Type="xs:string" use="required" 
/>
        </
xs:complexType
>
      </
xs:element
>
    </
xs:sequence
>
  </
xs:complexType
>
</
xs:element

 
Again we have the is-constant annotation.
We then map the element List to the relationship ProductToList as defined previously and to the table ProductList. Because header is then an attribute named the same as the field in the database, it’ll map automatically as usual.
However we’ll then map the inner element “ProductID” within the List element to the next relationship ListToProduct and the table ProductListProduct.

The SQL XML Bulk Load will then automatically handle the identity column in the ProductList table and map it to the ProductListID column in the ProductListProduct table.

This gives us this following complete XSD file

And when running the bulk load with this XML and this XSD we get the following result:

Result for importing SQL XML Bulk Load

Bookmark and Share DotnetKicks dotnetshoutout

Powered by BlogEngine.NET 1.6.1.0
Theme by Mads Kristensen | Modified by Mooglegiant

About:
Allan Svelmøe Hansen

My real name is Allan Svelmøe Hansen.
I live in Denmark, where I work as a developer for hedal:kruse:brohus using SQL Server and the .NET framework since 2004.
My primary fields of expertise is back-end data integration, database design and optimization.


       View Allan Svelmøe Hansen's profile on LinkedIn     

Disclaimer

The opinions expressed herein are my own personal opinions and thoughts and does not represent my employers view in any way, nor are my results guaranteed for all situations.
Content is presented "as is", with no warranty.