Getting Multipart Identifier could not be bound using sp_MSforeachtable - sql

I'm using the sp_MSForeachtable to retrieve the columnames of all tables and concatenating the columnames in a single string. I'm using the following query. I've executed the same providing the parameter through a variable for a single table and works perfectly, but when executed from the SP it fails with the error 'The multi-part identifier "dbo.TableNm" could not be bound.'
DECLARE #query nvarchar(max)
SELECT #query =
'DECLARE #Names VARCHAR(255)
DECLARE #DB VARCHAR(255)
SELECT #Names = COALESCE(#Names + '', '', '''') + COLUMN_NAME FROM Information_Schema.COLUMNS
WHERE TABLE_NAME = ?
SELECT TOP 1 #DB = TABLE_CATALOG FROM Information_Schema.COLUMNS
WHERE TABLE_NAME = ?
SELECT #DB AS [DataBase], ? AS [Table], #Names AS [Columns]'
EXEC sp_MSforeachtable #query
I thought the error might be associated with having multiple tables with the same name in different databases so I tried pre-fixing the database but i still get the same error.
DECLARE #query nvarchar(max)
SELECT #query =
'DECLARE #Names VARCHAR(255)
DECLARE #DB VARCHAR(255)
DECLARE #TableNm VARCHAR(255) = ?
SET #DB = ''People_Directory''
SELECT #Names = COALESCE(#Names + '', '', '''') + COLUMN_NAME FROM Information_Schema.COLUMNS
WHERE TABLE_NAME = #TableNm
AND TABLE_CATALOG = #DB
SELECT #DB AS [DataBase], #TableNm AS [Table], #Names AS [Columns]'
EXEC sp_MSforeachtable #query
I'll keep trying bu I'm running out of ideas. Any thoughts?

The ? in the query will be replaced with the quoted schema-qualified name of the table. It will not be enclosed in quotes, so your query is equivalent to:
SELECT ...
WHERE TABLE_NAME = [dbo].[YourTable]
...
SELECT #DB As [DataBase], [dbo].[YourTable] As [Table], #Names As [Columns]
This will obviously generate an error.
You'll need to add the quotes around the ? so that it's treated as a string. However, your query still won't work, as the TABLE_NAME column doesn't include the schema name, and isn't quoted.
To make the query work as expected, you'll need to combine the TABLE_SCHEMA and TABLE_NAME columns, and make sure the values are quoted, before comparing to the current table name:
DECLARE #query nvarchar(max)
SELECT #query =
'DECLARE #Names VARCHAR(255)
DECLARE #DB VARCHAR(255)
SELECT #Names = COALESCE(#Names + '', '', '''') + COLUMN_NAME FROM Information_Schema.COLUMNS
WHERE QUOTENAME(TABLE_SCHEMA) + ''.'' + QUOTENAME(TABLE_NAME) = ''?''
SELECT TOP 1 #DB = TABLE_CATALOG FROM Information_Schema.COLUMNS
WHERE QUOTENAME(TABLE_SCHEMA) + ''.'' + QUOTENAME(TABLE_NAME) = ''?''
SELECT #DB AS [DataBase], ''?'' AS [Table], #Names AS [Columns]'
EXEC sp_MSforeachtable #query
EDIT:
You don't actually need to use sp_MSforeachtable to do this. Using one of the methods from this article, you can retrieve this information in a single query:
SELECT
T.TABLE_CATALOG As [DataBase],
QUOTENAME(T.TABLE_SCHEMA) + '.' + QUOTENAME(T.TABLE_NAME) As [Table],
STUFF(
(
SELECT ', ' + COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS As C
WHERE C.TABLE_CATALOG = T.TABLE_CATALOG
And C.TABLE_SCHEMA = T.TABLE_SCHEMA
And C.TABLE_NAME = T.TABLE_NAME
ORDER BY C.ORDINAL_POSITION
FOR XML PATH(''), TYPE
).value('.', 'varchar(max)')
, 1, 1, '') As [Columns]
FROM
INFORMATION_SCHEMA.TABLES As T
;

