0

It's a common question how to find the columns in a table by querying the system columns. For example SQL server query to get the list of columns in a table along with Data types, NOT NULL, and PRIMARY KEY constraints gives a query which works in most cases. However, a column of type sysname is returned as plain nvarchar. In Management Studio (SSMS) scripting out a table definition will correctly give the sysname type. And so will sp_columns. But how can I find whether a column is of sysname type using an SQL query against the system tables? (I don't want to run sp_columns separately for each table.)

In case you are wondering what the sysname type is to start with, What is SYSNAME data type in SQL Server? gives some info.

To give more detail:

create view some_table_names as select name from sys.tables

Then running

sp_columns 'some_table_names'

reports type_name=sysname. But a simple query against sys.columns gives just varchar:

select type_name(c.system_type_id)
from sys.objects t
join sys.columns c
  on t.object_id = c.object_id
where t.name = 'some_table_names'

I had a look at the definition of sp_columns to see if I could do the same thing. It looks up the column details in a system table sys.spt_columns_odbc_view. But this is apparently some top secret internal table that can only be queried from a direct administrator connection (DAC) or from SSMS. (See What is spt_columns_odbc_view and why is it not accessible?) The sp_columns proc manages to query this view even though I am not running it from Management Studio or over a DAC. But I don't know how to repeat that trick in my own code.

Is there some other way to tell whether a column is of sysname type?

Ed Avis
  • 1,350
  • 17
  • 36
  • Does this answer your question? [What is SYSNAME data type in SQL Server?](https://stackoverflow.com/questions/5720212/what-is-sysname-data-type-in-sql-server) – Panagiotis Kanavos Jun 03 '20 at 10:02
  • Why are you creating tables with the `sysname` type in the first place? There's no reason to use this type for non-system tables. System tables *already* exist in any new database so they don't need scripting - unless someone performed unsupported modifications on them? – Panagiotis Kanavos Jun 03 '20 at 10:03
  • Thanks for the link. Yes, I read that question first and posted an answer discussing use cases: https://stackoverflow.com/a/62169114/626804 – Ed Avis Jun 03 '20 at 10:08
  • The definition of `syname` isn't secret. It's essentually a UDT for `nvarchar(128)`. Add a join to `sys.types` to your query: `select ty.name, c.max_length AS type_max_length, ty.max_length AS type_max_length from sys.objects t join sys.columns c on t.object_id = c.object_id join sys.types ty ON ty.system_type_id = c.system_type_id where t.name = N'test_table';` – Dan Guzman Jun 03 '20 at 10:08
  • @EdAvis that's not a valid case. You don't need that specific alias for any kind of maintenance script. You only need the actual *name* of the column – Panagiotis Kanavos Jun 03 '20 at 10:10
  • @DanGuzman you are right that in current versions of MSSQL `sysname` is functionally equivalent to `nvarchar(128)`, but this is subject to change (and has changed in the past). If you define a table with `sysname` and script it out using SSMS, it will correctly print a table definition using that type. I would like to view the column type in the same way SSMS queries it, rather than forcing it to `nvarchar`. – Ed Avis Jun 03 '20 at 10:13
  • @EdAvis no it's not. A sys name **is** a `nvarchar`, you aren't forcing anything. `If you define a table with sysname` why do that at all? There's no reason to pick that instead of the *actual* `nvarchar(128)` type – Panagiotis Kanavos Jun 03 '20 at 10:13
  • @EdAvis, add `AND c.user_type_id = t.user_type_id` to the query in my comment to return only `sysname`. – Dan Guzman Jun 03 '20 at 10:14
  • @DanGuzman I think you mean `c.user_type_id = ty.user_type_id`. Thank you. Do you want to post your solution as an official 'answer' so I can then accept it? – Ed Avis Jun 03 '20 at 10:20

2 Answers2

1

First of all, there's no valid case for storing data as sysname, even for maintenance scripts. For a script you want the actual name of a table or column, so storing it in an nvarchar(128) field is perfectly fine.

The system itself treats sysname as a user type in the sys.types table. If you check the record, you'll see that the database itself tells you this is a nvarchar. If you want to return that alias name, join the sys.columns.user_type_id column with types.user_type_id, eg :

select object_name(object_id), 
types.name,
* 
from sys.columns
inner join sys.types on types.user_type_id =sys.columns.user_type_id
where columns.user_type_id=256

or just

select object_name(object_id), 
TYPE_NAME(user_type_id),
* 
from sys.columns
where user_type_id=256

UPDATE

I just checked the type's record on a server where I databases with different collations and noticed that the collation changes to match the database's. So even on the same server, using that alias can lead to collation issues

Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Thanks for your answer and comments. The question of whether you *should* use the sysname type is a bit of a distraction from the underlying question. I don't particularly mind either way but since Microsoft has used it in system tables, if you create views of those tables you will get columns of sysname type by default, and tables created using 'select into' will also get it. For consistency with Microsoft's own views, I have used the same user data type. Perhaps there is nothing to be gained by having it and Microsoft should just use nvarchar(128) everywhere instead, but... – Ed Avis Jun 03 '20 at 10:42
  • @EdAvis on the contrary, it's the most important part. In fact, I just noticed that the UDT's collation matches the *server's*. So *every* installation will have a *different* collation for `sysname`. You gain no consistency, no clarity by using that type. You get a dependency to the server's collation instead – Panagiotis Kanavos Jun 03 '20 at 10:43
  • But I specifically want my "table name" data to match the server's internal collation. I am storing an SQL Server object name in whatever form is native to the server. That wouldn't be appropriate for application data, of course. – Ed Avis Jun 03 '20 at 10:49
  • @EdAvis sorry, it follows the *database* collation. This means that even on the same server, `sysname` will have a different definition. Even on the same server you may get collation mismatch errors. And even the server's collation isn't "native". It's just a different way to sort and match the same data, something that *can* be overriden at the database and column level – Panagiotis Kanavos Jun 03 '20 at 10:50
  • 1
    Anyway, if you insist, just use `TYPE_NAME(user_type_id)` to get `sysname` – Panagiotis Kanavos Jun 03 '20 at 10:53
  • Thank you for the correction. Again, I don't mind much in practice (since I don't have non-ASCII characters in object names, nor do I care much about sort ordering) but if Microsoft have chosen to use the sysname type in the system tables and views, I don't see a reason to deviate from that and pick a different type instead. – Ed Avis Jun 03 '20 at 10:53
  • @EdAvis even with `nvarchar`, different database collations, even `Latin1_General_100_CI_AS` vs `Latin1_General_CI_AS` will raise collation errors. And you *do* care about sorting and ordering - a `WHERE columnName=someOtherName` clause will use the columns' collations and fail if they are different, or result in full table scans. A column collation's sort order affects how indexes are used for equality searches too – Panagiotis Kanavos Jun 03 '20 at 10:57
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215224/discussion-between-ed-avis-and-panagiotis-kanavos). – Ed Avis Jun 03 '20 at 11:00
1

