SQL Server Query Metadata
Pop quiz, how you determine the metadata of a query in SQL Server? For a table, you can query the sys.schemas/sys.tables/sys.columns tables but a query? You might start pulling the query apart and looking up each column and its metadata but then you have to factor in function calls and suddenly, you're writing a parser within your query and you have an infinite recursion error.
But, if you're on SQL Server 2012+, you have a friend in sys.dm_exec_describe_first_result_set.
Let's start with a random query from Glen Berry's diagnostic query set
-- Drive level latency information (Query 28) (Drive Level Latency) -- Based on code from Jimmy May SELECT tab.[Drive], tab.volume_mount_point AS [Volume Mount Point], CASE WHEN num_of_reads = 0 THEN 0 ELSE (io_stall_read_ms/num_of_reads) END AS [Read Latency], CASE WHEN num_of_writes = 0 THEN 0 ELSE (io_stall_write_ms/num_of_writes) END AS [Write Latency], CASE WHEN (num_of_reads = 0 AND num_of_writes = 0) THEN 0 ELSE (io_stall/(num_of_reads + num_of_writes)) END AS [Overall Latency], CASE WHEN num_of_reads = 0 THEN 0 ELSE (num_of_bytes_read/num_of_reads) END AS [Avg Bytes/Read], CASE WHEN num_of_writes = 0 THEN 0 ELSE (num_of_bytes_written/num_of_writes) END AS [Avg Bytes/Write], CASE WHEN (num_of_reads = 0 AND num_of_writes = 0) THEN 0 ELSE ((num_of_bytes_read + num_of_bytes_written)/(num_of_reads + num_of_writes)) END AS [Avg Bytes/Transfer] FROM (SELECT LEFT(UPPER(mf.physical_name), 2) AS Drive, SUM(num_of_reads) AS num_of_reads, SUM(io_stall_read_ms) AS io_stall_read_ms, SUM(num_of_writes) AS num_of_writes, SUM(io_stall_write_ms) AS io_stall_write_ms, SUM(num_of_bytes_read) AS num_of_bytes_read, SUM(num_of_bytes_written) AS num_of_bytes_written, SUM(io_stall) AS io_stall, vs.volume_mount_point FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs INNER JOIN sys.master_files AS mf WITH (NOLOCK) ON vfs.database_id = mf.database_id AND vfs.file_id = mf.file_id CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.[file_id]) AS vs GROUP BY LEFT(UPPER(mf.physical_name), 2), vs.volume_mount_point) AS tab ORDER BY [Overall Latency] OPTION (RECOMPILE);
Drive | Volume Mount Point | Read Latency | Write Latency | Overall Latency | Avg Bytes/Read | Avg Bytes/Write | Avg Bytes/Transfer |
---|---|---|---|---|---|---|---|
C: | C:\ | 0 | 0 | 0 | 64447 | 4493 | 31990 |
The results of the query aren't exciting, but what are the columns and expected data types? Pre-2012, most people dump the query results into a table with an impossible filter like WHERE 1=2 and then query the above system tables.
With the power of SQL Server 2012+, let's see what we can do. I'm going to pass in as the first argument our query and specify NULL for the next two parameters.
SELECT DEDFRS.column_ordinal , DEDFRS.name , DEDFRS.is_nullable , DEDFRS.system_type_name , DEDFRS.max_length , DEDFRS.precision , DEDFRS.scale FROM sys.dm_exec_describe_first_result_set(N' SELECT tab.[Drive], tab.volume_mount_point AS [Volume Mount Point], CASE WHEN num_of_reads = 0 THEN 0 ELSE (io_stall_read_ms/num_of_reads) END AS [Read Latency], CASE WHEN num_of_writes = 0 THEN 0 ELSE (io_stall_write_ms/num_of_writes) END AS [Write Latency], CASE WHEN (num_of_reads = 0 AND num_of_writes = 0) THEN 0 ELSE (io_stall/(num_of_reads + num_of_writes)) END AS [Overall Latency], CASE WHEN num_of_reads = 0 THEN 0 ELSE (num_of_bytes_read/num_of_reads) END AS [Avg Bytes/Read], CASE WHEN num_of_writes = 0 THEN 0 ELSE (num_of_bytes_written/num_of_writes) END AS [Avg Bytes/Write], CASE WHEN (num_of_reads = 0 AND num_of_writes = 0) THEN 0 ELSE ((num_of_bytes_read + num_of_bytes_written)/(num_of_reads + num_of_writes)) END AS [Avg Bytes/Transfer] FROM (SELECT LEFT(UPPER(mf.physical_name), 2) AS Drive, SUM(num_of_reads) AS num_of_reads, SUM(io_stall_read_ms) AS io_stall_read_ms, SUM(num_of_writes) AS num_of_writes, SUM(io_stall_write_ms) AS io_stall_write_ms, SUM(num_of_bytes_read) AS num_of_bytes_read, SUM(num_of_bytes_written) AS num_of_bytes_written, SUM(io_stall) AS io_stall, vs.volume_mount_point FROM sys.dm_io_virtual_file_stats(NULL, NULL) AS vfs INNER JOIN sys.master_files AS mf WITH (NOLOCK) ON vfs.database_id = mf.database_id AND vfs.file_id = mf.file_id CROSS APPLY sys.dm_os_volume_stats(mf.database_id, mf.[file_id]) AS vs GROUP BY LEFT(UPPER(mf.physical_name), 2), vs.volume_mount_point) AS tab ORDER BY [Overall Latency] OPTION (RECOMPILE);', NULL, NULL) AS DEDFRS;
Look at our results. Now you can see the column names from our query, their basic type and whether they're nullable. That's pretty freaking handy.
column_ordinal | name | is_nullable | system_type_name | max_length | precision | scale |
---|---|---|---|---|---|---|
1 | Drive | 1 | nvarchar(2) | 4 | 0 | 0 |
2 | Volume Mount Point | 1 | nvarchar(256) | 512 | 0 | 0 |
3 | Read Latency | 1 | bigint | 8 | 19 | 0 |
4 | Write Latency | 1 | bigint | 8 | 19 | 0 |
5 | Overall Latency | 1 | bigint | 8 | 19 | 0 |
6 | Avg Bytes/Read | 1 | bigint | 8 | 19 | 0 |
7 | Avg Bytes/Write | 1 | bigint | 8 | 19 | 0 |
8 | Avg Bytes/Transfer | 1 | bigint | 8 | 19 | 0 |
I'm thinking that I can use this technique against an arbitrary source of queries to build out the result tables and then ETL data into them. That should simplify my staging step for table loads. What can you use this for? Add links in the comments to how you use sys.dm_exec_describe_first_result_set
1 comment:
It's a cracking DMV and really useful. I recently blogged a little script that uses it to return a table definition based on a query's result set that you might like, https://sqlundercover.com/2017/08/21/undercover-toolbox-generate-a-temporary-table-definition-to-match-the-resultset-of-a-query/
Post a Comment