Easy task is giving me a hard time. I want to select one column from different tables and insert it in a results table. Basically, much like a union all:
SELECT Email FROM TableA
UNION ALL
Select Email FROM TableB
and so on...
However, I want to do this in an automated way. As I said, seems so simple, but I am stumbling over it. My code attempt:
USE MyDatabase
IF OBJECT_ID ('TEMPDB..#Selection') IS NOT NULL DROP TableA #Selection;
SELECT Name AS TableA, ROW_NUMBER() OVER ( ORDER BY (SELECT 1)) AS RowNumb
INTO #Selection
FROM Sys.TableAs AS T
WHERE NAME LIKE '%abc%'
ORDER BY 2
IF OBJECT_ID ('TEMPDB..#Result') IS NOT NULL DROP TableA #Result;
CREATE TableA #Result ( Email VARCHAR (200))
DECLARE #Counter INT
SET #Counter = 1
WHILE #Counter <= ( SELECT MAX (RowNumb) FROM #Selection )
BEGIN
DECLARE #Table VARCHAR (100)
SET #Table = ( SELECT TableA FROM #Selection WHERE RowNumb = #Counter )
-- PRINT #Table SET #Counter = #Counter + 1 END
INSERT INTO #Result
SELECT Email
FROM #Table
SET #Counter = #Counter + 1
END
I am sure someone will find my mistakes quickly. Thanks a lot for any guidance!
Kind regards, M.
I would (personally) go for something more like this:
CREATE TABLE #Email (email nvarchar(200));
DECLARE #SQL nvarchar(MAX);
SET #SQL = N'INSERT INTO #Email (Email)' + NCHAR(13) + NCHAR(10) +
STUFF((SELECT NCHAR(13) + NCHAR(10) +
N'UNION ALL' + NCHAR(13) + NCHAR(10) +
N'SELECT CONVERT(nvarchar(200),email)' + NCHAR(13) + NCHAR(10) +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
FROM sys.schemas s
JOIN sys.tables t ON s.schema_id = t.schema_id
JOIN sys.columns c ON t.object_id = c.object_id
WHERE c.[name] = N'Email'
FOR XML PATH(N''),TYPE).value(N'.','nvarchar(MAX)'),1,13,N'') + N';';
PRINT #SQL;
EXEC sp_executesql #SQL;
SELECT *
FROM #Email;
DROP TABLE #Email;
This creates a dynamic statement that creates a UNION ALL query against every table with the column Email (in the current database) and inserts the value into said temporary table. It then returns said values from the temporary table (and then disposes of the table, as I don't actually know what you're going to do with it).
Related
I have almost 1000 tables and most of them have a common column ItemNumber. How do I search across all the tables in the database for a value or list of values that exist in this common column, such as 350 or (350, 465)? The tables have different schemas.
Table A100
ItemNumber
Detail
230
Car
245
Plane
Table A1000
ItemNumber
ProductDescription
350
Pie
465
Cherry
This does not perform type checking, so you can get conversion errors if the target column is not the correct type. Also, this script uses LIKE, you would probably need to change that to a direct comparison.
SET NOCOUNT ON
DECLARE #ID NVARCHAR(100) = '2'
DECLARE #ColumnName NVARCHAR(100) ='UserID'
DECLARE #Sql NVARCHAR(MAX)=N'CREATE TABLE #TempResults(TableName NVARCHAR(50), ColumnName NVARCHAR(50), ItemCount INT)'
SELECT
#Sql = #Sql + N'INSERT INTO #TempResults SELECT * FROM (SELECT '''+ST.Name+''' AS TableName, '''+C.Name+''' AS ColumnName, COUNT(*) AS ItemCount FROM '+ST.Name+' WHERE '+C.Name+'='+#ID+') AS X WHERE ItemCount > 0 '
FROM
sys.columns C
INNER JOIN sys.tables ST ON C.object_id = ST.object_id
WHERE
C.Name LIKE '%'+#ColumnName+'%'
SET #Sql = #Sql + N'SELECT * FROM #TempResults'
exec sp_executesql #sql
You need to do this with dynamic SQL. You will need to query all 1000 tables, and make sure you are converting the values correctly if the columsn are different types.
You don't need a temp table for this, you can just script one giant UNION ALL query. You must make sure to quote all dynamic names correctly using QUOTENAME.
To be able to return data for multiple items, you should create a Table Valued Parameter, which you can pass in using sp_executesql.
First create a table type
CREATE TYPE dbo.IntList (Id int PRIMARY KEY);
Then you create a table variable containing them, and pass it in. You can also do this in a client application and pass in a TVP.
SET NOCOUNT ON;
DECLARE #Items dbo.IntList;
INSERT #Items (Id) VALUES(350),(465);
DECLARE #Sql nvarchar(max);
SELECT
#Sql = STRING_AGG(CONVERT(nvarchar(max), N'
SELECT
' + QUOTENAME(t.name, '''') + ' AS TableName,
t.ItemNumber,
COUNT(*) AS ItemCount
FROM ' + QUOTENAME(t.Name) + ' t
JOIN #items i ON i.Id = t.ItemNumber
GROUP BY
t.ItemNumber
HAVING COUNT(*) > 0
' ),
N'
UNION ALL
' )
FROM
sys.tables t
WHERE t.object_id IN (
SELECT c.object_id
FROM sys.columns c
WHERE
c.Name = 'ItemNumber'
);
PRINT #sql; -- your friend
EXEC sp_executesql
#sql,
N'#items dbo.IntList',
#items = #items READONLY;
If you don't need to know the count, and only want to know if a value exists, you can change the dynamic SQL to an EXISTS
....
SELECT
#Sql = STRING_AGG(CONVERT(nvarchar(max), N'
SELECT
' + QUOTENAME(t.name, '''') + ' AS TableName,
t.ItemNumber
FROM #items i
WHERE i.Id IN (
SELECT t.ItemNumber
FROM ' + QUOTENAME(t.Name) + ' t
)
' ),
N'
UNION ALL
' )
....
I am attempting to return all table results from each table in my database AdventureWorksDW2019. Would my current approach work somehow? Or is there more simple/efficient way to do this? In my current approach, I am storing all of the table names in a temp table with their respective row number when sorted by ascending name. Then, I am trying to wrap that in a WHILE statement to loop through each table and select all results from each table. Any advice would be greatly appreciated!
DROP TABLE IF EXISTS #TableNamesSorted
SELECT
name,
RowNum = ROW_NUMBER() OVER(ORDER BY name)
INTO #TableNamesSorted
FROM
SYSOBJECTS
WHERE
xtype = 'U'
DECLARE #i INT = 0;
DECLARE #currentTableName varchar(25);
WHILE #i < (SELECT COUNT(*) FROM #TableNamesSorted)
BEGIN
SET #i = #i + 1
SET #currentTableName = (SELECT name from #TableNamesSorted WHERE RowNum = #i)
SELECT * FROM (SELECT #currentTableName)
END
I absolutely abhor loops when they are not needed and this is one of those times. You can easily generate a string with a select top 10 from each table. Please notice this will also handle schemas which if you have more than one schema your solution would fail. I also included the name of the table as the first column so you know what table you are looking at sample data for. It is this simple, no temp tables, no loops.
declare #sql nvarchar(max) = ''
select #sql += 'select top 10 TableName = ''' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ''', * from ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ';'
from sys.tables t
join sys.schemas s on s.schema_id = t.schema_id
exec sp_executesql #sql
Although there are likely better means to go about this (thank you Dai and SMor),
I got it to work using this:
IF OBJECT_ID('tempdb.#TableNamesSorted') IS NULL DROP TABLE #TableNamesSorted
SELECT
name,
RowNum = ROW_NUMBER() OVER(ORDER BY name)
INTO #TableNamesSorted
FROM
SYSOBJECTS
WHERE
xtype = 'U'
DECLARE #i INT = 0;
DECLARE #currentTableName varchar(25);
DECLARE #SQL nvarchar(1000)
DECLARE #TableCount INT = (SELECT COUNT(*) FROM #TableNamesSorted)
WHILE #i <= #TableCount
BEGIN TRY
SET #i = #i + 1
SET #currentTableName = (SELECT name from #TableNamesSorted WHERE RowNum = #i)
SET #SQL = CONCAT('SELECT TOP 10 * FROM ', #currentTableName)
EXEC (#SQL)
END TRY
BEGIN CATCH
Print 'Errors on ' + #currentTableName
END CATCH;
I need to select all unique values from all columns in a table.
I have tried to implement the query below which I found in the thread How to get unique values from all columns of a table in SQL Server.
declare #Sql_Str varchar(8000)='';
select #Sql_Str=#Sql_Str+' select cast (' +name +' as varchar(500))
from <yourtable> union'
from sys.columns
where [object_id]=object_id('<yourtable>');
set #Sql_Str=SUBSTRING(#Sql_Str,1,len(#Sql_Str)-6);
exec(#Sql_Str)
I cannot get that query to work however. My table has 118 columns. I think that may be more data than the query above may handle.
Try something like this:
DECLARE #Schema VARCHAR(500)='dbo';
DECLARE #tableName VARCHAR(500)='SomeTable';
DECLARE #cmd NVARCHAR(MAX)=
(
SELECT STUFF(
(
SELECT ' UNION ALL SELECT ''' + c.TABLE_SCHEMA + ''' AS TableSchema '
+ ',''' + c.TABLE_NAME + ''' AS TableName '
+ ',''' + c.COLUMN_NAME + ''' AS ColumnName '
+ ',''' + c.DATA_TYPE + ''' AS ColumnType '
+ ',CAST(' + QUOTENAME(c.COLUMN_NAME)+' AS NVARCHAR(MAX)) AS Value '
+ ' FROM ' + QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME)
+ ' WHERE ' + QUOTENAME(c.COLUMN_NAME) + ' IS NOT NULL '
+ ' GROUP BY ' + QUOTENAME(c.COLUMN_NAME) + ' '
FROM INFORMATION_SCHEMA.COLUMNS AS c
WHERE TABLE_NAME=#TableName
AND TABLE_SCHEMA=#Schema
--exclude not supported types
--AND c.DATA_TYPE NOT IN('xml') --add more types
FOR XML PATH(''),TYPE
).value('.','nvarchar(max)'),1,10,'')
);
--PRINT #cmd
EXEC(#cmd);
This statement will first create a long list of UNION ALL SELECT with GROUP BY (better than DISTINCT) as dynamically created SQL and executes this with EXEC().
You can decomment PRINT to examine the statement created.
This should work in tSQL:
declare #table_name varchar(55)
set #table_name= 'IV00101' ---- <-- Change this to your table name
create table #colcount (
colname varchar(55),
dct int,
tot int
)
create table #colContent (
colname varchar(55),
col_val nvarchar(max),
col_val_count int
)
create table #sqlexecs( s varchar(max))
declare #col_name varchar(max), #sql nvarchar(max), #sql2 nvarchar(max)
declare c cursor for
select name from sys.columns where [object_id]=object_id(#table_name)
open c
fetch next from c into #col_name
while ##FETCH_STATUS = 0
begin
set #sql = 'select cn.name, count(distinct '+#col_name+') as dct_numrow, count('+#col_name+') as tot_numrow from '+#table_name+' join (select name from sys.columns where name = '''+#col_name+''' and [object_id]=object_id('''+#table_name+''')) cn on cn.name = '''+#col_name+''' group by cn.name'
set #sql2 = 'select ' +#col_name+', count('+#col_name+') as colvalcnt from '+#table_name+' group by '+#col_name
--insert into #sqlexecs values (#sql) --uncomment to view sql selects produced by #sql
--insert into #sqlexecs values (#sql2) --uncomment to view sql selects produced by #sql2
insert into #colcount execute sp_executesql #sql
------
declare #d int, #t int
set #d = (select dct from #colcount where colname = #col_name)
set #t = (select tot from #colcount where colname = #col_name)
if (#d <> #t)
begin
insert into #colContent (colname) values (#col_name)
insert into #colContent (col_val,col_val_count) execute sp_executesql #sql2
end
else
begin
insert into #colContent values (#col_name,1,1)
end
fetch next from c into #col_name
end
close c
deallocate c
--select * from #sqlexecs -- uncomment to view sql code produced by #sql and #sql2
select * from #colcount --order by dct desc
select * from #colContent
drop table #colcount
drop table #colContent
drop table #sqlexecs
The first table shows column name, distinct value count, and total value count.
The second table shows column name, distinct values, and the number of times a distinct value appears. If values in column are all distinct (column is a candidate key), colname | 1 | 1 is shown. This should work if copy/pasted, please let me know it doesn't. Dev for use in Dynamics GP.
I am looking for a more appropriate way to execute several inserts into a nonexistent table.
To create the table beforehand is not easily possible, as I don't know the data type of the selected column.
An "insert with create" would do, but I don't think there is anything like that.
Is there any better way to do so than to select into and then to insert?
Here is the "bad" way I do it, in an example very much stripped down to demonstrate the problem.
set nocount on
declare
#name sysname = '',
#i int = 0,
#sql nvarchar(4000) = ''
declare test cursor for
select top 10 a.name from sys.tables a inner join sys.columns b on a.object_id = b.object_id --and b.name = 'description'
open test
fetch next from test into #name
while (##FETCH_STATUS <> -1)
begin
if #i = 0 begin
set #sql = 'select distinct top 10 description into #t1 from ' + #name + ''
select #sql
-- exec sp_executesql #sql
end
else begin
set #sql = 'insert #t1 select distinct top 10 description into #t1 from ' + #name + ''
select #sql
-- exec sp_executesql #sql
end
set #i = #i + 1
fetch next from test into #name
end
close test
deallocate test
if object_id ('tempdb..#t1') is not null select * from #t1
This solution is "bad" as you need the statement at two positions. In the case shown here this is trivial, but when the statement gets more complex this can become an issue.
You can simplify your query into this one:
set nocount on
declare
#name sysname = '',
#i int = 0,
#sql nvarchar(4000) = N''
if object_id ('tempdb..#t1') is not null DROP TABLE #t1
;WITH cte AS (
select top 10 a.[name]
from sys.tables a
inner join sys.columns b
on a.object_id = b.object_id --and b.name = 'description'
)
SELECT #sql = #sql + N'UNION ALL
select distinct top 10 description
from ' + QUOTENAME([name]) + CHAR(13)
FROM cte
SELECT #sql = N';WITH cte AS (' + STUFF(#sql,1,10,') SELECT * INTO #t1 FROM cte')
PRINT #sql
--EXEC (#sql)
select * from #t1
No cursor or while loop;
Temporary table is dropped (if exists) before query execution;
You got a weird query, as for now it takes the first table from sys.tables and SELECT TOP 10 Descriptions from this table as many times as there are columns in this table.
The SELECT INTO statement copies data from one table into a new table, this might help you.
Example:-
SELECT *
INTO newtable
FROM oldtable
WHERE condition
The above also supports joins.
Does anyone know how to check a a variable against all database table with columns storing the same type of information? I have a poorly designed database that stores ssn in over 60 tables within one database. some of the variations of columns in the various tables include:
app_ssn
ca_ssn
cand_ssn
crl_ssn
cu_ssn
emtaddr_ssn
re_ssn
sfcart_ssn
sfordr_ssn
socsecno
ssn
Ssn
SSN
I want to create a stored procedure that will accept a value and check it against every table that has 'ssn' in the name.Does anyone have idea as to how to do this?
-- I assume that table/column names don't need to be surrounded by square braces. You may want to save matches in a table - I just select them. I also assume ssn is a char.
alter proc proc1
#search1 varchar(500)
as
begin
set nocount on
declare #strsql varchar(500)
declare #curtable sysname
declare #prevtable sysname
declare #column sysname
select top 1 #curtable= table_schema+'.'+table_name, #column=column_name
from INFORMATION_SCHEMA.COLUMNS
where CHARINDEX('ssn',column_name) > 0
order by table_schema+'.'+table_name +column_name
-- make sure that at least one column has ssn in the column name
if #curtable is not null
begin
while (1=1)
begin
set #strsql = 'select * from ' +#curtable +' where '+''''+#search1+''''+ ' = '+#column
print #strsql
-- any matches for passed in ssn will match here...
exec (#strsql)
set #prevtable = #curtable+#column
select top 1 #curtable= table_schema+'.'+table_name, #column=column_name
from INFORMATION_SCHEMA.COLUMNS
where CHARINDEX('ssn',column_name) > 0
and table_schema+'.'+table_name +column_name> #prevtable
order by table_schema+'.'+table_name +column_name
-- when we run out of columns that contain ssn we are done...
if ##ROWCOUNT = 0
break
end
end
end
What you will need to do is some research. But here is where you can start;
SELECT tbl.NAME AS TableName
,cl.NAME AS ColumnName
,IDENTITY(INT, 1, 1) AS ID
INTO #ColumnsToLoop
FROM sys.tables tbl
JOIN sys.columns cl ON cl.object_id = tbl.object_id
This will give you the table / column relation then you can simply build a dynamic SQL string based on each row in the query above (basically loop it) and use EXEC or sp_execsql. So basically;
DECLARE #Loop int = (select min(ID) From #ColumnsToLoop),#MX int = (Select MAX(ID) From #ColumnsToLoop)
WHILE(#Loop<=#MX)
BEGIN
DECLARE #SQL nvarchar(MAX) = 'SQL String'
//Construct the dynamic SQL String
EXEC(#SQL);
SET #Loop += 1
END
Perhaps I went a little too crazy with this one, but let me know. I thought it would best the primary key of the search results with the table name so you could join it to your tables. I also managed to do it without a single cursor or loop.
DECLARE #SSN VARCHAR(25) = '%99%',
#SQL VARCHAR(MAX);
WITH CTE_PrimaryKeys
AS
(
SELECT TABLE_CATALOG,
TABLE_SCHEMA,
TABLE_NAME,
column_name
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE D
WHERE OBJECTPROPERTY(OBJECT_ID(constraint_name), 'IsPrimaryKey') = 1
),
CTE_Columns
AS
(
SELECT A.*,
CONCAT(A.TABLE_CATALOG,'.',A.TABLE_SCHEMA,'.',A.TABLE_NAME) AS FullTableName,
CASE WHEN B.COLUMN_NAME IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey
FROM INFORMATION_SCHEMA.COLUMNS A
LEFT JOIN CTE_PrimaryKeys B
ON A.TABLE_CATALOG = B.TABLE_CATALOG
AND A.TABLE_SCHEMA = B.TABLE_SCHEMA
AND A.TABLE_NAME = B.TABLE_NAME
AND A.COLUMN_NAME = B.COLUMN_NAME
),
CTE_Select
AS
(
SELECT
'SELECT ' +
--This returns the pk_col casted as Varchar and the table name in another columns
STUFF((SELECT ',CAST(' + COLUMN_NAME + ' AS VARCHAR(MAX)) AS pk_col,''' + B.TABLE_NAME + ''' AS Table_Name'
FROM CTE_Columns B
WHERE A.Table_Name = B.TABLE_NAME
AND B.IsPrimaryKey = 1
FOR XML PATH ('')),1,1,'')
+ ' FROM ' + fullTableName +
--This is where I list the columns where LIKE desired SSN
' WHERE ' +
STUFF((SELECT COLUMN_NAME + ' LIKE ''' + #SSN + ''' OR '
FROM CTE_Columns B
WHERE A.Table_Name = B.TABLE_NAME
--This is where I filter so I only get desired columns
AND (
--Uncomment the Collate if your database is case sensitive
COLUMN_NAME /*COLLATE SQL_Latin1_General_CP1_CI_AS*/ LIKE '%ssn%'
--list your column Names that don't have ssn in them
--OR COLUMN_NAME IN ('col1','col2')
)
FOR XML PATH ('')),1,0,'') AS Selects
FROM CTE_Columns A
GROUP BY A.FullTableName,A.TABLE_NAME
)
--Unioning them all together and getting rid of last trailing "OR "
SELECT #SQL = COALESCE(#sql,'') + SUBSTRING(selects,1,LEN(selects) - 3) + ' UNION ALL ' + CHAR(13) --new line for easier debugging
FROM CTE_Select
WHERE selects IS NOT NULL
--Look at your code
SELECT SUBSTRING(#sql,1,LEN(#sql) - 11)