{"id":81967,"date":"2018-12-11T02:59:32","date_gmt":"2018-12-11T02:59:32","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=81967"},"modified":"2018-12-13T16:58:18","modified_gmt":"2018-12-13T16:58:18","slug":"technique-and-simple-utility-to-determine-the-datatype-of-a-scalar-t-sql-expression","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/blogs\/technique-and-simple-utility-to-determine-the-datatype-of-a-scalar-t-sql-expression\/","title":{"rendered":"Technique and Simple Utility to Determine the Datatype of a Scalar T-SQL Expression"},"content":{"rendered":"<p><em>Note: post revised to take into consideration tips from comments<\/em><\/p>\n<p>The other day, I was working with a query where someone had put together an expression that had something along the lines of:<\/p>\n<pre class=\"lang:none theme:none\">CASE WHEN Value = 'X' THEN NULL\r\n       ELSE CAST(SomeOtherValue as numeric(18,2) \/ 12.0 \r\n     END AS AnnualisedSomeOtherValue<\/pre>\n<p>And I thought to myself&#8230; &#8220;Self, what will the datatype of this be? Will it fit what the programmer wanted it to be? Is it weird I addressed myself as self?&#8221;\u00a0 Instinctively, I wanted to believe it would be a numeric(18,2) because 12.0 should be less precise than the 18,2 numeric&#8230; But I had to know. (If you try this yourself, see what happens as you add zeros to the end, like 12.0000!) So, I went and coded a few statements using a sql_variant datatype to hold the expression value and SQL_VARIANT_PROPERTY to interrogate its properties:<\/p>\n<pre class=\"lang:none theme:none \">DECLARE @switch char(1) = 'Y';\r\nDECLARE @value sql_variant = CASE WHEN @switch = 'X'\r\n                                      THEN NULL\r\n                                  ELSE CAST('100.20' AS numeric(18, 2)) \r\n                                                                 \/ 12.0\r\n                             END;\r\nSELECT @value,\r\n       SQL_VARIANT_PROPERTY(@value, 'BaseType') AS DataType,\r\n       SQL_VARIANT_PROPERTY(@value, 'Precision') AS NumericPrecision,\r\n       SQL_VARIANT_PROPERTY(@value, 'Scale') AS NumericScale;<\/pre>\n<p>Since X &lt;&gt; Y, this returns the 100.20 \/ 12.0 value, which is output into a numeric(23,6). Interesting:<\/p>\n<pre class=\"lang:none theme:none\">Value         DataType       NumericPrecision   NumericScale\r\n------------- -------------- ------------------ -------------\r\n8.350000      numeric        23                 6<\/pre>\n<p>Now, what would the value of NULL be output into?<\/p>\n<pre class=\"lang:none theme:none \">DECLARE @switch char(1) = 'X';\r\nDECLARE @value sql_variant = \r\n       CASE WHEN @switch = 'X' THEN NULL\r\n            ELSE CAST('100.20' AS numeric(18,2)) \/ 12.0 END<\/pre>\n<p>This just returns NULLs when evaluating the sql_variant value:<\/p>\n<pre class=\"lang:none theme:none\">Value         DataType       NumericPrecision   NumericScale\r\n------------- -------------- ------------------ --------------\r\nNULL          NULL           NULL               NULL<\/pre>\n<p>Even if you cast the NULL value<\/p>\n<pre class=\"lang:none theme:none\">DECLARE @switch char(1) = 'X';\r\nDECLARE @value sql_variant = CASE WHEN @switch = 'X' \r\n                                       THEN CAST(NULL as numeric(18,2))\r\n                                  ELSE CAST('100.20' AS numeric(18,2)) \r\n                                                            \/ 12.0 END<\/pre>\n<p>The sql_variant with the NULL output will not register a type, which makes some sense, as a NULL is generally typeless on its own, without a type coming from the container, like a variable or column. If you have ever worked with SSIS, you know that the container of the output will have a datatype, which (as was taught me by Phil Factor in the comments!) can be fetched using the system function sys.dm_exec_describe_first_result_set, by taking your expression and making it a properly delimited string, thusly:<\/p>\n<pre class=\"lang:none theme:none\">SELECT system_type_name FROM sys.dm_exec_describe_first_result_set('\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0DECLARE @switch char(1) = ''X''; \u00a0 \r\n             SELECT CASE WHEN @switch = ''X'' THEN CAST(NULL as numeric(18,2)) \u00a0 \r\n                         ELSE CAST(''100.20'' AS numeric(18,2)) \/ 12.0 END', NULL, 1)<\/pre>\n<p>This tells us that the datatype is numeric(23,6), exactly as we have seen earlier.<\/p>\n<p>Now, I wanted to build something to make this easy the next time. So, the following utility is a simple T-SQL batch, which uses SQLCMD variables in Management Studio, will give me the characteristics of an expression from a table expression in context of the table. Thanks to Phil Factor, we can see not only the results of the expression, and the values of additional columns, but we will also get the datatype of the other columns in the output as well.<\/p>\n<p>Basically, put this code in SSMS, turn on SQLCMD mode, and fill in the blanks with the bits of code in the variables (they should be self-explanatory with the comments\/example), and you will see the datatype of your expression (note that since the technique requires the ability to create a stored procedure, I left in the SQL_VARIANT_PROPERTY output if you do not have rights to create a procedure (I used a procedure so the setvar values could be basic query text with no need to double up on single quotes, which can get extremely tedious for larger queries\/expressions).\u00a0<\/p>\n<pre class=\"lang:none theme:none\">--expression to check the resultant datatype of, Can't reference\r\n--variables, but can be any valid expression\r\n:setvar expressionToTest \"CASE WHEN UnitPrice &lt; 10 THEN NULL ELSE CAST(UnitPrice AS numeric(18,2)) \/ 12 END\"\r\n\r\n--comma delimited list of additional columns or expressions\r\n:setvar additionalColumns \"UnitPrice,InvoiceId\"\r\n\r\n--everything starting with FROM in a query. No need for anything \r\n--but a blank if the expression doesn't reference a table. (can be \r\n--multiline, will turn off syntax coloring in SSMS)\r\n:setvar expressionFROM \"FROM Sales.InvoiceLines ORDER BY InvoiceLineId\"\r\n\r\n--all rows will have the same datatype, but you may want to see \r\n--that for yourself\r\n:setvar numRowsToTest 1\r\n\r\nSELECT TOP($(numRowsToTest))\r\n       $(expressionToTest) AS EvaluatedExpressionValue,\r\n       SQL_VARIANT_PROPERTY(CAST($(expressionToTest) AS sql_variant), \r\n                                                'BaseType') AS DataType,\r\n\r\n       --Numeric data\r\n       SQL_VARIANT_PROPERTY(\r\n           CAST($(expressionToTest) AS sql_variant), 'Precision') \r\n                                                 AS NumericPrecision,\r\n\r\n       SQL_VARIANT_PROPERTY(CAST($(expressionToTest) AS sql_variant), \r\n                                            'Scale') AS NumericScale,\r\n\r\n       --Note: Unicode sizes may vary now, and will change in 2019 \r\n       --with UTF-8, but 2 bytes is generally true for most collations\r\n       CASE WHEN SQL_VARIANT_PROPERTY(\r\n                     CAST($(expressionToTest) AS sql_variant), 'BaseType') \r\n                                      IN ('nchar', 'nvarchar')\r\n            THEN CAST(SQL_VARIANT_PROPERTY(\r\n                              CAST($(expressionToTest) AS sql_variant),\r\n                              'MaxLength') AS int) \/ 2\r\n            WHEN SQL_VARIANT_PROPERTY(\r\n                     CAST($(expressionToTest) AS sql_variant), 'BaseType') \r\n                                      IN ('varbinary', 'varchar', 'char' )\r\n                THEN CAST(SQL_VARIANT_PROPERTY(\r\n                              CAST($(expressionToTest) AS sql_variant),\r\n                              'MaxLength') AS int) \r\n       END AS MaxLength,\r\n       SQL_VARIANT_PROPERTY(\r\n           CAST($(expressionToTest) AS sql_variant), 'Collation') \r\n                                     AS StringCollation,\r\n       SQL_VARIANT_PROPERTY(\r\n           CAST($(expressionToTest) AS sql_variant), 'TotalBytes') \r\n                                     AS TotalBytesOfStorageInclOverhead,\r\n       '--' AS [--],\r\n       $(additionalColumns) \r\n       $(expressionFROM);\r\nGO\r\nDROP PROCEDURE IF EXISTS dbo.[###DropMe###] --most heinous name possible\r\nGO\r\nCREATE PROCEDURE [###DropMe###]\r\nAS\r\nSELECT TOP($(numRowsToTest))\r\n       $(expressionToTest) AS EvaluatedExpressionValue,\r\n       $(additionalColumns) \r\n       $(expressionFROM);\r\nGO\r\n--get the system metadata for the columns... There is a LOT more detail in the result\r\n--set, if you want to know more about the output of columns\/expressions\r\nSELECT name, dm_exec_describe_first_result_set.is_nullable, system_type_name \r\nFROM sys.dm_exec_describe_first_result_set('###DropMe###',NULL,1);\r\nGO\r\n--cleanup\r\nDROP PROCEDURE IF EXISTS dbo.[###DropMe###]\r\n<\/pre>\n<p>Now, we can see that the expression &#8220;CASE WHEN UnitPrice &lt; 10 THEN NULL ELSE CAST(UnitPrice AS numeric(18,2)) \/ 12 END&#8221; will return an expression of type numeric(22,6), along with the fact that the UnitPrice column is a decimal(18,2):<\/p>\n<pre class=\"lang:none theme:none\">EvaluatedExpressionValue\tDataType\tNumericPrecision\t\t\r\n19.166666\t                numeric\t        22\t                \r\n\r\n\u2026 NumericScale  MaxLength\r\n\u2026 6             NULL\r\n\r\n\u2026 StringCollation\tTotalBytesOfStorageInclOverhead\t--\tUnitPrice\tInvoiceId\r\n\u2026 NULL\t                9\t                        --\t230.00\t        1\r\n\r\n\r\nname                            is_nullable\tsystem_type_name\r\nEvaluatedExpressionValue\t1\t        numeric(22,6)\r\nUnitPrice\t                1\t        decimal(18,2)\r\nInvoiceId\t                0\t        int\r\nInvoiceLineID\t                0\t        int\r\n<\/pre>\n<p>Hope you find this useful, or at least find it informative that you can use SQL_VARIANT_PROPERTY and the\u00a0sys.dm_exec_describe_first_result_set objects to get more information about the datatype of expressions you are using in your queries. And if you have any sort of math background, you may be slightly surprised by the datatypes you find in the output.<\/p>\n<p>Also, if you were wondering, the expression N&#8217;Merry Christmas! Happy Holidays!&#8217; is nvarchar(32), and it requires 72 bytes to store. If you were wondering, which you probably weren&#8217;t.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note: post revised to take into consideration tips from comments The other day, I was working with a query where someone had put together an expression that had something along the lines of: CASE WHEN Value = &#8216;X&#8217; THEN NULL ELSE CAST(SomeOtherValue as numeric(18,2) \/ 12.0 END AS AnnualisedSomeOtherValue And I thought to myself&#8230; &#8220;Self,&#8230;&hellip;<\/p>\n","protected":false},"author":56085,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[2],"tags":[68872,68873,4183],"coauthors":[19684],"class_list":["post-81967","post","type-post","status-publish","format-standard","hentry","category-blogs","tag-datatypes","tag-sql_variant","tag-t-sql"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81967","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/56085"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=81967"}],"version-history":[{"count":10,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81967\/revisions"}],"predecessor-version":[{"id":82459,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/81967\/revisions\/82459"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=81967"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=81967"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=81967"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=81967"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}