Get column stats in SQL Server 2008 - sql-server

I am trying to determine two things in a SQL Server 2008 database.
First, I need to know the columns that have null values in them.
Second, I need to know the count of nulls per column in the actual tables.
I know I can get the first one by doing:
SELECT t.name, c.name
FROM sys.tables t
INNER JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.is_nullable = 1
I am struggling to find the second part though.

Say you have a table atable with columns not_nullable and is_nullable. As count does not count anything - you could instead count only where the columns is null
SELECT count(*) count_nulls FROM atable WHERE is_nullable is null;
Now how to do that for all nullable columns?
I came up with this - but its veeeery slow. But then again - how often do you need to do that?
create table #nullcolumns(nullable_column varchar(255), count_nulls int);
declare #sqladd nvarchar(1000);
DECLARE users_cursor CURSOR FOR
SELECT /* TOP 20 */ 'INSERT INTO #nullcolumns SELECT '''+
t.name+'.'+c.name+''' as nullable_column, count(*) from '
+t.name+' WHERE '+c.name+' IS NULL '
FROM sys.tables t
INNER JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.is_nullable = 1
OPEN users_cursor
FETCH NEXT FROM users_cursor
INTO #sqladd
WHILE ##FETCH_STATUS = 0
BEGIN
exec sp_executesql #sqladd;
print #sqladd;
FETCH NEXT FROM users_cursor
INTO #sqladd
END
CLOSE users_cursor
DEALLOCATE users_cursor
SELECT * FROM #nullcolumns;

Related

Need to change GUID to varchar or char

I am trying to use a reference table to update a code, but I am getting the error:
Conversion failed when converting from a character string to
uniqueidentifier
The ID is a guid: 086B9FE7-3980-47D7-BB05-003708F1D564 and the reference code I want to use is 4 characters, like H100.
I received the initial error when I tried to alter the datatype in the table and then update the file with the values from the reference table. I have tried converting and casting based on other articles, but even if I cast or convert successfully, I still get the same message.
RefTable:
Id | ReportCode
6340FCEA-161C-42F4-8D7F-46B4C2E6C4E2 | H100
DataTable:
CauseId
6340FCEA-161C-42F4-8D7F-46B4C2E6C4E2
Code I am using to try and update. The first works, and the second bring the error msg:
select cast(nvarchar(36), ID) as ID
from [dbo].[reftable]
UPDATE dbo.datatable
SET causeid = L.reportcode
FROM dbo.datatable S
join dbo.reftable L on S.causeid = L.id
Uniqueidentifier is a particular column type that holds 36 bytes, with some hyphen characters in the middle. Asigning a string value that doesn't match it's format will always fail when trying to convert it.
-- Conversion failed when converting from a character string to uniqueidentifier.
SELECT CONVERT(UNIQUEIDENTIFIER, 'H100')
-- OK
SELECT CONVERT(UNIQUEIDENTIFIER, 'c029f8be-29dc-41c1-8b38-737b4cc5a4df')
If you want to use a common VARCHAR as your new causeid value, you need to first convert the column type to VARCHAR (or NVARCHAR).
ALTER TABLE dbo.datatable ALTER COLUMN causeid VARCHAR(200) -- NOT NULL if you need
If you can't alter the data type it's probably because there is an INDEX or a CONSTRAINT linked to it. When you try to alter, the SQL engine will tell you which object is linked to it. You will have to drop them, alter the column type and then create them again.
I use these queries to check indexes and constraints. I updated the search values for your table.
DECLARE #table_name VARCHAR(200) = 'datatable'
DECLARE #column_name VARCHAR(200) = 'causeid'
-- Indexes
SELECT
SchemaName = SCHEMA_NAME(t.schema_id),
TableName = t.name,
IndexName = ind.name,
IndexType = CASE ind.index_id WHEN 0 THEN 'Heap' WHEN 1 THEN 'Clustered' ELSE 'Nonclustered' END,
Disabled = ind.is_disabled,
ColumnOrder = ic.index_column_id,
ColumnName = col.name,
ColumnType = y.name,
ColumnLength = y.max_length,
ColumnIncluded = ic.is_included_column
FROM
sys.indexes ind
INNER JOIN sys.index_columns ic ON ind.object_id = ic.object_id and ind.index_id = ic.index_id
INNER JOIN sys.columns col ON ic.object_id = col.object_id and ic.column_id = col.column_id
INNER JOIN sys.tables t ON ind.object_id = t.object_id
INNER JOIN sys.types y ON y.user_type_id = col.user_type_id
WHERE
t.is_ms_shipped = 0 AND
t.name = #table_name AND
col.name = #column_name
ORDER BY
SchemaName,
t.name,
ind.name,
ic.index_column_id
-- Constraints
SELECT
TableName = t.Name,
ColumnName = c.Name,
dc.Name,
dc.definition
FROM
sys.tables t
INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id
INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id
WHERE
t.name = #table_name AND
c.name = #column_name
ORDER BY
t.Name

What is the T-SQL syntax to get the length of each column of a specific Table?

My database is hosted on Microsoft SQL Server 2012 and I need to write a T-SQL query to pull the length of each column of a specific table.
Assuming my database is called mydatabase and the table is called table1 with 3 columns (namely col1, col2 and col3), how do I write my sql query to get that information?
Ideally, I want the output to be something like this:
ColumnName Length
col1 50
col2 30
col3 25
Additional info: I will need to run this query on several other tables where I don't know the number or names of the columns therein. So the query should output the names of the columns with their respective column length.
I assume by length you mean, for example, that if it is a varchar(50) it has a length of 50. If it's a decimal(18,2) then you want to know Scale 18, Precision 2. This should help:
SELECT c.[name] AS ColumnName, st.[name] AS DataType,
CASE WHEN st.[name] IN ('varchar','nvarchar') THEN c.max_length END AS Max_Length,
CASE WHEN st.[name] NOT IN ('varchar','nvarchar') THEN c.scale END AS Scale,
CASE WHEN st.[name] NOT IN ('varchar','nvarchar') THEN c.[precision] END AS [Precision]
FROM sys.tables t
JOIN sys.columns c ON t.object_id = c.object_id
JOIN sys.systypes st ON c.system_type_id = st.xtype
WHERE t.[name] = 'YourTableName';
If you are looking for a Query that will return the Maximum permitted length of a column, Then you can View it from the INFORMATION_SCHEMA.COLUMN view
SELECT
ORDINAL_POSITION,
COLLATION_NAME,
CHARACTER_MAXIMUM_LENGTH
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'YourTableName'
Or if you are looking for the Maximum Lenght of the Data Stored is Each Column
Use MAX() and LEN() function
SELECT MAX(LEN(Col1)) FROM YourTable
You can use COL_LENGTH to get this (more information can be found here)
You could write something like the following:
Select COL_LENGTH ( 'table1' , 'Col1' )
Select COL_LENGTH ( 'table1' , 'Col2' )
Select COL_LENGTH ( 'table1' , 'Col3' )
EDIT:
With the extra information provided I think the below is what you are looking for:
SELECT
t.name AS 'Table_Name'
,c.name AS 'Column_Name'
,I.CHARACTER_MAXIMUM_LENGTH
,I.DATA_TYPE
FROM sys.columns c
JOIN sys.tables t ON c.object_id = t.object_id
JOIN INFORMATION_SCHEMA.COLUMNS I on I.COLUMN_NAME = c.name
You will probably have to add a where clause in as this is currently looking for everything on a database.
By joining the sys.columns and sys.tables with information_schema.columns
You can find the length of columns/tables with out needing to know the name.

Looking for everywhere a particular value has been used in foreign keys in one pass

Good afternoon.
I am using SQL Server 2008/TSQL. It is probably important to note I do not have write access to any db (I am a read only user). I do not have write access to the db but can insert temp tables if absolutely needed.
I would like to preface what I am about to say with letting everyone know I have no formal education in SQL. Hopefully this makes sense - I may be inaccurate in some vocabulary etc.
Scope of what I am trying to do:
1. Select a specific recordID (value) in a column (primary key) from a table
2. Find where that specific number/recordID is used in all dependents/foreign keys
3. Return the tablename and columnname with a count of how many times that value was found
So, as an example...
You have a table with information on a person tied to a recordID, say something like:
dbo.MemberInfo with RecordID, Name Etc.
The ID number of the member (MemberInfo.RecordID) is used in other
tables, say: dbo.Awards as [HonoreeID]
(dbo.Awards.HonoreeID=MemberInfo.RecordID) dbo.Address as [MemberID]
(dbo.Address.MemberID=MemberInfo.RecordID) dbo.Contact as [PersonID]
(dbo.Contact.PersonID=MemberInfo.RecordID) ...and potentially a few
hundred others
I basically want to run through all the tables and see how many times a particular value/record is in use. Now, to add some complexity to this, it needs to be generic, as the column I may be looking up dependents on may change from day to day. (Ex. I may be looking for dependents of EventID tomorrow)
My current process is:
-Use a select to find the ID of the person I need
-Look at all the foreign keys linked to RecordID (Primary Key) of dbo.Members
-Dump the tablenames and columns of the foreign keys out into Excel
-Do a find and replace to make a bunch of SELECT COUNTS with a WHERE=#Variable
-Put it into SQL, define my variable and set it equal to the initial ID number
There has to be a better way. I have attempted many variations of the following with lots of errors and no success:
--DECLARE #Selected CHAR
SELECT T.Name, C.Name
--SET #Selected=(SELECT T.Name FROM sys.tables T)
--CASE WHEN (T.NAME IS NOT NULL) THEN 1
--ELSE '0' END AS 'MyTrial'
FROM
--sys.tables t
sys.foreign_key_columns AS fk
INNER JOIN sys.tables AS t ON fk.parent_object_id = t.object_id
INNER JOIN sys.columns AS c ON fk.parent_object_id = c.object_id AND fk.parent_column_id = c.column_id
WHERE Referenced_Object_ID=--Insert object ID here
My line of thinking was/is:
1. Query foreign keys used in a table/column, return the table name and column name from the dependent tables
2. Feed these results into something that can build me a new query to return a count of a value in each of the tables/columns where applicable
3. Return the tablename as well, so it can easily be fed into another select statement should I need to look at the details making up the count.
So my results might look something like this:
Tablename, Columnname, Count of Value in Column
Ideally, no value, table name etc. would be returned if the count is less than one.
My process may be extremely flawed out of the gate, but anything offered helps me learn. Thanks!
The script below uses no stored procedures but does use a temporary table. As far as I can conceive, there's no way around it. It runs very quickly for me, although I was working with a relatively small test database.
This is my first venture into dynamic SQL, so I can't testify to the safety of this code against SQL injection attacks and such. I, too, am self-taught.
Declare #Primary_Table varchar(100) = '';
Declare #Column_Name varchar(100) = '';
Declare #Specific_Value int = ;
IF(OBJECT_ID('tempdb..#Selected_Tables') is not null)
Begin
Drop Table #Selected_Tables;
End
Select Distinct T.Name as Table_Name
, C.Name as Column_Name
, CAST(null as bigint) as Referenced_Records
Into #Selected_Tables
FROM sys.foreign_key_columns AS fk
INNER JOIN sys.tables AS t
ON fk.parent_object_id = t.object_id
INNER JOIN sys.columns AS c
ON fk.parent_object_id = c.object_id
AND fk.parent_column_id = c.column_id
Inner Join sys.tables AS t2
ON fk.referenced_object_id = t2.object_id
Inner Join sys.columns AS c2
on fk.referenced_column_id = c2.column_id
Where t2.name = #Primary_Table
and c2.Name = #Column_Name;
Declare #sqlCommand nvarchar(max);
Declare #Unprocessed_Records int = (Select Count(1)
From #Selected_Tables
Where Referenced_Records is null);
Declare #Processing_Table varchar(1000) = (Select Top 1 Table_Name
From #Selected_Tables
Where Referenced_Records is null);
Declare #Processing_Column varchar(1000) = (Select Top 1 Column_Name
From #Selected_Tables
Where Referenced_Records is null
and Table_Name = #Processing_Table);
While #Unprocessed_Records > 0
Begin
Set #sqlCommand = 'Update #Selected_Tables '
+ 'Set Referenced_Records = (Select Count(1) '
+ 'From ' + #Processing_Table + ' '
+ 'Where ' + #Processing_Column + ' = ' + CAST(#Specific_Value as nvarchar(1000)) + ') '
+ 'Where Table_Name = ''' + #Processing_Table + ''' '
+ 'and Column_Name = ''' + #Processing_Column + ''';'
Exec (#sqlCommand);
Set #Unprocessed_Records = (Select Count(1)
From #Selected_Tables
Where Referenced_Records is null);
Set #Processing_Table = (Select Top 1 Table_Name
From #Selected_Tables
Where Referenced_Records is null);
Set #Processing_Column = (Select Top 1 Column_Name
From #Selected_Tables
Where Referenced_Records is null
and Table_Name = #Processing_Table);
End;
Select * From #Selected_Tables;