The sys.types catalog view exposes data types that can be specified in DDL. You can join to this view on user_type_id to identify column type. As you can see from this query, sysname is not an internal secret type.

SELECT c.Name AS ColumnName, ty.name AS TypeName, c.max_length AS ColumnLengthBytes
FROM sys.objects t
JOIN sys.columns c ON t.object_id = c.object_id 
JOIN sys.types ty ON c.user_type_id = ty.user_type_id
WHERE t.name = N'test_table';

sysname is similar to a user-defined type. It differs from a UDT created with CREATE TYPE in that the is_user_defined column of sys.types will be zero instead of one since it's defined by SQL Server rather than a user.

One can also join on system_type_id to also return both the user and base system type.

SELECT c.Name AS ColumnName, ty.name AS TypeName, c.max_length AS ColumnLengthBytes
FROM sys.objects t
JOIN sys.columns c ON t.object_id = c.object_id 
JOIN sys.types ty ON ty.system_type_id = c.system_type_id
WHERE t.name = N'test_table';
Dan Guzman
  • 43,250
  • 3
  • 46
  • 71
  • The collation follows the database's collation too, which means using `sysname` to store metadata from multiple databases can easily lead to collation errors – Panagiotis Kanavos Jun 03 '20 at 10:54
  • @PanagiotisKanavos, mixed collation environments are a pain regardless of whether one uses `sysname` or `nvarchar(128)`. There is no single collation one can choose to store meta-data from differing collations while retaining the source comparison and ordering semantics. Microsoft introduced catalog collation `Latin1_General_100_CI_AS_KS_WS_SC` for contained databases to address some of the problem with mixed collations but pity the poor soul with a case-sensitive database collation in non-contained databases. – Dan Guzman Jun 03 '20 at 11:30