Using loop with UNION t-sql - sql-server

I have a need to query several tables, with similar data, but I don't want to write a lengthy UNION statement. Also, the number of tables can vary. Currently I accomplish this using Coldfusion by:
looping over a list of tablenames
performing the UNION inside the loop
Like this
set i = 0
set table_suffixes = "blue,green,red,yellow"
loop list="table_suffixes" index="idx_suffix"
set tName = "table_" & idx_suffix
if i > 0
UNION
end if
select *
FROM tName
end loop
Can this be done in T-SQL? I have not been able to find any articles about using a list to control a loop. The closest I could find would be to place a query in the loop. Like This
While (SELECT table_suffix FROM vendor_name WHERE discipline = 'electrical')
BEGIN
*union query...*
END
Any help to point me in the right direction is much appreciated. I am using SQL-Server 2008

Although generally nor recommended, here is an example of generating and executing dynamic SQL within T-SQL.
Note I have used UNION ALL. You should understand the differences between this and UNION
Once you have this working you could wrap it in a a stored procedure that accepts a parameter to use for DISCIPLINE
Also please note, the fact you have to do this implies that you have some serious design flaws in your database.
DECLARE #TableName VARCHAR(100)
DECLARE #DSQL VARCHAR(4000)
SET #DSQL = ''
DECLARE cTableList CURSOR FOR
SELECT table_suffix FROM vendor_name WHERE discipline = 'electrical'
OPEN cTableList
FETCH NEXT FROM cTableList INTO #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #DSQL = #DSQL + 'SELECT * FROM ' + #TableName + ' UNION ALL '
END
CLOSE cTableList
DEALLOCATE cTableList
-- Remove the last UNION ALL
IF LEN(#DSQL) > 11 SET #DSQL = LEFT(#DSQL,LEN(#DSQL) - 11)
-- Print it out for debugging purposes
PRINT (#DSQL)
-- Execute it
EXEC (#DSQL)

Try this one -
Query:
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL' + CHAR(13) + 'SELECT * FROM [' + s.name + '].[' + o.name + ']'
FROM sys.objects o
JOIN sys.schemas s ON o.[schema_id] = s.[schema_id]
WHERE o.[type] = 'U'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '')
PRINT #SQL
Output:
SELECT * FROM [dbo].[test1]
UNION ALL
SELECT * FROM [dbo].[MyTable]
UNION ALL
...
In your case see something like this -
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = STUFF((
SELECT CHAR(13) + 'UNION ALL' + CHAR(13) + 'SELECT * FROM ' + table_suffix
FROM vendor_name
WHERE discipline = 'electrical'
WHERE o.[type] = 'U'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 11, '')
PRINT #SQL

Related

Is there a way to select last N columns in SQL Server Results Tab?

Shortly,
I don't mean the last 5 rows. via SELECT TOP 5 * FROM Table Order By Key DESC
But I mean last 5 columns in Results Tab.
Why? because each time I add a new column and filling it outside SQL-Server, I need to see its result without moving Horizontal Scroll. and many columns have the same name at the beginning.
It's not coding issues. but it's about the SQL-Server IDE Results tab itself.
I Searched many times. but this thing never asked I think. so please I
want an approach for that. I assume something like built-in function
or something anyone knows
Probably a dynamic statement, based on system catalog views, may help here:
DECLARE #tablename sysname = 'table'
DECLARE #schemaname sysname = 'dbo'
DECLARE #stm nvarchar(max)
SELECT #stm = (
SELECT TOP(5) CONCAT(#stm, N',', col.[name])
FROM sys.columns col
JOIN sys.tables tab ON col.object_id = tab.object_id
JOIN sys.schemas sch ON tab.schema_id = sch.schema_id
WHERE
tab.[name] = #tablename AND
sch.[name] = #schemaname
ORDER BY col.column_id DESC
FOR XML PATH('')
)
SELECT #stm = CONCAT(
N'SELECT ',
STUFF(#stm, 1, 1, N''),
N' FROM ',
QUOTENAME(#schemaname),
N'.',
QUOTENAME(#tablename)
)
PRINT #stm
EXEC sp_executesql #stm
Like I mentioned in the comments, if your table's definition is constantly changing this suggests a far larger design flaw in your database. Object definitions should be pretty static, and they should definitely not be changing every time you connect to the instance. That is the root cause of your problem, not that it's "too difficult", to type the name of 5 columns, rather than using *.
This means fixing your design, which we can't comment on, but I suggest that is your next major step to do. Normalise your design, and use multiple tables (as I suspect you're adding extra columns each time due to a lack of normalisation).
In the interim, you can use dynamic SQL:
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE #Schema sysname = N'dbo',
#Table sysname = N'rCTE_Vs_Tally';
SET #SQL = N'SELECT ' +
STUFF((SELECT TOP(5)
N',' + #CRLF +
N' ' + QUOTENAME(c.[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 s.[name] = #Schema
AND t.[name] = #Table
ORDER BY C.column_id DESC
FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,10,'') + #CRLF +
N'FROM ' + QUOTENAME(#Schema) + N'.' + QUOTENAME(#Table) + #CRLF +
N'ORDER BY [Key] DESC;' --Assumes all tables have the column Key
PRINT #SQL;
EXEC sp_executesql #SQL;
Ok Deveton, as explained, a Dynamic SQL is needed for this.
I think the below would answer your question:
declare #columns varchar(max)
, #sql varchar(max)
, #tblname varchar(20) = 'Settings'
select #columns = stuff((select top 5 ',' + quotename(column_name)
from information_schema.columns
where table_name = #tblname
order by ordinal_position desc
for xml path(''), type).value('.', 'nvarchar(max)'),1,1,'')
set #sql = 'select '+#columns+' from '+quotename(#tblname)
exec (#sql)
Please advise if it works for you?
EDIT
I also added the functionality to specify top 200 and order by the Identity column descending:
declare #columns varchar(max)
, #sql varchar(max)
, #tblname varchar(20) = '_btblInvoiceLines'
declare #idcolumn varchar(max) = quotename((select column_name from information_schema.columns where table_name = #tblname and COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1))
select #columns = stuff((select top 5 ',' + quotename(column_name)
from information_schema.columns
where table_name = #tblname
order by ordinal_position desc
for xml path(''), type).value('.', 'nvarchar(max)'),1,1,'')
set #sql = 'select top 200'+#columns+' from '+quotename(#tblname)+' order by '+#idcolumn+' desc'
print(#sql)
exec (#sql)
Just press "End" key when you are in the Results tab.
After that you will be at end of columns.

Which is faster trigger or stored procedure?

I currently have a trigger on each table that handles a history log. The trigger is the exact same on every table. See below.
If I move this to a stored procedure, will it be faster?
Also if I use a stored procedure will the trigger release for the user to continue?
create trigger ' + #TABLE_NAME + '_ChangeTracking on ' + #TABLE_NAME + ' for
insert, update, delete
as
declare #bit int ,
#field int ,
#maxfield int ,
#char int ,
#fieldname varchar(128) ,
#TableName varchar(128) ,
#PKCols varchar(1000) ,
#sql nvarchar(max),
#Type nvarchar(1) ,
#PKValueSelect varchar(1000),
#MasterId nvarchar(max) = ''0''
select #TableName = ''' + #TABLE_NAME + '''
if exists(select * from CNF_HIL_Tables where referencetable = #TableName and Active = 1)
begin
if exists (select * from inserted)
if exists (select * from deleted)
select #Type = ''2''
else
select #Type = ''3''
else
select #Type = ''1''
select * into #ins from inserted
select * into #del from deleted
select #PKCols = coalesce(#PKCols + '' and'', '' on'') + '' i.'' + c.COLUMN_NAME + '' = d.'' + c.COLUMN_NAME
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE c on c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME and c.TABLE_NAME = pk.TABLE_NAME
where pk.TABLE_NAME = #TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
select #PKValueSelect = coalesce(#PKValueSelect+''+'','''') + ''convert(varchar(100), coalesce(i.'' + COLUMN_NAME + '',d.'' + COLUMN_NAME + ''))''
from INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk
inner join INFORMATION_SCHEMA.KEY_COLUMN_USAGE c on c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME and c.TABLE_NAME = pk.TABLE_NAME
where pk.TABLE_NAME = #TableName
and CONSTRAINT_TYPE = ''PRIMARY KEY''
select #field = 0,
#maxfield = max(ORDINAL_POSITION)
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TableName
while #field < #maxfield
begin
select #field = min(ORDINAL_POSITION)
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TableName
and ORDINAL_POSITION > #field
select #bit = (#field - 1 )% 8 + 1
select #bit = power(2,#bit - 1)
select #char = ((#field - 1) / 8) + 1
if substring(COLUMNS_UPDATED(),#char, 1) & #bit > 0 or #Type in (''1'',''3'')
begin
select #fieldname = COLUMN_NAME from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = #TableName and ORDINAL_POSITION = #field
if exists(select * from CNF_Hil_Columns INNER JOIN CNF_HIL_Tables ON CNF_HIL_Tables.TablesId = CNF_Hil_Columns.TablesId
where CNF_HIL_Tables.referencetable = #TableName and CNF_Hil_Columns.ColumnName = #fieldname
and CNF_Hil_Columns.Active = 1
)
begin
if #MasterId = 0
begin
select #sql = ''insert DATA_HIL_Master (OperationType, ReferenceTable, ReferenceId, UserId, WorkstationId, InsDateTime)''
select #sql = #sql + '' select '''''' + #Type + ''''''''
select #sql = #sql + '', '''''' + #TableName + ''''''''
select #sql = #sql + '','' + #PKValueSelect
select #sql = #sql + '',convert(varchar(1000),i.Last_UserId_Log)''
select #sql = #sql + '',convert(varchar(1000),i.Last_WorkstationId_Log)''
select #sql = #sql + '',convert(varchar(1000),i.Last_DateTime_Log)''
select #sql = #sql + '' from #ins i full outer join #del d''
select #sql = #sql + #PKCols
select #sql = #sql + '' SELECT #MasterId = SCOPE_IDENTITY() ''
EXECUTE sp_executesql #sql, N''#MasterId nvarchar(max) OUTPUT'', #MasterId OUTPUT
end
select #sql = ''insert data_HIL_Detail (MasterId, ColumnName, OriginalValue, ModifiedValue)''
select #sql = #sql + '' select convert(varchar(1000),'' + #MasterId + '')''
select #sql = #sql + '','''''' + #fieldname + ''''''''
select #sql = #sql + '', convert(varchar(1000),d.'' + #fieldname + '')''
select #sql = #sql + '', convert(varchar(1000),i.'' + #fieldname + '')''
select #sql = #sql + '' from #ins i full outer join #del d''
select #sql = #sql + #PKCols
EXECUTE sp_executesql #sql
END
END
END
END
I was actually searching for an answer to this question and stumbled in here.
I've found a lot of different answers, but as a student, I am currently being told that "stored procedures run quicker than individual SQL statements; this improves performance." So, it seems the answer is "yes".
However, it seems "performance" may be interpreted differently by different people. I'm not very experienced yet, so I don't really understand all the nuances yet. I've seen some comments attributing the difference to "cache", and others that suggest using stored procedure only because of better "control" for security and maintenance rather than anything performance related.
While reading my course material, I also came across something that might be relevant. This is from Beginning Databases with PostgreSQL: From Novice to Professional (Stones and Matthew, 2005):
Stored Procedures reside on the server side, not on the client side, adding to
access control. Invoked from the client side, only the results are passed on to the caller, this reduces network traffic. Several applications can use a single stored procedure, standardizing processing rules.
So, maybe that's what is meant by "performance".
Stored procedures also seem more similar to functions themselves, which are objects stored in a database and used by all other database objects. Whereas triggers are object associated with a table that runs a function.
Generally, Irrespective of trigger or stored procedure, you have got the same code. In trigger, you can not call it directly and in stored procedure, you are calling directly. So, whether you use trigger or stored procedure, execution wise it is same. The first time it is called, the execution plan is cached.
In your case, as you are using specifically using inserted, deleted tables, you should have different stored procedure code to implement auditing. Or you can consider using SQL Server temporal tables or Change Data Capture or SQL Server auditing
But, there are few disadvantages of using trigger.
It can make the transaction longer
Difficult to debug

How to Select tables names to this query

I want to select phone numbers from all tables in my databese and names of these tables too. I write a query that shows me all phone_numbers but I dont't know how to select table name to each phone number. This is my query:
DECLARE #SQL AS VarChar(MAX)
SET #SQL = ''
SELECT #SQL = #SQL + 'SELECT phone_number FROM ' + TABLE_SCHEMA + '.[' + TABLE_NAME + ']' + CHAR(13)
FROM INFORMATION_SCHEMA.TABLES where table_name in (select table_name
from information_schema.columns
where column_name = 'phone_number'
)
You can simply add the table name as a constant to the SELECT clause. But, I presume you're going to want to run this query, which means you have a few more things to change:
You're probably going to want sp_executesql, which requires a Unicode variable. So, you need to DECLARE #SQL NVARCHAR(MAX).
Do you want one result set or multiple result sets? I'm guessing you want all the results in one result set, which means you're going to want to use UNION ALL between the parts of the query.
So, try something like this:
DECLARE #sql NVARCHAR(MAX)
SET #sql = N'SELECT '''' AS table_name, '''' AS phone_number FROM [dbo].[SomeTable] WHERE 1 = 0'
DECLARE #table_name SYSNAME
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT table_name
FROM INFORMATION_SCHEMA.columns
WHERE column_name = 'phone_number'
OPEN cur
FETCH NEXT FROM cur INTO #table_name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = #sql + N' UNION ALL SELECT ''' + #table_name + N''', phone_number FROM [' + #table_name + N']'
FETCH NEXT FROM cur INTO #table_name
END
CLOSE cur
DEALLOCATE cur
EXEC sp_executesql #sql
When I used [dbo].[SomeTable], just use some table that you know exists. You would also need to modify the query if you want fully-qualified table names, but the above should get you started.
Another solution without CURSOR. You could combine each query with UNION like this.
--SELECT DISTINCT phone_number FROM dbo.Course c
DECLARE #sql nvarchar(max) = ''
SELECT #sql = #sql + N' SELECT DISTINCT phone_number, '''+ s.name + '.' + t.name + ''' AS TableName
FROM '+ s.name + '.' + t.name + Char(13) + ' UNION' + char(13)
FROM sys.tables t
INNER JOIN sys.schemas s ON t.schema_id = s.schema_id
INNER JOIN sys.columns c ON c.object_id = t.object_id
WHERE c.name ='phone_number'
IF(#sql != '')
BEGIN
SET #sql = LEFT(#sql,len(#sql) - 6) -- remove last UNION
PRINT #sql
-- execute sql
EXEC sp_executesql #sql
END
I also add column TableName: Table of phone_number
If you want TableName if the first column then change it
SELECT #sql = #sql + N' SELECT DISTINCT '''+ s.name + '.' + t.name + ''' AS TableName, phone_number
FROM '+ s.name + '.' + t.name + Char(13) + ' UNION' + char(13)

Select any column with respect to cell containing

I have a table named a. Some cells containing a string 'Empty' in many columns. I want to find this columns. Can you help me?.
Try this dynamic query, it will check all the columns with character data and list the columns which has the word 'Empty'.
DECLARE #SearchText VARCHAR(50) = 'Empty'
DECLARE #sql NVARCHAR(MAX) = 'SELECT '
SELECT #sql = #sql + 'MAX(CASE WHEN ' + c.COLUMN_NAME + ' LIKE ''%'+ #SearchText +'%'' THEN ''' + c.COLUMN_NAME +''' ELSE '''' END) + '','' + '
FROM INFORMATION_SCHEMA.COLUMNS c WHERE c.TABLE_SCHEMA = 'dbo' and c.TABLE_NAME = 'a'
AND c.DATA_TYPE IN ('varchar','char','nvarchar','nchar','sysname')
SET #sql = #sql + ''''' FROM dbo.a'
EXEC sys.sp_executesql #sql
Hope this helps
Use the LIKE operator:
SELECT a.*
FROM a
WHERE a.col1 LIKE '%Empty%' OR a.col2 LIKE '%Empty%' OR ...
In sql server you can get object id of table then using that object id you can fetch columns. In that case it will be as below:
Step 1: First get Object Id of table
select * from sys.tables order by name
Step 2: Now get columns of your table and search in it:
select * from a where 'Empty' in (select name from sys.columns where object_id =1977058079)
Note: object_id is what you get fetch in first step for you relevant table
You can do it using unpivot with an help of dynamic query , here i have done below an working sample for you , there might be some modification you might have to do to put the below psedo code with your working .
Sample table structure been used :
create table ColTest
(
name1 varchar(10),
name2 varchar(10),
name3 varchar(10),
name4 varchar(10)
)
insert into ColTest values ('sdas','asdasda','ewrewr','erefds')
insert into ColTest values ('sdas','asdasda','EMPTY','erefds')
insert into ColTest values ('EMPTY','asdasda','ewrewr','erefds')
DECLARE #table_name SYSNAME
SELECT #table_name = 'ColTest'
DECLARE #tmpTable SYSNAME
SELECT #tmpTable = 'ColTest2'
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = '
SELECT * into
' + #tmpTable + '
FROM ' + #table_name + '
UNPIVOT (
cell_value FOR column_name IN (
' + STUFF((
SELECT ', [' + c.name + ']'
FROM sys.columns c WITH(NOLOCK)
LEFT JOIN (
SELECT i.[object_id], i.column_id
FROM sys.index_columns i WITH(NOLOCK)
WHERE i.index_id = 1
) i ON c.[object_id] = i.[object_id] AND c.column_id = i.column_id
WHERE c.[object_id] = OBJECT_ID(#table_name)
AND i.[object_id] IS NULL
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, '') + '
)
) unpiv'
PRINT #SQL
EXEC sys.sp_executesql #SQL
select * from ColTest2 where cell_value = 'EMPTY'
I'd suggest dynamic SQL
--First you set the variable #TableName to your actual table's name.
DECLARE #TableName VARCHAR(100)='a';
--The following statement will create a list of all columns with a data type containing the word "char" (others should not hold the value Empty)
DECLARE #ColList VARCHAR(MAX)=
STUFF(
(
SELECT ' OR ' + COLUMN_NAME + ' LIKE ''%empty%'''
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#TableName AND DATA_TYPE LIKE '%char%'
FOR XML PATH('')
),1,4,'');
--This statement builds a command
DECLARE #cmd VARCHAR(MAX)=
(
SELECT 'SELECT * FROM [' + #TableName + '] WHERE ' + #ColList
);
--Here you can see the command
PRINT #cmd;
--And here it is executed
EXEC(#cmd);

Does anybody know of a proc to turn a row into an INSERT statement?

Does anybody know of a proc or script which will generate any row into an insert statement into the same table?
Basically, I'd like to call something like
exec RowToInsertStatement 'dbo.user', 45;
And the following code would be output
insert into dbo.MyTable( FirstName, LastName, Position)
values( 'John', 'MacIntyre', 'Software Consultant');
I realize I could
insert into dbo.MyTable
select * from dbo.MyTable where id=45;
But this obviously won't work, because the ID column will complain (I hope it complains) and there's no way to just override that one column without listing all columns, and in some tables there could be hundreds.
So, does anybody know of a proc that will write this simple insert for me?
EDIT 3:04: The purpose of this is so I can make a copy of the row, so after the INSERT is generated, I can modify it into something like
insert into dbo.MyTable( FirstName, LastName, Position)
values( 'Dave', 'Smith', 'Software Consultant');
.. no obviously this contrived example is so simple it doesn't make sense, but if you have a table with 60 columns, and all you need is to change 3 or 4 values, then it starts to be a hassle.
Does that make sense?
Update
I believe the following dynamic query is what you want:
declare #tableName varchar(100), #id int, #columns varchar(max), #pk varchar(20)
set #tableName = 'MyTable'
set #pk = 'id'
set #id = 45
set #columns = stuff((select ',['+c.name+']' [text()] from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
print 'insert into [' + #tableName + '] (' + #columns + ')
select ' + #columns + '
from [' + #tableName + ']
where ' + #pk + ' = ' + cast(#id as varchar)
Update 2
The actual thing that you wanted:
declare #tableName varchar(100), #id int, #columns nvarchar(max), #pk nvarchar(20), #columnValues nvarchar(max)
set #tableName = 'MyTable'
set #pk = 'id'
set #id = 45
set #columns = stuff((select ',['+c.name+']' [text()] from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
set #columnValues = 'set #actualColumnValues = (select' +
stuff((select ','','''''' + cast(['+c.name+'] as varchar(max)) + '''''''' [text()]' [text()]
from sys.tables t
join sys.columns c on t.object_id = c.object_id
where t.name = #tableName and c.name <> #pk for xml path('')),1,1,'')
+ 'from [' + #tableName + ']
where ' + #pk + ' = ' + cast(#id as varchar)
+ 'for xml path(''''))'
--select #columnValues
declare #actualColumnValues nvarchar(max), #columnValuesParams nvarchar(500)
SET #columnValuesParams = N'#actualColumnValues nvarchar(max) OUTPUT';
EXECUTE sp_executesql #columnValues, #columnValuesParams, #actualColumnValues OUTPUT;
--SELECT stuff(#actualColumnValues, 1,1, '')
declare #statement nvarchar(max)
set #statement =
'insert into [' + #tableName + '] (' + #columns + ')
select ' + stuff(#actualColumnValues,1,1,'')
print #statement
What it does is this:
It generates the insert statement and then it queries the actual data from the table and generates the select statement with that data. May not work correctly for some really complex datatypes but for varchars, datetimes and ints should work like a charm.
This stored proc works great for me:
http://vyaskn.tripod.com/code.htm#inserts
Did you know that in Enterprise Manager and SQL Server Management Studio that you can, from the object browser, drag the list of columns into the text window and it will drop the names of all the columns into the text, separated by commas?

Resources