📜 ⬆️ ⬇️

Dynamic T-SQL and how it can be useful

In our projects we have to solve various problems. To solve some of them, we use dynamic T-Sql (hereinafter referred to as dynamic sq l).

What is dynamic sql for ? Everyone decides for himself. In one of the projects, using dynamic sql, we solved the tasks of building dynamic reports, in others - data migration. Also dynamic sql is indispensable in cases when you need to create / change / get data or objects, but the values ​​/ names come as parameters. Yes, this may seem absurd, but there are also such tasks.



Next, we will show several examples of how this can be implemented using dynamic sql .
')
There are several ways to execute a dynamic command:


These methods differ radically. On a small example, we will try to explain how they differ.

Sample code with EXEC / EXECUTE
 DECLARE @sql varchar(1000) DECLARE @columnList varchar(75) DECLARE @city varchar(75) SET @columnList = 'CustomerID, ContactName, City' SET @city = 'London' SELECT @sql = ' SELECT CustomerID, ContactName, City ' + ' FROM dbo.customers WHERE 1 = 1 ' SELECT @sql = @sql + ' AND City LIKE ''' + @city + '''' EXEC (@sql) 


As can be seen from the query above, we form a dynamic team. If you execute select @sql , the result will be the following:

 SELECT CustomerID, ContactName, City FROM customers WHERE City = 'London' 

What's wrong with that? - The request will work, and everyone will be happy. But still, there are several reasons why you should not do this:

  1. When writing a command, it is very easy to make a mistake with the number "'", since additional “'” must be specified to pass the text value to the request.
  2. With such a query, Sql injections are possible (SQL Injection). For example, it is worth setting a value for @city like this
     set @city = '''DROP TABLE customers--''' 
    - and the result will be sad, because the select operation succeeds, as DROP TABLE customers operation.
  3. It is possible that you will have several variables containing the codes of your commands. Something like EXEC(@sql1 + @sql2 + @sql3).
    What difficulties may arise here?
    It must be remembered that each command will work separately, although at first glance it may seem that the concatenation operation (@sql1 + @sql2 + @sql3) will be performed, and then the general command will be executed. You also need to remember that a general limitation is imposed on the EXEC command parameter of 4000 characters.
  4. There is an implicit type conversion, since Parameters are passed as a string.

What will change when using sp_executesql ? - It's easier for a developer to write code and debug it, because The code will be written almost like a normal Sql query.

Sample code with sp_executesql
 DECLARE @sqlCommand varchar (1000) DECLARE @columnList varchar (75) DECLARE @city varchar (75) SET @city = 'London' SET @sqlCommand = 'SELECT CustomerID, ContactName, City FROM customers WHERE City = @city' EXECUTE sp_executesql @sqlCommand, N'@city nvarchar(75)', @city = @city 


What has changed?

  1. Unlike EXECUTE when using sp_executesql , no type casting is necessary if we use typed parameters sp_executesql.
  2. This solves the problem with the extra "'".
  3. The security problem is solved - Sql injection (SQL Injection).

For both approaches, query plans are cached, but they are different. These differences are shown in Figure 1 and Figure 2 .

Receiving a request plan
 SELECT q.TEXT,cp.usecounts,cp.objtype,p.*, q.*, cp.plan_handle FROM sys.dm_exec_cached_plans cp CROSS apply sys.dm_exec_query_plan(cp.plan_handle) p CROSS apply sys.dm_exec_sql_text(cp.plan_handle) AS q WHERE q.TEXT NOT LIKE '%sys.dm_exec_cached_plans %' and cp.cacheobjtype = 'Compiled Plan' AND q.TEXT LIKE '%customers%' 


Plan a query when using exec


Query plan when using sp_executesql


Also, one of the advantages of using sp_executesql is the ability to return a value through the OUT parameter.

Below is an example of how we solved one of the problems in the project using dynamic sql .

Suppose we have a product (yes it doesn’t matter what it is: a product, a job application form, a personal profile). The point is that each object has its own set of properties (attributes) that characterizes it, and there may be a different number, and they will be of a different type. How to store in a database is an architecture problem.

For the client, a report was needed that was n rows per m columns. Where m was our attribute set. The report was collected on a group of objects or for some object from the group. But the meaning remains the same: each report contains a different number of columns for each group of objects.

Since the connection between objects initially existed, the solution was chosen without changing the database architecture. In our opinion, there may be several solutions to this problem:


Link to scripts for creating tables and query .

The report will be based on the usual query:

Main report code
 SELECT p.Id as ProductID, p.Name as [], pcp.Name as PropertiesName, vpp.Value as Value FROM dbo.Products p INNER JOIN dbo.PropertiesCategoryOfProducts pcp ON pcp.CategoryOfProductsId = p.CategoryOfProductsId INNER JOIN dbo.ValueOfProductProperty vpp ON vpp.ProductID = p.Id and vpp.PropertiesCategoryOfProductsId = pcp.Id where p.CategoryOfProductsId = @CategoryOfProductsId 


Request code to build a report
 SELECT p.Id as ProductID, p.Name as [], pcp.Name as PropertiesName, vpp.Value as Value FROM dbo.Products p INNER JOIN dbo.PropertiesCategoryOfProducts pcp ON pcp.CategoryOfProductsId = p.CategoryOfProductsId INNER JOIN dbo.ValueOfProductProperty vpp ON vpp.ProductID = p.Id and vpp.PropertiesCategoryOfProductsId = pcp.Id where p.CategoryOfProductsId = @CategoryOfProductsId      declare @CategoryOfProductsId int = 1 declare @PivotColumnHeaders nvarchar(max)= REVERSE(STUFF(REVERSE((select '[' + Name + ']' + ',' as 'data()' from dbo.PropertiesCategoryOfProducts t where t.CategoryOfProductsId = @CategoryOfProductsId FOR XML PATH('') )),1,1,'')) if(@PivotColumnHeaders>'') declare @PivotTableSQL nvarchar(max) BEGIN SET @PivotTableSQL = N' SELECT * from (SELECT p.Id as ProductID, p.Name as [], pcp.Name as PropertiesName, vpp.Value as Value FROM dbo.Products p INNER JOIN dbo.PropertiesCategoryOfProducts pcp ON pcp.CategoryOfProductsId = p.CategoryOfProductsId INNER JOIN dbo.ValueOfProductProperty vpp ON vpp.ProductID = p.Id and vpp.PropertiesCategoryOfProductsId = pcp.Id where p.CategoryOfProductsId = @CategoryOfProductsId ) as Pivot_Data PIVOT ( MIN(Value) FOR PropertiesName IN ( ' + @PivotColumnHeaders + ' ) ) AS PivotTable ' EXECUTE sp_executesql @PivotTableSQL, N'@CategoryOfProductsId int', @CategoryOfProductsId = @CategoryOfProductsId; END 


Let's look at what we wrote here:

Source: https://habr.com/ru/post/272807/


All Articles