How to get the name of the Parent Table with the help of Column name in Sys.Columns

I wanted to get the table name. I have the column name and when I try to look up at the Sys.Columns table I get the matching name of the column. How will I get the table name to which the required column is associated
SELECT OBJECT_SCHEMA_NAME(object_id) AS TableSchemaName,
OBJECT_NAME(object_id) AS TableName
FROM sys.columns
WHERE name = 'YourColumnName'
I hope this helps:
select t.name from sys.columns c
inner join sys.tables t
on c.object_id = t.object_id
where c.name = 'insert column name here'
select OBJECT_NAME(object_id) as TableName from sys.Columns where name='columnNamehere'
Try this
declare #columnName As varchar(50) = 'ParentColumnName'
select t.name from sys.tables t
join sys.columns c
on c.object_id = t.object_id
and c.name = #columnName
select name as 'TableName' from sys.tables where object_id=
(select object_id from sys.columns where name='UserName')
I know that this is an old question, but the answers listed to date do not get at what the parent table name is for a view's columns, nor if a column is aliased to have a new name with respect to the column name in the parent table.
Unfortunately, (at least in 2008R2) it seems that even with registering your Views to a Schema, the referencing_minor_id of sys.dm_sql_referenced_entities (or the equivalent column from sys.SQL_Modules) is always set to zero. However, you can retrieve all referred-to tables (and parent views), along with which fields of those tables are queried with sys.dm_sql_referenced_entities (or sys.SQL_Modules). However, it does not capture the oorder of those bindings, so the following won't quite work to link view columns directly to table columns, but it'll provide an approximation:
DECLARE #obj_name nvarchar(50) = 'Table_or_View_name_here';
with obj_id as (
select object_id, name, OBJECT_SCHEMA_NAME(object_id)+'.'+name as qualified_name from sys.all_objects as o
where o.name = #obj_name
),
tv_columns as ( -- table or view
select o.name as Obj_Name, c.* from sys.columns as c join
obj_id as o on
c.object_id=o.object_id
),
sql_referenced_entities as (
SELECT
o.name as referencing_name,
o.object_id,
COALESCE(NULLIF(referencing_minor_id,0),ROW_NUMBER() OVER (ORDER BY (select 'NO_SORT'))) as referencing_minor_id,
referenced_server_name,
referenced_database_name,
referenced_schema_name,
referenced_entity_name,
referenced_minor_name,
referenced_id,
referenced_minor_id,
referenced_class,
referenced_class_desc,
is_caller_dependent,
is_ambiguous
FROM obj_id as o, sys.dm_sql_referenced_entities((select qualified_name from obj_id), 'OBJECT') where referenced_minor_id<>0
)
select
c.object_id as object_id,
o.name as object_name,
c.column_id,
c.name as column_name,
c2.object_id as parent_table_object_id,
o2.name as parent_table_name,
c2.column_id as parent_column_id,
c2.name as parent_column_name
-- ,c.*,
-- ,c2.*
from sys.columns as c join
obj_id as o on
c.object_id=o.object_id left outer join
(sql_referenced_entities as s join
sys.all_objects as o2 on
s.referenced_id=o2.object_id and s.referenced_class=1 join
sys.columns as c2 on
s.referenced_id=c2.object_id and s.referenced_minor_id=c2.column_id
) on
c.object_id=s.object_id and c.column_id=s.referencing_minor_id
To get the true aliases used, as well as any calculations involving the combinations of multiple fields, you would have to parse the output of either OBJECT_DEFINITION(OBJECT_ID('schema.view')) (or potentially exec sp_helptext 'schema.view'), as follows
Starting with OBJECT_DEFINITION(OBJECT_ID('schema.view'))
Mark what is enclosed in single quotes as superseeding the removal and other rules to follow
Remove blocks between /* */ comments
Remove any text after --, up to the next linebreak sequence (see EXEC SP_HELPTEXT 'sp_helptext' for their end-of-line code)
Look up table/subselect aliases from the FROM clause
Parse out the SELECT clause and break on commas, rather than end-of-line.
Reduce any contiguous whitespace to a single space character. Force whitespace before [ and after ] when a dot/period (or whitespace) don't already appear.
We'll put the above into a stored procedure that we'll call usp_helptext_for_view
See which table_alias.field_name are aliased or simply appear as field_name. See the below code snippet to see how to seperate the field alias from the definition.
Link the view to the table as appropriate.
drop table #s
create table #s (id bigint identity(1,1) primary key, text nvarchar(max))
insert into #s (text) exec usp_helptext_for_view #qualified_viewname
with s as (select
id,
text,
az=(select
MIN(x*(case x when 0 then null else 1 end)) FROM (VALUES
(charindex(#viewfieldname,text COLLATE Latin1_General_CI_AS)),
(charindex('['+#viewfieldname+']',text COLLATE Latin1_General_CI_AS)),
(charindex('AS ['+#viewfieldname+']',text COLLATE Latin1_General_CI_AS)),
(charindex('as '+#viewfieldname,text COLLATE Latin1_General_CI_AS))
) AS value(x)
),
NULLIF(charindex('=',text),0)) as eq --oh, the irony of how the two different styles are applied
FROM #s
)
SELECT
#viewfieldname as ViewField,
CASE eq WHEN NULL
THEN IIF(az IS NULL, NULL, LEFT(text, az-2))
ELSE RIGHT(text,LENGTH(text)-eq) -- alternately ELSE CASE az WHEN NULL THEN NULL WHEN <eq THEN RIGHT(text,LENGTH(text)-eq) ELSE NULL END
END as ViewFieldDefinition,
id as sortPosition
FROM s
WHERE text like '%'+#viewfieldname+'%' -- you should be able to eliminate this clause without affecting the results.
ORDER BY id, #viewfieldname

Retrieve column descriptions from SQL Server-linked table in MS Access

I am linking to tables in SQL Server from an MS Access front-end. There are column descriptions for some of the tables in SQL Server that I would like to bring forward when I create the linked tables in Access. Is there a way to get at the column descriptions programmatically?
(I know how to append the description to the linked tables, I just need help getting at the descriptions in the back end.)
Try something like:
DECLARE #TableName varchar(100)
SELECT #TableName = 'yourtablename'
-- This will determine if we're using version 9 (2005) of SQL Server, and execute code accordingly
IF CAST(REPLACE(SUBSTRING(CAST(SERVERPROPERTY('productversion') as varchar),1,2), '.','') as int) >= 9
BEGIN
-- This is a SQL 2005 machine
SELECT
[Table Name] = OBJECT_NAME(c.object_id),
[Column Name] = c.name,
[Description] = ex.value
FROM
sys.columns c
LEFT OUTER JOIN
sys.extended_properties ex
ON
ex.major_id = c.object_id
AND ex.minor_id = c.column_id
AND ex.name = 'MS_Description'
WHERE
OBJECTPROPERTY(c.object_id, 'IsMsShipped')=0
AND OBJECT_NAME(c.object_id) = #TableName
ORDER
BY OBJECT_NAME(c.object_id), c.column_id
END
ELSE
BEGIN
-- assume this is a SQL 2000
SELECT
[Table Name] = i_s.TABLE_NAME,
[Column Name] = i_s.COLUMN_NAME,
[Description] = s.value
FROM
INFORMATION_SCHEMA.COLUMNS i_s
LEFT OUTER JOIN
sysproperties s
ON
s.id = OBJECT_ID(i_s.TABLE_SCHEMA+'.'+i_s.TABLE_NAME)
AND s.smallid = i_s.ORDINAL_POSITION
AND s.name = 'MS_Description'
WHERE
OBJECTPROPERTY(OBJECT_ID(i_s.TABLE_SCHEMA+'.'+i_s.TABLE_NAME), 'IsMsShipped')=0
AND i_s.TABLE_NAME = #TableName
ORDER BY
i_s.TABLE_NAME, i_s.ORDINAL_POSITION
END

Resources