SQL Server Find Columns by Data Type Across the Database
"Where is every datetime column?" — "Which tables still use text instead of varchar(max)?" — "Are any columns using a deprecated user-defined type?" These are recurring DBA questions during schema audits, deprecation sweeps, and pre-migration assessments. This T-SQL script joins sys.columns to sys.types, sys.tables, and sys.schemas to produce the canonical answer: every column in the current database with its full type signature.
Purpose and Overview
Which columns survived the move from text to varchar(max)? Which fields still carry the deprecated Money_T UDT three releases after it was officially retired? Did a recent merge silently reintroduce datetime on a column the design review thought it had migrated to datetime2? A single catalog-view query against sys.columns and sys.types answers all three — and every other question of the form "where is every column of type X in this database?" — without an INFORMATION_SCHEMA round trip or a manual table-by-table sweep.
The query reads four catalog views together: sys.columns (one row per column on every table or view), sys.types (one row per system or user-defined data type), sys.tables (one row per user table), and sys.schemas (schema-name resolution). Each result row carries the schema name, table name, column name and ordinal, the data-type name and its home schema, three boolean flags (is_user_defined, is_assembly_type, is_nullable), and the precision/scale/max_length details that distinguish decimal(18,2) from decimal(38,8). Add a WHERE t.name = N'datetime' clause to scope the report to a single type — the canonical answer to "show me every datetime column."
Code Breakdown
The complete script is shown below. Run it in the database whose columns you want to audit.
1SELECT s.name AS [schema],
2 ts.name AS TableName,
3 c.name AS column_name,
4 c.column_id,
5 SCHEMA_NAME(t.schema_id) AS DatatypeSchema,
6 t.name AS Datatypename,
7 t.is_user_defined,
8 t.is_assembly_type,
9 c.is_nullable,
10 c.max_length,
11 c.PRECISION,
12 c.scale
13FROM sys.columns AS c
14INNER JOIN sys.types AS t ON c.user_type_id = t.user_type_id
15INNER JOIN sys.tables ts ON ts.OBJECT_ID = c.OBJECT_ID
16INNER JOIN sys.schemas s ON s.schema_id = ts.schema_id
17ORDER BY s.name, ts.name, c.column_id;
sys.columns — One Row per Column
sys.columns is the per-column catalog view. Each row carries object_id (the parent table or view), column_id (the 1-based ordinal of the column), name, is_nullable, max_length, precision, scale, and two type-id columns: system_type_id and user_type_id. For built-in types the two values are equal; for user-defined types user_type_id carries the UDT identifier while system_type_id carries the underlying built-in type the UDT is based on. The script joins on user_type_id so user-defined types resolve to their own name (not to the underlying base type).
sys.types — Built-in and User-Defined Types
sys.types has one row per system type (int, varchar, nvarchar, datetime, etc.) and one row per user-defined or CLR data type. The boolean columns is_user_defined and is_assembly_type separate the three categories: built-in types have both flags set to 0; classic CREATE TYPE user-defined types have is_user_defined = 1 and is_assembly_type = 0; CLR types loaded from an assembly have both flags equal to 1. The schema_id column lets you resolve the type's home schema — system types live in sys, user-defined types live in whichever schema they were created in (typically dbo).
sys.tables — Filter to User Tables Only
Joining sys.columns to sys.tables (rather than sys.objects) restricts the result set to user tables. Views, table-valued function return types, and other object types that also have columns won't appear. To include views, swap sys.tables for sys.objects and filter on type IN ('U', 'V').
sys.schemas — Schema Name Resolution
sys.schemas provides the name column from a schema_id. Two schema-name columns are reported in the output: the schema of the table (s.name) and the schema of the data type (SCHEMA_NAME(t.schema_id) AS DatatypeSchema). These differ for user-defined types: a user-defined type created in dbo and used by a table in sales will show schema = 'sales' and DatatypeSchema = 'dbo'.
max_length, precision, and scale — Type Discriminators
For most types precision and scale are zero and only max_length matters. For varchar(50) max_length is 50; for nvarchar(50) it is 100 (two bytes per character); for varchar(MAX) and nvarchar(MAX) it is -1. For numeric types precision is the total digits and scale is the digits to the right of the decimal — decimal(18,2) returns precision 18, scale 2. The combination of type name + max_length + precision + scale is what fully identifies "the same type" across columns.
Key Benefits and Use Cases
- Find every column matching a specific type: add
WHERE t.name = N'datetime'(or any other type) to scope the report - Locate deprecated types: filter on
t.name IN ('text', 'ntext', 'image')to find columns using the legacy LOB types that need migration tovarchar(max),nvarchar(max), andvarbinary(max) - Audit user-defined type usage with
WHERE t.is_user_defined = 1— particularly useful when retiring a UDT and needing the list of column-level changes required - Pre-migration assessment when moving a database to a different platform or version with different type support (e.g., migrating to Azure SQL DB where certain types behave differently)
- Schema-name-aware output handles multi-schema databases such as Dynamics, NAV, SharePoint, and any database that uses schemas as a logical boundary
- Identifies columns with very large declared sizes by sorting on
max_length DESCto spot over-provisioned strings
Performance Considerations
- Metadata-only: reads four catalog views with no user-data access. Runs instantly even on databases with hundreds of thousands of columns
- Includes views via swap-in: replace
sys.tableswithsys.viewsto audit view column types instead; the rest of the join structure is identical - Excludes computed columns? No:
sys.columnsincludes computed columns. To exclude them, addWHERE c.is_computed = 0. To audit only computed columns and their expressions, join tosys.computed_columnsand select thedefinitioncolumn - max_length quirk: for
nvarcharcolumns, dividemax_lengthby 2 to get the character count. ForMAXtypesmax_lengthis -1 — handle this explicitly when formatting output - No partition awareness needed: column metadata is partition-independent, so the report represents the logical schema regardless of partitioning
- Includes hidden columns: temporal table period columns and graph-table pseudo-columns appear unless filtered with
c.is_hidden = 0
Practical Tips
- For type-specific filtering, add a WHERE clause after the FROM/JOINs. Examples:
WHERE t.name = N'datetime'— every datetime columnWHERE t.name IN ('text','ntext','image')— every deprecated LOB columnWHERE t.is_user_defined = 1— every UDT columnWHERE t.name = N'nvarchar' AND c.max_length = -1— every nvarchar(MAX) column
- For multi-database scans, wrap the query in
sp_MSforeachdbor build a UNION ALL acrossDatabaseName.sys.columnsreferences — the join shape stays the same - Combine with
sys.foreign_keysto find columns whose data type doesn't match their referenced column's type, a common cause of join performance issues from implicit conversion - Output as CSV for a schema-diff workflow: capture the result against the source database, capture against the target, and use a row-by-row diff to surface drift
- For exact byte counts, join
c.max_lengthand theis_nullableflag withsys.dm_db_index_physical_statsto estimate per-row storage cost — useful when sizing a new table from existing columns - Add
c.collation_nameto the SELECT list when auditing string columns across a multi-collation database
Conclusion
This column-by-data-type query is the canonical T-SQL probe for any data-type-driven schema audit. By joining sys.columns to sys.types, sys.tables, and sys.schemas, it gives the DBA a one-screen view of every column's type signature — built-in or user-defined, system or CLR, with full precision/scale/max_length detail. Pair it with a WHERE filter for type-specific sweeps, with an export step for diffing against a target schema, or with a foreign-key join for type-mismatch detection in your referential integrity boundaries.
References
- SQL Server 2012 — List All The Column With Specific Data Types in Database by Pinal Dave — Original SQL Authority article publishing the script used in this post
- Microsoft Docs: sys.columns — Catalog view reference for every column in every table and view
- Microsoft Docs: sys.types — Catalog view reference covering built-in, user-defined, and assembly types
- Microsoft Docs: sys.tables — Catalog view containing one row per user table in the current database
- Microsoft Docs: INFORMATION_SCHEMA.COLUMNS — Cross-platform alternative view that is portable across DBMSes but exposes less SQL-Server-specific detail than the catalog views