Related

Dynamic SQL to get rows from information_schema

Let’s say I’m looking for a specific column in my database so I have something like this
SELECT COLUMN_NAME, TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE COLUMN_NAME like ‘%employeeid%’
But I also want to know how many rows each table has, I was told I can do this using Dynamic SQL so I have this now
DECLARE
#tableName NVARCHAR(MAX),
#sql NVARCHAR(MAX),
#colName NVARCHAR(MAX);
DECLARE CUR_TABLE CURSOR FOR
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.COLUMNS
OPEN CUR_TABLE
FETCH NEXT FROM CUR_TABLE
INTO #tableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #colName = '%employeeid%'
SET #sql = 'SELECT COLUMN_NAME, TABLE_NAME, (SELECT COUNT(*) FROM ' + #tableName +') AS ROWS FROM INFORMATION_SCHEMA.COLUMNS where column_name like ' + ''' + #colName + ''';
FETCH NEXT FROM CUR_TABLE
INTO #tableName
END;
CLOSE CUR_TABLE
DEALLOCATE CUR_TABLE
EXEC sp_executesql #sql
But this doesn't work, What I'm trying to do is query a table with the column I am looking for, with the table name, and number of rows in the table.
How can I fix this?
You can make use of SQL Server's dynamic management views to quickly obtain the row counts*.
Find all tables with a column named 'MyColumn' and their current rows:
select Schema_Name(t.schema_id) schemaName, t.name TableName, s.row_count
from sys.columns c
join sys.tables t on t.object_id = c.object_id
join sys.dm_db_partition_stats s on s.object_id = c.object_id and s.index_id <= 1
where c.name='MyColumn';
* Accurate except for frequently updated tables where there could be some lag
The following uses INFORMATION_SCHEMA, dynamic SQL, and STRING_AGG() to build a query that will return a single result set.
DECLARE #ColumnName sysname = 'ProductID'
DECLARE #Newline VARCHAR(2) = CHAR(13) + CHAR(10)
DECLARE #SqlTemplate NVARCHAR(MAX) =
+ 'SELECT'
+ ' ColumnName = <ColumnNameString>,'
+ ' TableName = <TableSchemaAndNameString>,'
+ ' Rows = (SELECT COUNT(*) FROM <TableSchemaAndName>)'
+ #Newline
DECLARE #UnionSql NVARCHAR(100) = 'UNION ALL ' + #Newline
DECLARE #Sql NVARCHAR(MAX) = (
SELECT STRING_AGG(
REPLACE(REPLACE(REPLACE(
#SqlTemplate
, '<ColumnNameString>', QUOTENAME(C.COLUMN_NAME, ''''))
, '<TableSchemaAndNameString>', QUOTENAME(C.TABLE_SCHEMA + '.' + C.TABLE_NAME, ''''))
, '<TableSchemaAndName>', QUOTENAME(C.TABLE_SCHEMA) + '.' + QUOTENAME(C.TABLE_NAME))
, #UnionSql)
WITHIN GROUP(ORDER BY C.TABLE_SCHEMA, C.TABLE_NAME)
FROM INFORMATION_SCHEMA.TABLES T
JOIN INFORMATION_SCHEMA.COLUMNS C
ON C.TABLE_SCHEMA = T.TABLE_SCHEMA
AND C.TABLE_NAME = T.TABLE_NAME
WHERE T.TABLE_TYPE = 'BASE TABLE' -- Omit views
AND C.COLUMN_NAME = #ColumnName
)
SET #Sql = #Sql + 'ORDER BY Rows DESC, TableName' + #Newline
--PRINT #Sql
EXEC (#Sql)
I generalized it a bit by adding TABLE_SCHEMA so that it could be used with the AdventureWorks database. See this db<>fiddle for a working demo. Also included is equivalent logic that uses FOR XML instead of STRING_AGG for older SQL Server versions.
Assuming that you are using SQL Server, here is a shorthand way using sp_msforeachtable.
DECLARE #ColumnName NVARCHAR(200) = 'ContactID'
CREATE TABLE #T
(
ColumnName NVARCHAR(200),
TableName NVARCHAR(200),
RecordCount INT
)
INSERT INTO #T (ColumnName, TableName)
SELECT
ColumnName = C.COLUMN_NAME,
TableName = '['+C.TABLE_SCHEMA+'].['+C.TABLE_NAME+']'
FROM
INFORMATION_SCHEMA.COLUMNS C
WHERE
C.COLUMN_NAME LIKE '%' + #ColumnName + '%'
EXEC SP_MSFOREACHTABLE 'IF EXISTS(SELECT * FROM #T WHERE TableName = ''?'') UPDATE #T SET RecordCount = (SELECT COUNT(*) FROM ? ) WHERE TableName = ''?'''
SELECT
ColumnName,TableName,
TableType = CASE
WHEN RecordCount IS NULL
THEN 'View'
ELSE 'Table'
END,
RecordCount
FROM
#T
ORDER BY
CASE WHEN RecordCount IS NULL THEN 'View' ELSE 'Table' END
DROP TABLE #T

query by partial column name

I have a
Checklist table and
there is 27 columns named "check1", "check2"..."check27".I would like to get all this values doing a query something like:
SELECT "check*" FROM Checklist;
Is this possible?
Which database? postgres, sqlite, mysql?
If select * is not an option, the most flexible approach is creating a dynamic query. You will first need to get the column names and then build your query:
DECLARE #tableName as varchar(100);
SET #tableName = 'Checklist';
DECLARE #columnList varchar(300);
SELECT #columnList = COALESCE(#columnList + ', ', '') + sc.name
FROM sysobjects so
INNER JOIN syscolumns sc ON so.id = sc.id
WHERE so.name = #tableName
AND sc.name LIKE 'check%'
DECLARE #query as varchar(4000);
SET #query = 'SELECT ' + #columnList + ' FROM ' + #tableName;
EXEC(#query);
The ending #query should contain SELECT check1, check2, check... FROM Checklist.
In Sql Server, this is terrible but you could do it... Building dynamic SQL
check% being your check* in the Select #columns query
DECLARE #columns NVARCHAR(max);
SELECT #columns = STUFF((
SELECT ',' + column_name
FROM INFORMATION_SCHEMA.columns
WHERE table_name = 'Checklist'
AND column_name LIKE 'check%'
FOR XML path('')
), 1, 1, '')
DECLARE #statement nvarchar(max) = 'SELECT ' + #columns + ' FROM Checklist'
EXECUTE sp_executesql #statement
Troll ass answer...
SELECT check1,check2,check3,check4,check5,check6,check7,check8,check9,check10,check11,check12,check13,check14,check15,check16,check17,check18,check19,check20,check21,check22,check23,check24,check25,check26,check27 FROM Checklist;

SQL Server: how to replace the line breaks in the text fields?

I have the following SQL
SELECT COLUMN_NAME
FROM Information_Schema.Columns
WHERE TABLE_NAME = 'TABLE'
AND DATA_TYPE = 'varchar'
ORDER BY COLUMN_NAME
It returns:
COLUMN_NAME
-------------------
CiudadConductor
CiudadPropietario
CodBaseLegal
DireccConductor
DireccPropietario
PTActa
PTClase
PTCodCategoriaClase
PTCodCIP
PTCodiConductor
With this SQL I need to identify the varchar columns that exist, but I want to do a data update.
I know that this SQL replaces but how do I make the two SQL statements work together.
REPLACE(ColumnName, CHAR(13),'')
I want to remove the line breaks in all varchar columns
This should work (untested), however, the PRINT will guide you if it's perhaps a little "off":
DECLARE #SQL nvarchar(MAX),
#Table sysname = N'T700InfracTrans';
SET #SQL = N'UPDATE ' + QUOTENAME(#Table) + NCHAR(13) + NCHAR(10) +
N'SET ' + STUFF((SELECT N',' + NCHAR(13) + NCHAR(10) +
N' ' + QUOTENAME(COLUMN_NAME) + N' = REPLACE(REPLACE(' + QUOTENAME(COLUMN_NAME) + N',CHAR(13),''''),CHAR(10),'''')'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #Table
AND DATA_TYPE = N'varchar'
ORDER BY COLUMN_NAME
FOR XML PATH(''),TYPE).value('.','nvarchar(MAX)'),1,7,N'') + N';';
PRINT #SQL; --Your debugging best friend.
--EXEC sp_executesql #SQL; --Uncomment to execute
Try this
DECLARE #SqlTab TABLE(ID INT IDENTITY(1, 1) NOT NULL PRIMARY KEY, SQL NVARCHAR(MAX) NOT NULL)
DECLARE #TableName sysname = 'TableName'
INSERT INTO #SqlTab(Sql)
SELECT
SQL = 'UPDATE ' + QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) + ' SET ' + QUOTENAME(COLUMN_NAME) + ' = REPLACE(REPLACE(' + COLUMN_NAME + ', CHAR(13), ''''), CHAR(10), '''') '
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
TABLE_NAME = #TableName --<-- Remove if you want to strip out CRLF in All Tables
AND DATA_TYPE IN('VARCHAR')
ORDER BY
TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
DECLARE #ID INT = -1, #Sql NVARCHAR(MAX)
WHILE(1 = 1)
BEGIN
SELECT
#ID = ID
,#Sql = Sql
FROM
#SqlTab
WHERE
ID > #ID
IF ##ROWCOUNT = 0 BREAK
--PRINT #Sql
EXEC SP_ExecuteSql #Sql
END

How can I modify a stored procedure to include multiple tables

Hi this Stored Procedure works for one table, I wondered can it be expanded to more than one table so that the returned values technically searches across multiple tables. I have read similar posts which cover an entire database but limiting to a small cluster of tables rather than having to do each manually.
Basically so the final execute runs this below basically going through all tables which start with LZO_AE
#table_name ='LZO_AE%'
IF NOT EXISTS (
SELECT *
FROM INFORMATION_SCHEMA.ROUTINES
WHERE ROUTINE_NAME = 'spSearchStringInTable'
AND ROUTINE_TYPE = 'PROCEDURE'
)
EXECUTE ('CREATE PROCEDURE dbo.spSearchStringInTable AS SET NOCOUNT ON;');
GO
ALTER PROCEDURE spSearchStringInTable (
#SearchString NVARCHAR(MAX)
,#Table_Schema SYSNAME = 'dbo'
,#Table_Name SYSNAME
)
AS
BEGIN
DECLARE #Columns NVARCHAR(MAX)
,#Cols NVARCHAR(MAX)
,#PkColumn NVARCHAR(MAX)
-- Get all character columns
SET #Columns = STUFF((
SELECT ', ' + QUOTENAME(Column_Name)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE IN (
'text'
,'ntext'
,'varchar'
,'nvarchar'
,'char'
,'nchar'
,'int'
)
AND TABLE_NAME = #Table_Name
AND TABLE_SCHEMA = #Table_Schema
ORDER BY COLUMN_NAME
FOR XML PATH('')
), 1, 2, '');
IF #Columns IS NULL -- no character columns
RETURN - 1;
-- Get columns for select statement - we need to convert all columns to nvarchar(max)
SET #Cols = STUFF((
SELECT ', CAST(' + QUOTENAME(Column_Name) + ' AS nvarchar(max)) COLLATE DATABASE_DEFAULT AS ' + QUOTENAME(Column_Name)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE IN (
'text'
,'ntext'
,'varchar'
,'nvarchar'
,'char'
,'nchar'
,'int'
)
AND TABLE_NAME = #Table_Name
AND TABLE_SCHEMA = #Table_Schema
ORDER BY COLUMN_NAME
FOR XML PATH('')
), 1, 2, '');
SET #PkColumn = STUFF((
SELECT N' + ''|'' + ' + ' CAST(' + QUOTENAME(CU.COLUMN_NAME) + ' AS nvarchar(max)) COLLATE DATABASE_DEFAULT '
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS AS TC
INNER JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS CU ON TC.CONSTRAINT_TYPE = 'PRIMARY KEY'
AND TC.CONSTRAINT_NAME = CU.CONSTRAINT_NAME
WHERE TC.TABLE_SCHEMA = #Table_Schema
AND TC.TABLE_NAME = #Table_Name
ORDER BY CU.ORDINAL_POSITION
FOR XML PATH('')
), 1, 9, '');
IF #PkColumn IS NULL
SELECT #PkColumn = 'CAST(NULL AS nvarchar(max))';
-- set select statement using dynamic UNPIVOT
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'SELECT *, ' + QUOTENAME(#Table_Schema, '''') + ' AS [Table Schema], ' + QUOTENAME(#Table_Name, '''') + ' AS [Table Name]' + ' FROM
(SELECT ' + #PkColumn + ' AS [PK Column], ' + #Cols + ' FROM ' + QUOTENAME(#Table_Schema) + '.' + QUOTENAME(#Table_Name) + ' ) src UNPIVOT ([Column Value] for [Column Name] IN (' + #Columns + ')) unpvt
WHERE [Column Value] LIKE ''%'' + #SearchString + ''%'''
--print #SQL
EXECUTE sp_ExecuteSQL #SQL
,N'#SearchString nvarchar(max)'
,#SearchString;
END
GO
execute
dbo.spSearchStringInTable
#SearchString = N'410605003',
#table_schema = 'dbo',
#table_name ='LZO_AETRIAGETREATMENTHISTORY'
You could use a CURSOR to retrieve the matching table names and then process each one in a loop. For example,
ALTER PROCEDURE [dbo].[multi_table_test] AS
BEGIN
SET NOCOUNT ON;
DECLARE #table_pattern NVARCHAR(max);
DECLARE #table_name NVARCHAR(max);
DECLARE #sql NVARCHAR(max)
DECLARE #output_table TABLE (table_name NVARCHAR(max), row_count INT);
DECLARE #row_count INT;
SET #table_pattern = N'LZO_AE%';
DECLARE table_list CURSOR FOR
SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME LIKE #table_pattern;
OPEN table_list;
FETCH NEXT FROM table_list INTO #table_name;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = N'SELECT #n = (SELECT COUNT(*) FROM [' + #table_name + N'])';
EXEC sp_executesql #sql, N'#n INT OUTPUT', #n = #row_count OUTPUT;
INSERT INTO #output_table (table_name, row_count) VALUES (#table_name, #row_count);
FETCH NEXT FROM table_list INTO #table_name;
END
CLOSE table_list;
DEALLOCATE table_list;
SELECT table_name, row_count FROM #output_table;
END
returns this
table_name row_count
---------- ---------
LZO_AE01 3
LZO_AE02 5

SQL query to dynamically COUNT(FIELD) for all fields of table X

This should be such an easy thing, but it has me totally stumped.
You can easily return the count of each field of a table manually, with oneliners such as:
select count(FIELD1) from TABLE1 --42,706
select count(FIELD5) from TABLE1 --42,686
select count(FIELD9) from TABLE1 --2,918
This is slow and painful if you want to review several dozen tables the same way, and requires you to know the names of the fields in advance.
How handy would it be to have a script you can connect to any database, simply feed it a table name, and it will automatically return the counts for each field of that table?
Seems you can get half the work done with:
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'TABLE1'
Something is flawed even with my barebones approach (explicitly hitting one field instead of them all):
declare #TABLENAME varchar(30), #FIELDNAME varchar(30)
set #TABLENAME = 'TABLE1'
set #FIELDNAME = (select top 1 COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TABLENAME
and COLUMN_NAME = 'FIELD9')
select #FIELDNAME, count(#FIELDNAME) from TABLE1
The result is 42,706. Recall from my example above that FIELD9 only contains 2,918 values.
Even if that wasn't a problem, the more dynamic query would replace the last line with:
select #FIELDNAME, count(#FIELDNAME) from #TABLENAME
But SQL Server returns:
Must declare the table variable "#TABLENAME".
So I can avoid that by restructuring the query with a temp table:
declare #FIELDNAME varchar(30)
set #FIELDNAME = (select top 1 COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'TABLE1'
and COLUMN_NAME = 'FIELD9')
if OBJECT_ID('TEMPDB..#TEMP1') is not null
drop table #TEMP1
select *
into #TEMP1
from TABLE1 --still not exactly dynamic!
select #FIELDNAME, count(#FIELDNAME) from #TEMP1
But that still brings us back to the original problem of returning 42,706 instead of 2,918.
I am running SQL Server 2008 R2, if it makes any difference.
Your query:
SELECT #FIELDNAME, COUNT(#FIELDNAME) FROM TABLE1
does not count FIELD9, #FIELDNAME is treated as a constant. It's like doing a COUNT(*).
You should use dynamic sql:
DECLARE #sql VARCHAR(MAX)
SET #sql = 'SELECT ''' + #fieldName + ''', COUNT([' + #fieldName + ']) FROM [' + #tableName + ']'
EXEC(#sql)
To get all columns and return it in a single result set without using a Temporary Table and CURSOR:
DECLARE #sql NVARCHAR(MAX) = ''
SELECT #sql = #sql +
'SELECT ''' + COLUMN_NAME + ''' AS ColName, COUNT([' + COLUMN_NAME + ']) FROM [' + #tableName + ']' + CHAR(10) +
'UNION ALL' + CHAR(10)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tableName
SELECT #sql = LEFT(#sql, LEN(#sql) - 10)
EXEC(#sql)
Just set the #TargetTableName will do the job
DECLARE #TargetTableName sysname = '*'
SET NOCOUNT ON
DECLARE #TableName sysname, #ColumnName sysname, #Sql nvarchar(max)
DECLARE #TableAndColumn table
(
TableName sysname,
ColumnName sysname
)
DECLARE #Result table
(
TableName sysname,
ColumnName sysname,
NonNullRecords int
)
INSERT #TableAndColumn
SELECT o.name, c.name FROM sys.objects o INNER JOIN sys.columns c ON o.object_id = c.object_id
WHERE (o.name = #TargetTableName OR #TargetTableName = '*') AND o.type = 'U' AND c.system_type_id NOT IN (34, 35, 99) -- 34:image 35:text 99:ntext
ORDER BY c.column_id
DECLARE column_cursor CURSOR FOR SELECT TableName, ColumnName FROM #TableAndColumn
OPEN column_cursor
FETCH NEXT FROM column_cursor
INTO #TableName, #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Sql = 'SELECT ''' + #TableName + ''' AS TableName, ''' + #ColumnName + ''' AS ColumnName, COUNT([' + #ColumnName + ']) AS NonNullRecords FROM [' + #TableName + ']'
print #Sql
INSERT #Result
EXEC (#Sql)
FETCH NEXT FROM column_cursor
INTO #TableName, #ColumnName
END
CLOSE column_cursor;
DEALLOCATE column_cursor;
SET NOCOUNT OFF
SELECT * FROM #Result