Loop through each table in database and get first three digits of one column - tsql

I am trying to create a table that will show me each table name, and the first three characters of the ID column (every table has this column), and then put that data into a table. We are using this to help map dependencies in our Salesforce Org which is replicated onto SQL using dbAMP. I adapted the code below as far as I could, and am looking for help to finish it.
UPDATE: The first half of the question is resolved, and the code now runs to give the first three characters of the ID. I still could use help in converting this code to spool the results into one single table.
USE Salesforce
GO
IF OBJECT_ID('tempdb..#tempResults') IS NOT NULL
DROP TABLE #tempResults
CREATE TABLE #tempResults
(
[Object_ID] VARCHAR(3)
--, [org] VARCHAR(100)
, [Table_Name] VARCHAR(100)
)
DECLARE cur CURSOR FOR
SELECT
'SELECT DISTINCT LEFT(' + QUOTENAME(c.COLUMN_NAME) + ',3) AS
[Object_ID], '''
--+ QUOTENAME(TABLE_CATALOG) + ' as [Org], '
+ QUOTENAME(TABLE_NAME) + ''' as [Table_Name] FROM '
+ QUOTENAME(TABLE_CATALOG) + '.' + QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE
c.[DATA_TYPE] IN ('nchar','varchar', 'nvarchar')
AND c.[CHARACTER_MAXIMUM_LENGTH] = 18
and c.TABLE_NAME not like '%upload%'
and c.TABLE_NAME not like '%Delta%'
and c.TABLE_NAME not like '%Update%'
and c.TABLE_NAME not like '%Previous%'
and C.COLUMN_NAME = 'ID'
DECLARE #cmd VARCHAR(MAX);
OPEN cur;
FETCH NEXT FROM cur INTO #cmd;
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT #cmd
INSERT INTO #tempResults
EXEC(#cmd);
FETCH NEXT FROM cur INTO #cmd;
END
CLOSE cur;
DEALLOCATE cur;
SELECT * FROM #tempResults
I'm not getting the Column ID trimmed to the first 3 characters, and it's not outputting to a table. I'm not that familiar with cursors so I'd appreciate any help. Thanks,

You're really close. The way you've got your select written it's actually trimming the column name and then appending that 3-character column name to the string, rather than getting the first three characters from the actual data within the ID column.
Try updating your select so the LEFT brackets the QUOTENAME(c.COLUMN_NAME) inside the string, like below. This small change made your script work on my salesforce installation.
SELECT
'SELECT DISTINCT LEFT(' + QUOTENAME(c.COLUMN_NAME) + ',3) AS '
+ QUOTENAME(TABLE_CATALOG + '.' + TABLE_SCHEMA + '.' + TABLE_NAME) + ' FROM '
+ QUOTENAME(TABLE_CATALOG) + '.' + QUOTENAME(TABLE_SCHEMA) + '.'
+ QUOTENAME(TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE
c.[DATA_TYPE] IN ('nchar','varchar', 'nvarchar')
AND c.[CHARACTER_MAXIMUM_LENGTH] = 18
and c.TABLE_NAME not like '%upload%'
and c.TABLE_NAME not like '%Delta%'
and c.TABLE_NAME not like '%UPdate%'
and c.TABLE_NAME not like '%Previous%'
and C.COLUMN_NAME = 'ID'
Update to answer part two:
First you'll need a destination table - I've used a temporary table, but this will work just fine as a permanent table.
I've adjusted the code to output two columns instead of one: IDSubstring (your 3-character ID portion) and SourceTable (this is exactly the same information that you were using as a column name previously). This way we know which table the ID portion belongs to.
Then, inside the cursor, instead of just executing, we do this:
INSERT INTO #tempResults
EXEC(#cmd);
This will populate our table and give us selectable values.
CREATE TABLE #tempResults
(
[IDSubstring] VARCHAR(3)
, [SourceTable] VARCHAR(50)
)
DECLARE cur CURSOR FOR
SELECT
'SELECT DISTINCT LEFT(' + QUOTENAME(c.COLUMN_NAME) + ',3) AS [IDSubstring], '''
+ QUOTENAME(TABLE_CATALOG + '.' + TABLE_SCHEMA + '.' + TABLE_NAME) + ''' as [SourceTable] FROM '
+ QUOTENAME(TABLE_CATALOG) + '.' + QUOTENAME(TABLE_SCHEMA) + '.'
+ QUOTENAME(TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE
c.[DATA_TYPE] IN ('nchar','varchar', 'nvarchar')
AND c.[CHARACTER_MAXIMUM_LENGTH] = 18
and c.TABLE_NAME not like '%upload%'
and c.TABLE_NAME not like '%Delta%'
and c.TABLE_NAME not like '%UPdate%'
and c.TABLE_NAME not like '%Previous%'
and C.COLUMN_NAME = 'ID'
DECLARE #cmd VARCHAR(MAX);
OPEN cur;
FETCH NEXT FROM cur INTO #cmd;
WHILE ##FETCH_STATUS = 0
BEGIN
--PRINT #cmd
INSERT INTO #tempResults
EXEC(#cmd);
FETCH NEXT FROM cur INTO #cmd;
END
CLOSE cur;
DEALLOCATE cur;
SELECT * FROM #tempResults
Update 2
You're correct in assuming it's an issue with the '''
The ''' is there because we need to build our string with single quotes inside of it.
As examples:
SELECT '' returns nothing
while SELECT '''' returns '
And SELECT 'This is an example''' will return This is an example'
So the ''' are part of a larger string definition started with the initial ' before "This" and can be broken down this way - the first two single quotes are the single quote we want to print within the string and the third single quote is the string terminating quote. If you just run the select statement and look at what it outputs, you can see where each single quote has been inserted into the string.
Updated SELECT is below.
SELECT
'SELECT DISTINCT LEFT(' + QUOTENAME(c.COLUMN_NAME) + ',3) AS [Object_ID], '''
+ QUOTENAME(TABLE_CATALOG) + ''' as [Org], '''
+ QUOTENAME(TABLE_NAME) + ''' as [Table_Name] FROM '
+ QUOTENAME(TABLE_CATALOG) + '.' + QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE
c.[DATA_TYPE] IN ('nchar','varchar', 'nvarchar')
AND c.[CHARACTER_MAXIMUM_LENGTH] = 18
and c.TABLE_NAME not like '%upload%'
and c.TABLE_NAME not like '%Delta%'
and c.TABLE_NAME not like '%Update%'
and c.TABLE_NAME not like '%Previous%'
and C.COLUMN_NAME = 'ID'

Related

Getting Error With GROUP BY when Converting Query to Dynamic SQL

I'm trying to convert the following query to dynamic SQL to allow for variations:
UPDATE T
SET SumCount = J.SUM
FROM #temp T
JOIN (SELECT Count_99221 + COUNT_99222 + Count_99223 [SUM], t2.userID
FROM #temp t2
GROUP BY t2.userID, Count_99221 + COUNT_99222 + Count_99223
) J ON T.userID = J.UserID
This is what I have for the Dynamic SQL:
DECLARE #sql3 nvarchar(2000) =
'UPDATE T ' +
'SET SumCount = J.SumOfColumns ' +
'FROM #temp T ' +
'JOIN (SELECT ' + #columnSumString + ' [SumOfColumns], t2.userID ' +
'FROM #temp t2 ' +
'GROUP BY t2.userID, ' + #columnSumString +
' ) J ON T.userID = J.UserID'
EXEC sp_executesql #sql3
I am receiving the following error only when I run the query as Dynamic SQL:
Each GROUP BY expression must contain at least one column that is not
an outer reference.
Can somebody help explain why this is happening? I am new to Dynamic SQL so I'm not privy to any limitations for running queries this way.
Thank you in advance.
EDIT:
The variable #columnString is a string made by concatenating several other column names, created in the following way:
DECLARE #Cursor Cursor
DECLARE #code varchar(20)
DECLARE #ID INT
SET #cptCursor = CURSOR FOR
SELECT * FROM dbo.Split(#UserInput,CHAR(44))
OPEN #cptCursor
FETCH NEXT FROM #cptCursor INTO #ID, #code
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #colName varchar(50) = 'Count_' + cast(#code as varchar(10))
DECLARE #sql nvarchar(50) = 'ALTER TABLE #temp ADD ' + #colName + ' int'
EXEC sp_executesql #sql
--Code that adds values to each column that is created.....
SET #columnSumString = #colName + ' + ' + #columnSumString
--SET #columnSumString = #code + ' + ' + #columnSumString
FETCH NEXT FROM #cptCursor INTO #ID, #code
END
CLOSE #Cursor
DEALLOCATE #Cursor
SET #columnSumString = SUBSTRING(#columnSumString,1,LEN(#columnSumString)-2)
SELECT #columnSumString
The user input is a comma separated string. "Count_99221 + COUNT_99222 + Count_99223" is just one example of columns created from the user input "99221, 99222, 99223".
I also realized I was concatenating the #code variable into #columnSumString instead of #colName. Now when I run the query I don't get the error (even though I don't understand how the above error message relates to that mistake) but every value of SumCount is NULL.
IMHO you must re-write your query as follow:
UPDATE #temp
SET SumCount =
(SELECT Count_99221 + COUNT_99222 + Count_99223
FROM #temp t2
WHERE t2.userID = #temp.userID)
So the dynamic SQL will become:
DECLARE #columnString varchar(200)
SET #columnString = Count_99221 + COUNT_99222 + Count_99223
DECLARE #sql3 nvarchar(2000) =
N'UPDATE #temp ' +
'SET SumCount = (SELECT ' + #columnString +
' FROM #temp t2 WHERE t2.userID = #temp.userID)'
EXEC sp_executesql #sql3

How can i pass parameters to a Common Table Expression inside a sp?

I want to rewrite a functioning, but slow stored procedure using CTE (common table Expression)
I have a big stored procedure where i build the nececary SQL dynamicaly based on parameter used. Currently there are 27 parameters, i compose the SQL string that i execute at the end it looks like:
DECLARE #SqlWhereClause NVARCHAR(MAX)
SET #SqlWhereClause = ' WHERE ([InTimeStamp] BETWEEN ''' + CONVERT(VARCHAR(19), #fromDate, 120) + ''' AND ''' + CONVERT(VARCHAR(19), #toDate, 120) + ''')'
IF #showOnlyErrors = '1'
BEGIN
SET #SqlWhereClause += ' AND Status = ''Error'''
END
IF LEN(LTRIM(RTRIM(#docNo))) > 0
BEGIN
IF #matchExact = '1'
BEGIN
SET #SqlWhereClause += ' AND DocumentNumber = ''' + #docNo + ''''
END
ELSE
BEGIN
SET #SqlWhereClause += ' AND (contains([DocumentNumber],'''+ #docNo +'''))'
END
END
At the end, i add the pagination and transform it to the final formSQL:
IF CONVERT(int, LTRIM(RTRIM(#takeRows))) > 0
BEGIN
SET #SqlOrderByClause += ' OFFSET ' + #rowNumberToSkip +' ROWS FETCH NEXT '+ #takeRows +' ROWS ONLY '
Set #RowCount = ' Select #totalRecords = count(1) from dbo.Messages WITH (NOLOCK) ' + #SqlWhereClause
END
SET #SQL = #SqlSelect + #SqlFrom + #SqlWhereClause + #SqlOrderByClause + ' ; ' + #RowCount
PRINT #SQL
EXECUTE sp_executesql #SQL, #params, #totalRecords OUTPUT
Everithing is working like a charm. No problems. Only performance problems. To solve one of it, i would try to use CTE (common table extpression)
But this is not working:
With DataSQL AS
(#SqlSelect + #SqlFrom + #SqlWhereClause + #SqlOrderByClause),
- incorrect syntax near #SqlSelect - Expecting '(' or Select.
I also tried this one:
WITH DataSQL AS
( Select #SqlSelect From #SqlFromFast
Where #SqlWhereClause Order By #SqlOrderByClause),
here i get:
An expression of non-boolean type specified in a context where a condition is expected, near 'Order'
Any idea? Or is it not possible to use CTE with multiple variables? All i found until now are simply queries with one, maybe two variables.
You could try:
WITH DataSQL AS
(
SELECT #SqlSelect SqlSelect
, #SqlFromFast SqlFromFast
, etcetera
)
Make sure you give aliases, or it won't work. I doubt it will help your performance issues though. You could try to use a temp table, that's usually quicker. you could also try to use inner variables:
DECLARE #Select VARCHAR(MAX) = #SQLSelect
And use those instead, that may help the optimizer, though that depends on your data.

Calling one sp from another

On this link
http://gallery.technet.microsoft.com/Compare-Data-0c5bfc87#content
we can find a stored procedure example which can compare two tables data. WHat I would like is to call this sp for each table in database. I have found next sp that will enumerate through all tables
http://weblogs.sqlteam.com/joew/archive/2007/10/23/60383.aspx
The problem is that I cannot get to pass properly parameters. Here is what I tried (I have placed both databases on local server):
Exec sp_MSforeachtable "EXEC sp_CompareTable dbName1, dbName2, NULL, PARSENAME('?', 1)"
and that fails with
Msg 102, Level 15, State 1, Line 1 Incorrect syntax near '[dbo].[Activities]'.
And the same message for every table. Can anyone help me with what I am doing wrong here?
I'll stick my neck out and post this as an answer because it's not formatted nicely as a comment.
Have you tried this:
sp_MSforeachtable "EXEC sp_CompareTable dbName1, dbName2, NULL, PARSENAME('[?]', 1)"
Update:
It looks like it doesn't like the PARSENAME. You could try this (I tried this on a version of sp_CompareTable with the EXEC changed to a PRINT).
Add this line to sp_CompareTable (before the EXEC):
SET #TableName = PARSENAME(#TableName,1)
Call it like this:
sp_MSforeachtable "EXEC sp_CompareTable dbName1, dbName2, dbo, '?'"
NB: This would be a quick fix for the case where you only have the "dbo" schema. It doesn't really answer the question of exactly why the original syntax doesn't work.
Update Again:
Here's a version of the Compare Table stored procedure which is tailored to run with sp_MSforeachtable
CREATE PROC [dbo].[uspCompareTable](#db1 varchar(250), #db2 sysname, #TableName sysname)
AS
declare #reason varchar(7)='Missing';
IF #TableName = '[dbo].[sysdiagrams]'
RETURN
IF CHARINDEX('.',#db1,1) <> 0
SET #db1=QUOTENAME(SUBSTRING(#db1,1, CHARINDEX('.',#db1,1)-1))+'.'+QUOTENAME(SUBSTRING(#db1, CHARINDEX('.',#db1,1)+1,DATALENGTH(#db1)-CHARINDEX('.',#db1,1)))
IF CHARINDEX('.',#db2,1) <> 0
SET #db2=QUOTENAME(SUBSTRING(#db2,1, CHARINDEX('.',#db2,1)-1))+'.'+QUOTENAME(SUBSTRING(#db2, CHARINDEX('.',#db2,1)+1,DATALENGTH(#db2)-CHARINDEX('.',#db2,1)))
EXEC ('
SELECT * FROM
(SELECT * FROM '+ #db1 + '.' + #TableName +'
EXCEPT
SELECT * FROM '+ #db2 + '.' + #TableName +') T
CROSS JOIN (SELECT '''+#reason +' in '+#db2 +'.' + #TableName+''' Reason) T2
UNION ALL
SELECT * FROM
(SELECT * FROM '+ #db2 + '.' + #TableName +'
EXCEPT
SELECT * FROM '+ #db1 + '.' + #TableName +' ) T
CROSS JOIN (SELECT ''' + #reason + ' in ' + #db1 + '.' + #TableName + ''' Reason) T2')
Here I'm assuming that schema will be part of the TableName (which it should be if you're calling from sp_MSforeachtable). Also a tweak to skip sysdiagrams which gets picked up on my system (SQL Server 2008 Express).
Usage would be
sp_MSforeachtable "EXEC uspCompareTable dbname1, dbname2, '?'"

Applying T-SQL...Back to Basics

IM very new to TSQL and am getting my head around stored proceedures etc.
I am using the Code to find a value within one table in my data base, but im not too sure how i would use this code...
Do i need to replace all #* with my relevant table or column name or simply compy paste and execute
Thanks for the help
How do I find a value anywhere in a SQL Server Database?
CREATE PROC SearchAllTables
(#SearchStr nvarchar(100) ) AS BEGIN
-- Copyright © 2002 Narayana Vyas Kondreddi. All rights reserved.
-- Purpose: To search all columns of all tables for a given search string
-- Written by: Narayana Vyas Kondreddi
-- Site: http://vyaskn.tripod.com
-- Tested on: SQL Server 7.0 and SQL Server 2000
-- Date modified: 28th July 2002 22:50 GMT
DECLARE #Results
TABLE(ColumnName nvarchar(370), ColumnValue nvarchar(3630))
SET NOCOUNT ON
DECLARE #TableName nvarchar(256), #ColumnName nvarchar(128), #SearchStr2 nvarchar(110)
SET #TableName = ''
SET #SearchStr2 = QUOTENAME('%' + #SearchStr + '%','''')
WHILE #TableName IS NOT NULL
BEGIN
SET #ColumnName = ''
SET #TableName = (SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME))
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > #TableName
AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)), 'IsMSShipped') = 0)
WHILE (#TableName IS NOT NULL)
AND (#ColumnName IS NOT NULL)
BEGIN
SET #ColumnName =(SELECT MIN(QUOTENAME(COLUMN_NAME))
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(#TableName, 2)
AND TABLE_NAME = PARSENAME(#TableName, 1)
AND DATA_TYPE IN ('char', 'varchar', 'nchar', 'nvarchar')
AND QUOTENAME(COLUMN_NAME) > #ColumnName)
IF #ColumnName IS NOT NULL
BEGIN
INSERT INTO #Results
EXEC
('SELECT ''' + #TableName + '.' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630)
FROM ' + #TableName + ' (NOLOCK) ' + ' WHERE ' + #ColumnName + ' LIKE ' + #SearchStr2)
END
END
END
SELECT ColumnName, ColumnValue
FROM #Results
END
Well, to answer your question:
First you have to copy the code to a query window an run it.
That will create the stored procedure.
Now you can call the stored procedute by calling:
EXEC SearchAllTables 'a string of your choice'
Note that you will only get hits from text columns (like 'char', 'varchar', 'nchar', 'nvarchar').

Export query to text file

What I am trying to do is export a Tsql query to a csv file. Simple enough, however I need to be able to specify which fields are wrapped in quotes "". I can get my query to export with all the feilds wrapped.
"SCHEN001","Joe Bloggs Inc","1","1","1","1","1","1","13","6","Mr John Smith"
What I would like to export is
"SCHEN001","Joe Bloggs Inc",1,1,1,1,1,1,13,6,"Mr John Smith"
Is this possible using Tsql?
Any ideas would be greatly appreciated
Take a look at the bcp.exe utility. It is ment for bulk operations and you can specify templates for exports etc.
A link that seems reasonable: http://www.simple-talk.com/sql/database-administration/creating-csv-files-using-bcp-and-stored-procedures/
Another approach is to use SQL Server Integration Services (if you have MS SQL Server Standard or Enterprise edition)
or, alternatively, you can copy grid results into Excel, and export CSV from there :-)
Try to use this script.
Set variable #TblName to the name of your table.
The script uses information_schema.columns
to get the datatypes for every column in selected table.
DECLARE #TblName varchar(128)
DECLARE #WhereClause varchar(255)
DECLARE #cmd varchar(7000)
SET #TblName = '<YOUR TABLENAME>' --TABLENAME
SET #cmd = ''
create table #tableDef (id int identity (1,1), ColType int, ColName varchar(128))
--Fetch table information
insert #tableDef (ColType, ColName)
select case when DATA_TYPE like '%char%' then 1
when DATA_TYPE like '%datetime%' then 2
else 0 end ,
COLUMN_NAME
from information_schema.columns
where TABLE_NAME = #TblName
order by ORDINAL_POSITION
SELECT #cmd = #cmd
+ ' CASE WHEN ' + ColName + ' IS NULL '
+ ' THEN ''NULL'' '
+ ' ELSE '
+ case ColType
when 1 then ''''''''' + ' + ColName + ' + '''''''''
when 2 then ''''''''' + ' + 'CONVERT(VARCHAR(20),' + ColName + ')' + ' + '''''''''
else 'CONVERT(VARCHAR(20),' + ColName + ')' end
+ ' END + '','' + '
from #tableDef
order by id
select #cmd = 'SELECT ' + left(#cmd,len(#cmd)-8) + '+'''' FROM ' + #tblName
exec (#cmd)
drop table #tableDef