Tag: T-SQL

  • How do you solve T-SQL problems?

    solvetsql

    Six scans, six logical reads. What could be the problem? There’s no problem if you’re always dealing with six rows of data. If you’re dealing with 6mm, however, this is likely to be a performance problem on your server. Many developers might write a query, check the statistics, and see something like that shown above, and think that it’s a good day’s work.

    Solving the problem is only half the job you should do. Solving things in an efficient way is the other half. When you tackle a new T-SQL challenge, what’s your methodology? Do you have one? I don’t need to know, though if you think you have a good one, post a note in the discussion, or perhaps write an article. If you aren’t sure how you begin, other than randomly trying ideas you vaguely understand or have seen others try, perhaps you want to start by reading Kathi Kellenberger’s Step by Step method.

    The image above comes from Kathi’s article, and is indeed the result of her first solution to a complex T-SQL problem. She lists out the basic steps she used to derive the solution, along with the business rules used. All too often I’ve found developers (including myself), start writing code without taking a few minutes to specifically state the business rules. Following that one step might eliminate lots of bugs in coding.

    Her solution worked, but with 6 scans occurring on her 11 rows of data, she knew this would be an issue. Moving forward she asked for help, which is perfectly acceptable way of working in your career. Try to solve it yourself, get a solution working, and then ask if it can be improved. Not only did she end up with a better performing solution, but she learned a bit more along the way. Something we all can do.

    Steve Jones


    The Voice of the DBA Podcasts

    We publish three versions of the podcast each day for you to enjoy.

  • Getting Information from a Database using Dynamic SQL

    I ran across someone that was building a restore script to automated their restores. This person wanted their script to work with any instance, and that means they’d need to find the path for the database files, if the database already existed.

    It was interesting to me, and I decided to give a solution a try, and I ended up using dynamic SQL, which I don’t love, but it worked. As I was digging through, I realized that all the database data is in sys.master_files. However I’d started this and it was an intriguing problem. I don’t love this solution, and I wouldn’t use it here, but there might be a place where you can use it.

    To find out if a database exists, you can easily use the sys.databases view to find it.

    USE master
    GO
    DECLARE @path VARCHAR(500)
       , @db VARCHAR(200)
    ;
    
    SELECT @db = 'AdventureWorks2008'
    
    IF EXISTS (SELECT name
             FROM sys.databases
     WHERE name = @db
     )

    This is a snippet, and you need more code for this to work, but it does work if you include the “then” and “else” blocks. If the database doesn’t exist, you can grab the default file paths from the registry if you want to build the restore script, but if it does, then what.

    You want to find the database files, but these aren’t stored in master. If you query sys.database_files, you’ll get this:

    dbfilepath

    I have 8 or 10 databases on this instance, but none appear. However if I query a specific database, I get the files.

    dbfilepath2

    How do I get this data, in a script, given that I can’t execute a “use” statement easily at runtime.

    There are probably a few ways, but for me, I decided dynamic SQL might make sense her. This is an administrative task, so it’s not likely to allow for SQL Injection as I wouldn’t expose this for users to run.

    The first step is to build my query. In this case, I want to execute this query:

     select physical_name
      from AdventureWorks2008.sys.database_files
       where file_id = 1

    This isn’t ideal, in that I could have many files for this database, but for now I’m concerned with just getting the primary data file.

    I can build this string dynamically like this. Note that I’ve assigned the result to a variable for now.

    DECLARE @sqlCommand nvarchar(1000)
    DECLARE @db varchar(75)
    DECLARE @file VARCHAR(500)
    SET @db = 'AdventureWorks2008'
    SET @sqlCommand = 'select @f = physical_name from ' + @db + '.sys.database_files where file_id = 1'
    
    SElect @sqlCommand

    However now I need to run this command and return a value. sp_executesql is a function that allows you to execute a string, pass in parameters, and assign them back. I can do this with this script.

    DECLARE @sqlCommand nvarchar(1000)
    DECLARE @db varchar(75)
    DECLARE @file VARCHAR(500)
    
    SET @db = 'AdventureWorks2008'
    SET @sqlCommand = 'select @f = physical_name from ' + @db + '.sys.database_files where file_id = 1'
    
    EXECUTE sp_executesql @sqlCommand, N'@f varchar(500) OUTPUT', @f=@file OUTPUT
    
    select @file

    If I run this, I’ll get the file path from the AdventureWorks2008 primary data file.

    That’s the first step in this process. If I wanted to complete it, I’d have to make sure I did this for each data file, probably using some temporary table instead of a variable, and storing all the physical paths and logical names, and using those to build a dynamic restore script.

    Or I could download this script: SQL 2005 Restore Script Generator

  • Adding Data to Filestream with T-SQL

    I’ve written on how to enable Filestream and how to add a filegroup, but I haven’t touched the Filestream impact on your tables. This post will look at the table side of Filestream and how you get your binary data into the database.

    Let’s assume you have a filestream enabled database. If you look at my File and Filegroups post, you’ll see how to do this. Given that, we now need to create a table and add data to it.

    Creating a Table with Filestream

    Filestream doesn’t have a separate entity like Filetables. Instead, the Filestream attribute is added to a column in a table, much like the identity property.

    I’ll create a simple table that holds Filestream data.

    CREATE TABLE FSDemo
    (
        id UNIQUEIDENTIFIER ROWGUIDCOL NOT NULL UNIQUE
      , name VARCHAR(20)  
      , jpg varbinary(MAX) FILESTREAM
    );
    go

    Here we are creating a basic table that has an ID and name, which are essentially meta data for our filestream data. You could have any amount of information in this table, but the Filestream data is stored in the jpg field, where we have a varbinary(max) datatype, and the FILESTREAM attribute on the table.

    I could have a more complex schema, such as the Production.Document table in Adventureworks2008 (shown below)

    CREATE TABLE [Production].[Document](
        [DocumentNode] [hierarchyid] NOT NULL,
        [DocumentLevel]  AS ([DocumentNode].[GetLevel]()),
        [Title] [nvarchar](50) NOT NULL,
        [Owner] [int] NOT NULL,
        [FolderFlag] [bit] NOT NULL,
        [FileName] [nvarchar](400) NOT NULL,
        [FileExtension] [nvarchar](8) NOT NULL,
        [Revision] [nchar](5) NOT NULL,
        [ChangeNumber] [int] NOT NULL,
        [Status] [tinyint] NOT NULL,
        [DocumentSummary] [nvarchar](max) NULL,
        [Document] [varbinary](max) FILESTREAM  NULL,
        [rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
        [ModifiedDate] [datetime] NOT NULL
        );

     

    Adding Data

    To add data to this table, we have a variety of choices using T-SQL. It’s possible to use an application, and pass in parameters that are streamed to the table, but for the simplicity of this example, let’s show two ways in T-SQL.

    First, let’s look at a direct insert. I can CAST my data to varbinary, and then insert it into the table. I have a small image that looks like this:

    circle

    It’s a simple circle, very small. The actual data in this image is this:

    0xFFD8FFE000104A46494600010101006000600000FFE100684578696600004D4 D002A000000080004011A0005000000010000003E011B00050000000100000046 01280003000000010002000001310002000000120000004E00000000000000600 000000100000060000000015061696E742E4E45542076332E352E313000FFDB00 43000201010201010202020202020202030503030303030604040305070607070 706070708090B0908080A0807070A0D0A0A0B0C0C0C0C07090E0F0D0C0E0B0C0C 0CFFDB004301020202030303060303060C0807080C0C0C0C0C0C0C0C0C0C0C0C0 C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C 0C0C0C0C0CFFC0001108000A000D03012200021101031101FFC4001F000001050 1010101010100000000000000000102030405060708090A0BFFC400B510000201 0303020403050504040000017D010203000411051221314106135161072271143 28191A1082342B1C11552D1F02433627282090A161718191A25262728292A3435 363738393A434445464748494A535455565758595A636465666768696A7374757 67778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3 B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E 8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F01000301010101010101010100000000 00000102030405060708090A0BFFC400B51100020102040403040705040400010 277000102031104052131061241510761711322328108144291A1B1C109233352 F0156272D10A162434E125F11718191A262728292A35363738393A43444546474 8494A535455565758595A636465666768696A737475767778797A828384858687 88898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C 4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3F4F5F6F7F8F9 FAFFDA000C03010002110311003F00E97FE0E87FF82CA7ED35FB04FED85E09F03 FC2BD6A5F87BE1097408B5C4D523D2ADAF1BC41746795248CBDC4722F970848C1 8940399373E43478FD75FF00827CFC68F177ED17FB0FFC2AF1D78F3461A078C7C 59E19B2D4F57B110B42B14F244199846DF346AFC384392A1C024E335E95E2EF87 BE1FF880968BAF687A3EB6B612F9F6C2FECA3B916F27F7D3783B5BDC60D6C5007 FFFD9

    I can insert this data directly into my table:

    INSERT INTO FSDemo(ID, name, jpg) 
    Values (NEWID()
          , 'circle.jpg'
          , 0xFFD8FFE000104A46494600010101006000600000FFE100684578696600004D4D002A000000080004011A0005000000010000003E011B0005000000010000004601280003000000010002000001310002000000120000004E00000000000000600000000100000060000000015061696E742E4E45542076332E352E313000FFDB0043000201010201010202020202020202030503030303030604040305070607070706070708090B0908080A0807070A0D0A0A0B0C0C0C0C07090E0F0D0C0E0B0C0C0CFFDB004301020202030303060303060C0807080C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0C0CFFC0001108000A000D03012200021101031101FFC4001F0000010501010101010100000000000000000102030405060708090A0BFFC400B5100002010303020403050504040000017D01020300041105122131410613516107227114328191A1082342B1C11552D1F02433627282090A161718191A25262728292A3435363738393A434445464748494A535455565758595A636465666768696A737475767778797A838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE1E2E3E4E5E6E7E8E9EAF1F2F3F4F5F6F7F8F9FAFFC4001F0100030101010101010101010000000000000102030405060708090A0BFFC400B51100020102040403040705040400010277000102031104052131061241510761711322328108144291A1B1C109233352F0156272D10A162434E125F11718191A262728292A35363738393A434445464748494A535455565758595A636465666768696A737475767778797A82838485868788898A92939495969798999AA2A3A4A5A6A7A8A9AAB2B3B4B5B6B7B8B9BAC2C3C4C5C6C7C8C9CAD2D3D4D5D6D7D8D9DAE2E3E4E5E6E7E8E9EAF2F3F4F5F6F7F8F9FAFFDA000C03010002110311003F00E97FE0E87FF82CA7ED35FB04FED85E09F03FC2BD6A5F87BE1097408B5C4D523D2ADAF1BC41746795248CBDC4722F970848C18940399373E43478FD75FF00827CFC68F177ED17FB0FFC2AF1D78F3461A078C7C59E19B2D4F57B110B42B14F244199846DF346AFC384392A1C024E335E95E2EF87BE1FF880968BAF687A3EB6B612F9F6C2FECA3B916F27F7D3783B5BDC60D6C5007FFFD9
          );
    go

    The other method I can show is a direct insert, streaming from the OPENROWSET command. Here I’ll pic an image on my system and insert it.

    INSERT INTO FSDemo(ID, name, jpg) 
    Values (NEWID()
          , 'uma.jpg'
          , (SELECT * FROM OPENROWSET(BULK N'C:\Users\Steve\Documents\SampleData\Uma.jpg', SINGLE_BLOB) AS CategoryImage)
          );
    go

    Now if I query the table

    SELECT
      id, name, jpg
     from FSDemo;
    go

    I see my data.

    Capture_027

    When I look at this in Management Studio, I see the actual hex data, which isn’t easy to understand. I need another way to extract this data and render it. However I can show one more quick demo here. Let me take an XML document and insert it:

    declare @list XML
    select @list = '
    <Lists>
      <Groceries Store=''Safeway''>
        <Item>Milk</Item>
        <Item>Eggs</Item>
        <Item>Bread</Item>
      </Groceries>
      <Drinks Store=''Tipsys''>
        <Drink>Fat Tire</Drink>
        <Drink>Klinker Brick Cabernet</Drink>
        <Drink>Patron</Drink>
      </Drinks>
      <Ranch>
        <Item>electric fence ribbon</Item>
      </Ranch>
    </Lists>'
    ;
    INSERT INTO FSDemo(ID, name, jpg) 
    Values (NEWID()
          , 'sample.xml'
          , CAST( @list AS VARBINARY(MAX))
          );
    go

    Once this is done, the data is in binary, as I see from the selection from the table.

    Capture_029

    However if I cast this result back, I get the data as expected.

    Capture_028

    Hopefully this helps you to understand how to insert data into a Filestream table. In another post, I’ll look at how you can extract this data for rendering.

  • Inserting Binary Data

    There are a number of articles on dealing with the insertion of binary data into SQL Server using BULK INSERT, OPENROWSET, and even Java. However I saw someone asking recently how to insert data dynamically and thought that was an interesting question.

    If you are working with some front end language, this is easy with the various ODBC/OLEDB/ADO/etc interfaces into SQL Server. These languages allow you to specify parameters as a binary stream.

    However if you need to insert this data in T-SQL, what can you do? I know that there is a CAST function and that should work with varbinary. Let’s test something:

    CREATE TABLE Binarytest
    ( id INT
    , note VARBINARY(500)
    )
    ;
    INSERT BinaryTest SELECT 1, CAST( 'A' AS VARBINARY)
    
    SELECT note, CAST( note AS VARCHAR) FROM BinaryTest

    This returns:

    binary1

    I also know that I can directly take binary data in SQL Server and work with it, as long as I specify it’s binary. So I can do this:

    INSERT BinaryTest SELECT 1, 0x41

    If I now query my table, I get:

    binary2

    Not the easiest format to work with, but if I construct binary data somehow, I can work with it in T-SQL if I need it.