I need to generate a dynamic sql in below specified format where my table is a parameter i.e, Number of columns is not static
For example, below may be the table schema
ID Name
1 asd
2 xyz
I need a query which generates the select statement as below
select 'ID :' + ID + ',Name :'+Name from table
The output from generated above sql will be like this
ID : 1, Name:asd
ID : 2, Name:xyz
If the table has more number of columns, select statement that needs to be changes varies as below
select 'ID :' + ID + ',Name :'+Name + ',Col3 :' + Col3 ...from table
Could someone help me regarding this
Thanks,
Sree
Here is one option which uses a little XML and string manipulation
I should add, NULL values will be excluded.
Example
Declare #YourTable Table ([ID] varchar(50),[Name] varchar(50))
Insert Into #YourTable Values
(1,'asd')
,(2,'xyz')
Select stuff(
replace(
replace(
replace(
replace(
(Select * from #YourTable for XML RAW)
,'<row ',',')
,'="',':')
,'" ',',')
,'"/>','')
,1,1,'')
Returns
(No column name)
ID:1,Name:asd,ID:2,Name:xyz
Use the information schema views. They contain all the information you need to generate your dynamic sql. The rest is just simple SQL and patience.
I am able to achieve this using below sql
DECLARE #TableName VARCHAR(MAX) = 'tableName'
DECLARE #SQL VARCHAR(MAX) = 'SELECT ''{''+'''
SELECT #SQL = #SQL + '
"'+COLUMN_NAME+'":"''' + '+coalesce(CAST('+COLUMN_NAME+' AS VARCHAR(MAX)),'''')+''",' FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = #TableName
SET #SQL = LEFT(#SQL,LEN(#SQL)-1) + '
}'' FROM ' + #TableName
PRINT #SQL
Thanks,
Sree
Related
We have a View in SQL Server that constantly evolving.
We want to show it in a report as it is (If we add/remove a field in the View, we don't have to modify the report and add/remove manuelly the field).
A sort of a table/matrix that is refreshing by itself.
Thank you by advance for your help.
Without using a table or matrix? Why not? You can't really do this without using one of these controls...
Ignoring that for a moment though, the problem you will face is that the dataset query has to always return the same structure every time it is run so you can't point it directly at a query that is constantly changing.
The only way you might be able to do this is to write a query that unpivots your table/view into another structure and then report on that. By using a matrix, you could reconstruct the table in the report.
There are drawbacks to this approach. All value data needs to be cast to a constant datatype so if each row has a mix of text and numeric values, they would all have to be converted to text.
This approach also assumes there is a key column on the table/view.
Below is a simple example of the kind of thing I mean. This is based on the sample 'AdventureWorksDW2016' database in case you want to test it.
DECLARE #Schema sysname = 'dbo' -- Schema where table/view resides
DECLARE #Table sysname = 'DimGeography' -- name of table or view to read from
DECLARE #KeyColumn sysname = 'GeographyKey' -- name of keycolumn, assumed to be INT in this exmaple
SELECT
COLUMN_NAME, ORDINAL_POSITION, DATA_TYPE
into #t
FROM INFORMATION_SCHEMA.COLUMNS t
WHERE TABLE_SCHEMA = #Schema AND TABLE_NAME = #Table
AND COLUMN_NAME != #KeyColumn
DECLARE #OrdPos int
DECLARE #ColName sysname
DECLARE #sql varchar(max) = ''
CREATE TABLE #result (KeyID int, ColumnName sysname, ColumnPosition int, ColumnValue varchar(75)) -- <= Update 75 to suit maximum column length
WHILE EXISTS(SELECT * FROM #t)
BEGIN
SELECT TOP 1 #OrdPos = ORDINAL_POSITION, #ColName = COLUMN_NAME FROM #t
SET #SQL = 'INSERT INTO #result SELECT ' + #KeyColumn + ', ''' + #ColName + ''', ' + CAST(#OrdPos as varchar(10)) + ', CAST(' + #ColName + ' as varchar(200)) FROM ' + QUOTENAME(#Schema) + '.' + QUOTENAME(#Table)
EXEC (#sql)
DELETE FROM #t WHERE ORDINAL_POSITION = #OrdPos
END
SELECT * FROM #result
If we take a look at the results (just for 2 keyid vales for simplicity) we can see we have a consistent structure.
SELECT * FROM #result where keyid in (207,208) order by KeyID, ColumnPosition
Now, you can build a simple report using a Matrix, have a row group that groups by KeyID and have a column group that groups by ColumnName. The column group sorting can be set to ColumnPosition and the matrix 'data' cell set to ColumnValue.
This whole process will effectively recreate the table/view and be dynamic.
Good afternoon,
I have a working query, where I loop through all my databases, and filter down to check which report is being used where. This works (see below)
I have this working query:
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp
(
ReportPath VARCHAR(500)
)
declare #SQL nvarchar(max)
set #SQL = STUFF((SELECT '
UNION ALL
' + 'SELECT path FROM ' + quotename(name) + '.dbo.ReportConfig where path like ''%/Standard Reports/Booking/Booked Out by Location%'' and Active = 1'
from sys.Databases
WHERE name LIKE 'SFB-%'
FOR XML PATH(''), type).value('.','varchar(max)'),1,15,'')
INSERT #temp
execute(#SQL)
SELECT ReportPath FROM #temp
And this is giving me the following output:
So I know that out of my 90 databases, the report is being used 6 times, but I don't know where.
So I want to include the database name of where this report is being used.
I googled around and tried a bunch of things, but I can't get it to work.
Any ideas?
Just a small tweak to what you already have will get you there
SET NOCOUNT ON;
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
--Add a column to your temp table
CREATE TABLE #temp
(
DatabaseName varchar(100)
,ReportPath VARCHAR(500)
)
declare #SQL nvarchar(max)
--adjust your dynamic query and add the [name] column as shown below
set #SQL = STUFF((SELECT '
UNION ALL
' + 'SELECT ''' + [name] + ''' as DatabaseName,path FROM ' + quotename(name) + '.dbo.ReportConfig where path like ''%/Standard Reports/Booking/Booked Out by Location%'' and Active = 1'
from sys.Databases
WHERE name LIKE 'SFB-%'
FOR XML PATH(''), type).value('.','varchar(max)'),1,15,'')
INSERT #temp
execute(#SQL)
SELECT DatabaseName, ReportPath FROM #temp
I'm at crossroads. Can somebody please help me... send me down the right path.
I want to compare / present data from 2 database tables as follows:
Application Database: Many tables have triggers that copy update/delete changes (auditing) to another database.
Audit Database: The information copied from the triggers in the application database
What I want to do should be fairly straightforward. Visually below, is what I want to do to compare data for what changes were made.
I have a working version with CROSS APPLY and UNIONS (it's long and manually typed out for the columns, tables, etc. sucks). The columns are NOT dynamic which makes hundreds of lines of code gross and unmanageable. There has to be a more elegant design. Please any ideas.
I only need to return ONE specific row (ID) from both tables, for comparison.
APP DB
colA colB colC colD
1 hello foo date
APP Audit DB
colA colB colC colD
1 hi bar date
THIS IS WHAT IS WISH TO OUTPUT:
colA_data ColumnName oldData newData
1 colB hi hello
1 colC bar foo
1 colD date date
I hope I have made sense of what I want to accomplish.
I would like to read column names dynamic (not hard), and then put the results side by side like about for reporting reasons. Obviously matching the columns and putting them into rows.
Sample code would be so much appreciated.
Probably the easiest thing to do is using UNPIVOT:
1. Static version
Just to introduce the use of UNPIVOT here is a simple static version that should solve your problem:
declare #appDB table( [colA] int, [colB] nvarchar(50),[colC] nvarchar(50),[colD] nvarchar(50))
declare #auditDB table( [colA] int, [colB] nvarchar(50),[colC] nvarchar(50),[colD] nvarchar(50))
insert into #appDB select 1,'hello', 'foo', 'date'
insert into #auditDB select 1,'hi', 'bar', 'date'
select old.ColA_data, old.ColumnName, old.OldData, new.NewData
from(
select o.colA as ColA_data, o.ColumnName, o.OldData
from #auditDB s
unpivot ([OldData] for [ColumnName] in ([colB], [colC], [colD])) o
) OLD
inner join
(
select n.colA as ColA_data, n.ColumnName, n.NewData
from #appDB t
unpivot ([NewData] for [ColumnName] in ([colB], [colC], [colD])) n
) NEW
on new.ColA_data = old.ColA_data and new.ColumnName = old.ColumnName
Results:
2. Dynamic version
Now the complete version. You can use dynamic SQL to change the columns retrieving them from SQL Server INFORMATION_SCHEMA metadata.
Please note that in this example I added a new column (ColE)
if OBJECT_ID('appDB') is not null drop table appDB
if OBJECT_ID('auditDB') is not null drop table auditDB
create table appDB (colA int, colB nvarchar(50),colC nvarchar(50),colD nvarchar(50),colE nvarchar(50))
create table auditDB(colA int, colB nvarchar(50),colC nvarchar(50),colD nvarchar(50),colE nvarchar(50))
insert into appDB select 1,'hello', 'foo', 'date','time'
insert into auditDB select 1,'hi', 'bar', 'date','time'
declare #cols nvarchar(max)='' --this variable holds all the dates that will become column names
declare #sql nvarchar(max)='' --this variable contains the TSQL dinamically generated
select #cols = #cols + ', [' +COLUMN_NAME + ']'
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME='appDB'
and COLUMN_NAME <>'colA'
set #cols = RIGHT(#cols, len(#cols)-2)
set #sql= #sql + ' select old.ColA_data, old.ColumnName, old.OldData, new.NewData '
set #sql= #sql + ' from('
set #sql= #sql + ' select o.colA as ColA_data, o.ColumnName, o.OldData'
set #sql= #sql + ' from auditDB s '
set #sql= #sql + ' unpivot ([OldData] for [ColumnName] in ('+#cols+')) o'
set #sql= #sql + ' ) OLD'
set #sql= #sql + ' inner join'
set #sql= #sql + ' ('
set #sql= #sql + ' select n.colA as ColA_data, n.ColumnName, n.NewData'
set #sql= #sql + ' from appDB t '
set #sql= #sql + ' unpivot ([NewData] for [ColumnName] in ('+#cols+')) n'
set #sql= #sql + ' ) NEW'
set #sql= #sql + ' on new.ColA_data = old.ColA_data and new.ColumnName = old.ColumnName'
exec(#sql)
Results:
Running Microsoft SQL Server 11.0.3128
on Windows Server 2012 R2 Essentials
I am attempting to return the name of a specific database based on a supplied variable (batch file that calls SQL script).
The process, in my head, should look something like this:
For each database in instance
Look in the current database
Return databasename if variable is found in column
The code I've been working with so far looks like this:
EXEC dbo.sp_MSForeachdb '
USE [?];
SELECT DB_NAME() AS DBName
UNION SELECT
ColumnName
FROM dbo.Items
WHERE ColumnName =''variable''
'
Problem is, this returns a lot more than I want it to since it returns "null" values for the databases that do not contain "variable" and creates messages for databases not containing "ColumnName".
But I can't seem to figure out how to get the specific info I want without the other stuff. First time poster, please let me know if I can improve the question.
Thanks!
EDIT: Oops, didn't realize at first you were working with mssql and not mysql. The principle below will still work; you'll just need to adjust the syntax a bit and use a user-function to replace group_concat since mssql doesn't have that.
Here's an approach without sp_MSForeachdb. Note that you will want to sanitize the parameters first.
delimiter $$
create procedure FindDatabases
(
in varName varchar(2000),
in tableName varchar(2000),
in columnName varchar(2000)
)
begin
declare selectQuery varchar(2000);
select group_concat(
concat('select ''',
table_schema,
''' as DatabaseName from ',
table_schema,
'.',
tableName,
' where ',
columnName,
' = ''',
varName,
'''')
separator ' union ') as DatabaseNames
from information_schema.tables
where table_name = tableName
into #selectQuery;
prepare preparedSql from #selectQuery;
execute preparedSql;
deallocate prepare preparedSql;
end $$
delimiter ;
Example usage:
call FindDatabases ( 'variable', 'Items', 'ColumnName' )
This procedure generates a sql query for each database with a table name matching the table name supplied, unions them together, and then executes them. Each query in the union returns its database name if the specified table in that database has a column matching the specified name that contains a value that matches the specified variable name. Only databases matching these requirements will be present in the query results, so you don't have to worry about null values in the results.
ADDITIONAL EDIT: As promised, here is a sqlserver version.
create procedure FindDatabases
(
#varName varchar(2000),
#tableName varchar(2000),
#columnName varchar(2000)
)
as
begin
declare #selectQuery nvarchar(2000)
-- first, get a list of database names that contain the specified table
IF OBJECT_ID('tempdb.dbo.#db_temp') IS NOT NULL
DROP TABLE #db_temp
CREATE TABLE #db_temp (DatabaseName SYSNAME)
SELECT #selectQuery = (
SELECT '
USE [' + d.name + '];
INSERT INTO #db_temp (DatabaseName)
SELECT DB_NAME() as DatabaseName
WHERE EXISTS(
SELECT 1
FROM sys.objects
WHERE [object_id] = OBJECT_ID(''' + #tableName + ''')
AND [type] = ''U''
)'
FROM sys.databases d
WHERE d.name NOT IN ('master', 'tempdb', 'model', 'msdb')
AND d.state_desc != 'OFFLINE'
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
EXEC sys.sp_executesql #selectQuery
-- use something like mysql's group_concat function to turn that list into a bunch of union all select statements
select
#selectQuery =
(
SELECT LEFT(database_names , LEN(database_names ) - 10) AS database_names
FROM #db_temp AS extern
CROSS APPLY
(
SELECT 'select ''' + DatabaseName + ''' as DatabaseName from ' + DatabaseName + '.dbo.' + #tableName +
' where ' + #columnName + ' = ''' + #varName + '''' + ' union all '
FROM #db_temp AS intern
FOR XML PATH('')
) pre_trimmed (database_names)
GROUP BY database_names
)
drop table #db_temp
-- run those select statements
exec sp_executesql #selectQuery
end
To run it:
exec FindDatabases 'someVar', 'Items', 'ColumnName'
I shamelessly pulled some snippets from here and here to work around the lack of a group_concat function and sqlserver's information_schema having only the local database's info and not sharing information across databases.
Is there a script out there that will let MSSQL find columns with records that have the same data in multiple tables.
What I want to do is find the primary keys to data tables that we imported from excel spread sheets that were made from another database.
Thanks,
Chris
You're going to want to look up the SysObjects and SysColumn system tables, very handy for this sort of thing.
Here's an example that looks through all tables for the integer value 500. Note that if you want to look for a different type of column you'll need to change the xtype. It's not a full blown "Compare every column in my database against every other column" example however it should give you the basic idea and hopefully get you started.
Additionally I'm using a memory table for this example. If your database is large you will want to use a temporary table and a cursor likely.
This returns a single column recordset with the value of "Table - ColumnName = Search Value"
-- declare my search table
DECLARE #Columns TABLE (TableName varchar(50), ColumnName varchar(50))
DECLARE #Results TABLE (Results VARCHAR(255))
DECLARE #SearchData INT
SET #SearchData = 500
DECLARE #TableName VARCHAR(50)
DECLARE #ColumnName VARCHAR(50)
DECLARE #Command VARCHAR(1024)
-- Find all tables with an integer column
Insert INTO #Columns
Select sysobjects.[Name] as TableName, syscolumns.[Name] as ColumnName
from dbo.sysobjects INNER Join dbo.syscolumns ON dbo.sysobjects.id = dbo.syscolumns.id
Where sysobjects.xtype = 'U' and syscolumns.xtype = 56 Order By TableName, ColumnName
--Loop!
WHILE NOT (Select TOP 1 TableName from #Columns) IS NULL
BEGIN
Select TOP 1 #TableName = TableName, #ColumnName = ColumnName from #Columns
SET #Command = 'Select ''' + #TableName + ' - ' + #ColumnName + ' = ' + CAST(#SearchData as varchar(32)) + ''' FROM ' + #TableName + ' WHERE ' + #ColumnName + ' = ' + CAST(#SearchData as VARCHAR(32))
Insert INTO #Results
exec(#Command)
Delete from #Columns where TableName = #TableName AND ColumnName = #ColumnName
END
-- Export all results
Select * from #Results