How do I use loop to generate column names dynamically? - sql

I have table sdata and it has 35 columns (id, name, TRx1, TRx2, TRx3, TRx4,..., TRx30, city, score, total)
I want to fetch data from the TRx1,...TRx30 columns.
Can I use loop here?
I did following code:
DECLARE #flag INT
DECLARE #sel varchar(255)
DECLARE #frm varchar(255)
SET #flag = 1;
SET #sel = 'select TRx';
SET #frm = ' from sdata';
exec(#sel +
(WHILE #flag <=5
#flag
SET #flag = #flag + 1)
+ #frm)
What wrong am I doing? And how can I resolve this?

If your table name is sdata, this code should work for you:
-- Grab the names of all the remaining columns
DECLARE #sql nvarchar(MAX);
DECLARE #columns nvarchar(MAX);
SELECT #columns = STUFF ( ( SELECT N'], [' + name
FROM sys.columns
WHERE object_id = (select top 1 object_id FROM sys.objects where name = 'sdata')
AND name LIKE 'TRx%' -- To limit which columns
ORDER BY column_id
FOR XML PATH('')), 1, 2, '') + ']';
PRINT #columns
SELECT #sql = 'SELECT ' + #columns + ' FROM sdata';
PRINT #sql;
EXEC (#sql);
Note I included PRINT statements so you could see what's going on. You might want to comment out the EXEC while testing.

This would be much easier to do by just copy/pasting the column names and changing them to be the correct one. However if you must do it this way, I do not advise using a loop at all. This method uses a tally table to generate the columns you want to select (in this example, columns 1 through 30, but that can be changed), then generates a dynamic SQL statement to execute against the SData table:
Declare #From Int = 1,
#To Int = 30,
#Sql NVarchar (Max)
Declare #Columns Table (Col Varchar (255))
;With Nums As
(
Select *
From (Values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) As V(N)
), Tally As
(
Select Row_Number() Over (Order By (Select Null)) As N
From Nums A --10
Cross Join Nums B --100
Cross Join Nums C --1000
)
Insert #Columns
Select 'TRx' + Cast(N As Varchar)
From Tally
Where N Between #From And #To
;With Cols As
(
Select (
Select QuoteName(Col) + ',' As [text()]
From #Columns
For Xml Path ('')
) As Cols
)
Select #Sql = 'Select ' + Left(Cols, Len(Cols) - 1) + ' From SData'
From Cols
--Select #Sql
Execute (#Sql)
Note: The --Select #Sql section is there to preview the generated query before executing it.

You can select the column names like this:
SELECT column_name
FROM information_schema.columns
WHERE table_name = 'my name here'

Related

Iterate through temporary table columns to select them

I have a final temporary table (#tempTable) with unknown columns number.
My final select is like this, it works :
SELECT temp.* FROM #tempTable temp
But instead of a '*' I would like to call each columns individually :
SELECT temp.col1, temp.col2 FROM #tempTable temp
To do so I need to iterate through my columns names and create a procedure, I tried something like this :
DECLARE #ColName VARCHAR(255)
SELECT #ColName = min(name) FROM tempdb.sys.columns
WHERE object_id = Object_id('tempdb..#TEMPTABLE');
WHILE #ColName is not null
BEGIN
-- i need to do it all in once and not each time....
declare #sql varchar(max) = 'SELECT tp.'+'#COlName'+'FROM #TEMPTABLE tp'
exec(#sql)
-- Increment the value, how to go to next column ?
select #ColName = min(name) FROM tempdb.sys.columns WHERE object_id =
Object_id('tempdb..#TEMPTABLE') > #ColName -- does not work because it is a string (column name)
END
Try this:
DECLARE #ColName VARCHAR(2000) = 'select '
SELECT #ColName = #ColName + ' temp.' + name + ',' FROM tempdb.sys.columns
WHERE object_id = Object_id('tempdb..#TEMPTABLE')
--delete last character, which is comma and append table name
#ColName = substring(#ColName, 1, LEN(#ColName) - 1) + ' from #TEMPTABLE temp'
exec(#ColName)
This query construct whole table list combined in select ... from ... statement. I increased size of the varchar variable, so it can accomodate long queries.
Also, IMO variable name such as #sql or #query would be more meaningful.
A set based approach
IF OBJECT_ID('tempdb..#TEMPTABLE','U') IS NOT NULL
DROP TABLE #TEMPTABLE;
CREATE TABLE #TEMPTABLE (
Id INT IDENTITY(1,1)
,Col1 INT
,Col2 BIGINT
,Col3 BIGINT
,Col4 DATETIME
,Col5 DATETIME
) ;
DECLARE #SQL NVARCHAR(MAX)
SELECT #SQL = N'SELECT ' + SUBSTRING((
SELECT N', temp.' + S.name
FROM
tempdb.sys.columns S
WHERE
S.object_id = OBJECT_ID('tempdb..#TEMPTABLE')
ORDER BY
S.column_id
FOR XML PATH('')
)
,2
,200000
) + N' FROM #TEMPTABLE temp'
EXEC sys.sp_executesql #SQL

How to pass table name as parameter in select statement in SQL Sever

Table:
Col
------
Table1
table2
table3
Query:
select count(*)
from #tablename
I wanted to pass table1, table2, table3 as parameters for #tablename in the select query and get the count for each table
Desired output:
2 (table 1 count) 3 (table 2 count) 4 (table 3 count)
you can use dynamic sql and a cursor to run through them:
Create temp table for testing:
DECLARE #tablenametable TABLE(tablename VARCHAR(100));
INSERT INTO #tablenametable
VALUES('table1'), ('table2'), ('table3');
Use a cursor to run through all tablenames in the table
DECLARE #tablename VARCHAR(100);
DECLARE dbcursor CURSOR
FOR
SELECT tablename
FROM #tablenametable;
OPEN dbcursor;
FETCH NEXT FROM dbcursor INTO #tablename;
WHILE ##FETCH_STATUS = 0
BEGIN
DECLARE #sql VARCHAR(MAX);
SET #sql = 'select count(*) from '+#tablename;
PRINT(#sql);
FETCH NEXT FROM dbcursor INTO #tablename;
END;
CLOSE dbcursor;
DEALLOCATE dbcursor;
Give the following results:
select count(*) from table1
select count(*) from table2
select count(*) from table3
Just change PRINT(#SQL) to EXEC(#SQL) when your happy with it
You can use dynamic sql query.
Query
declare #sql as varchar(max);
select #sql = stuff((
select ' union all '
+ 'select cast(count(*) as varchar(100))
+ ' + char(39) + '(' + [Col] +' Count)' + char(39)
+ ' as [table_counts] '
+ ' from ' + [col]
from [your_table_name]
for xml path('')
)
, 1, 11, ''
);
exec(#sql);
Find a demo here
As mentioned here, you have to use dynamic SQL.
First approach is where you specify table name yourself:
declare #tablename varchar(30), #SQL varchar(30)
set #tablename = 'Table1' --here you specify the name
set #SQL = concat('SELECT COUNT(*) FROM ', #tablename) --here you build the query
EXEC(#SQL)
Second approach lets you use table with names of tables:
declare #SQL varchar(8000)
set #SQL = ''
declare #TableNames table(name varchar(30))
insert into #TableNames values ('Table1'), ('Table2'), ('Table3')
--here you build the query
select #SQL = #SQL + ' SELECT ''' + name + ''' AS [TableName], COUNT(*) AS [Count] FROM ' + name + ' UNION ALL' from #TableNames
-- get rid of last "UNION ALL"
set #SQL = LEFT(#SQL, LEN(#SQL) - 10)
--execute the query
EXEC(#SQL)
The result of it will be:
TableName Count
Table1 3
Table2 6
Table3 4
You can use sys.dm_db_partition_stats like this [1]:
select
t.col tableName, sum(s.row_count) tableCount
from
yourTable t
join
sys.dm_db_partition_stats s
on
(object_name(s.object_id) = t.col )
and
(s.index_id < 2)
group by
t.col;
[1]. Related answer
One line output version will be:
select
sum(s.row_count), ' ('+t.col +' count) '
from
yourTable t
join
sys.dm_db_partition_stats s
on
(object_name(s.object_id) = t.col )
and
(s.index_id < 2)
group by
t.col
for xml path('');
output:
2 (Table1 count) 3 (table2 count) 4 (table3 count)

SQL Server : Duplicate row in table while changing some values with select *

I need to insert an almost duplicated row into table, while changing few values.
For example insert duplicated row with new id (I don't want automatic id) and different name but all other values the same.
The problem is that I need to make a select *
I know that there is a way to insert from select and changing values this way :
insert into Table1(id,name,surname) select newid(),'David',surname from Table1 where id=1
but I don't want to enlist all fields ,instead I want to use select *, so if fields added I won't have to change my stored procedure.
I want something like :
insert into Table1 (
update (SELECT *
FROM Table1
WHERE id= 1 ) t
set t.id= newid(),name='David')
Is there a way to do it ?
You can use temp hash table to accomplish this.
SELECT *
INTO #temp
FROM Table1
WHERE id= 1;
UPDATE #temp
SET ID = newid(),
Name='David'
INSERT INTO Table1 SELECT * FROM #temp;
Note that the #temp table is automatically dropped when the client disconnect from the DB server.
Also, as previously noted, I prefer to use column names separately instead of *.
Example: SQL Fiddle
The code I use:
declare #table sysname
declare #excludecols nvarchar(max)
declare #uniqueWhereToCopy nvarchar(max)
declare #valuesToChange nvarchar(max)
--copy settings
set #table = 'orsrg' --the tablename
set #excludecols='' --columnnames to exclude from the copy, seperated by commas
set #uniqueWhereToCopy = 'ID=1188'
set #valuesToChange = 'regel='' 4''' --columnName=<value>,columnName2=<value2>, .... (needed for unique indexes)
set #excludecols=#excludecols + ','
set #valuesToChange=#valuesToChange + ','
--get the columnnames to copy
declare #sqlcolumns nvarchar(max)
set #sqlcolumns = ''
SELECT #sqlcolumns = #sqlcolumns + name from
(select '[' + c.name + '], ' as name FROM sys.COLUMNS c inner join sys.objects o
on c.object_id = o.object_id
WHERE o.name = #table
and is_identity = 0 /*exclude identity*/
and is_rowguidcol = 0 /*exclude rowguids*/
and is_computed = 0 /*exclude computed columns*/
and system_type_id <> 189 /*exclude timestamp*/
and charindex(c.name, #excludecols,1) = 0 /*exclude user specified columns*/)q
--get the select columns and values
declare #sqlselectvalues nvarchar(max)
set #sqlselectvalues = #sqlcolumns
while len(#valuesToChange)>1
begin
declare #colValueSet nvarchar(max)
declare #colname sysname
declare #value nvarchar(max)
set #colValueSet = left(#valuesToChange,charindex(',',#valuesToChange,1)-1)
set #valuesToChange = substring(#valuesToChange,charindex(',',#valuesToChange,1)+1,len(#valuesToChange))
set #colname = '[' + left(#colValueSet,charindex('=',#colValueSet,1)-1) +']'
set #value = substring(#colValueSet,charindex('=',#colValueSet,1)+1,len(#colValueSet))
set #sqlselectvalues = REPLACE(#sqlselectvalues,#colname,#value)
end
--remove the last comma
set #sqlcolumns = left(#sqlcolumns, len(#sqlcolumns)-1)
set #sqlselectvalues = left(#sqlselectvalues, len(#sqlselectvalues)-1)
--create the statement
declare #stmt nvarchar(max)
set #stmt = 'Insert into ' + #table + '(' + #sqlcolumns + ') select ' + #sqlselectvalues + ' from ' + #table + ' with (nolock) where ' + #uniqueWhereToCopy
--copy the row
exec sp_executesql #stmt
No, because a SELECT * will always contain the id column.
Generally, you should avoid SELECT * anywhere except when querying interactively. When the stored procedure is compiled, the query text will be parsed and replaced with the correct columns, rendering your stored procedure invalid on every change to the structure anyway.

Get top three most common values from every column in a table

I'm trying to write a query that will produce a very small sample of data from each column of a table, in which the sample is made up of the top 3 most common values. This particular problem is part of a bigger task, which is to write scripts that can characterize a database and its tables, its data integrity, and also quickly survey common values in the table on a per-column basis. Think of this as an automated "analysis" of a table.
On a single column basis, I do this already by simply calculating the frequency of values and then sorting by frequency. If I had a column called "color" and all colors were in it, and it just so happened that the color "blue" was in most rows, then the top 1 most frequently occurring value would be "blue". In SQL that is easy to calculate.
However, I'm not sure how I would do this over multiple columns.
Currently, when I do a calculation over all columns of a table, I perform the following type of query:
USE database;
DECLARE #t nvarchar(max)
SET #t = N'SELECT '
SELECT #t = #t + 'count(DISTINCT CAST(' + c.name + ' as varchar(max))) "' + c.name + '",'
FROM sys.columns c
WHERE c.object_id = object_id('table');
SET #t = SUBSTRING(#t, 1, LEN(#t) - 1) + ' FROM table;'
EXEC sp_executesql #t
However, its not entirely clear to me how I would do that here.
(Sidenote:columns that are of type text, ntext, and image, since those would cause errors while counting distinct values, but i'm less concerned about solving that)
But the problem of getting top three most frequent values per column has got me absolutely stumped.
Ideally, I'd like to end up with something like this:
Col1 Col2 Col3 Col4 Col5
---------------------------------------------------------------------
1,2,3 red,blue,green 29,17,0 c,d,j nevada,california,utah
I hacked this together, but it seems to work:
I cant help but think I should be using RANK().
USE <DB>;
DECLARE #query nvarchar(max)
DECLARE #column nvarchar(max)
DECLARE #table nvarchar(max)
DECLARE #i INT = 1
DECLARE #maxi INT = 10
DECLARE #target NVARCHAR(MAX) = <table>
declare #stage TABLE (i int IDENTITY(1,1), col nvarchar(max), tbl nvarchar(max))
declare #results table (ColumnName nvarchar(max), ColumnValue nvarchar(max), ColumnCount int, TableName NVARCHAR(MAX))
insert into #stage
select c.name, o.name
from sys.columns c
join sys.objects o on o.object_id=c.object_id and o.type = 'u'
and c.system_type_id IN (select system_type_id from sys.types where [name] not in ('text','ntext','image'))
and o.name like #target
SET #maxi = (select max(i) from #stage)
while #i <= #maxi
BEGIN
set #column = (select col from #stage where i = #i)
set #table = (select tbl from #stage where i = #i)
SET #query = N'SELECT ' +''''+#column+''''+' , '+ #column
SELECT #query = #query + ', COUNT( ' + #column + ' ) as count' + #column + ' , ''' + #table + ''' as tablename'
select #query = #query + ' from ' + #table + ' group by ' + #column
--Select #query
insert into #results
EXEC sp_executesql #query
SET #i = #i + 1
END
select * from #results
; with cte as (
select *, ROW_NUMBER() over (partition by Columnname order by ColumnCount desc) as rn from #results
)
select * from cte where rn <=3
Start with this SQL Statement builder, and modify it to suit your liking:
EDIT Added Order by Desc
With ColumnSet As
(
Select TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
From INFORMATION_SCHEMA.COLUMNS
Where 1=1
And TABLE_NAME IN ('Table1')
And COLUMN_NAME IN ('Column1', 'Column2')
)
Select 'Select Top 3 ' + COLUMN_NAME + ', Count (*) NumInstances From ' + TABLE_SCHEMA + '.'+ TABLE_NAME + ' Group By ' + COLUMN_NAME + ' Order by Count (*) Desc'
From ColumnSet

SQL schema and value

I have a select statement I want to make. I want to select
SELECT COLUMN_NAME AS FieldName FROM
INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'table1'
However I want to create another column named Value which is a particular row in table1
so I have rows of the column name and the corresponding single value. Any thoughts on how to approach this?
The following query produces a value (the minimum) for each column:
SELECT '''select '+COLUMN_NAME+''' AS FieldName, (select cast(MIN('+COLUMN_NAME+') as varchar(8000)) from '+const.tablename+')'
FROM INFORMATION_SCHEMA.COLUMNS c cross join
(select 'AllCurveNames' as tablename) const
WHERE c.TABLE_NAME = const.tablename
However, this produces a separate query for each row. To combine them together, you need a string aggregate concatenation. This is how you would do it in SQL Server:
declare #sql varchar(max);
SELECT #sql = (select 'select '''+COLUMN_NAME+''' AS FieldName, (select cast(MIN('+COLUMN_NAME+') as varchar(8000)) from '+const.tablename + ') union all '
FROM INFORMATION_SCHEMA.COLUMNS c cross join
(select WHATEVER as tablename) const
WHERE c.TABLE_NAME = const.tablename
for xml path('')
);
select #sql = LEFT(#sql, len(#sql) - 9);
exec(#sql);
Use a cross join, which is implicit if you just select from two tables with no join (i.e., from t1, t2):
SELECT COLUMN_NAME AS FieldName,
Table1.MyField
FROM
INFORMATION_SCHEMA.COLUMNS, Table1
WHERE
TABLE_NAME = 'table1'
AND
MyTable.ID = 123
I actually came up with a bit of a crazy solution but it works:
declare #tbl_name as varchar(255)
declare #field as varchar(255)
declare #val as varchar(255)
declare #SQL as nvarchar(4000)
create table #tbl ( [FieldName][varchar](255), [FieldVal][varchar](255))
set #tbl_name = 'table1'
DECLARE mah_cursor CURSOR FAST_FORWARD
FOR
SELECT COLUMN_NAME FROM
INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tbl_name
OPEN mah_cursor
FETCH NEXT FROM mah_cursor INTO #field
WHILE ##FETCH_STATUS = 0
BEGIN
set #SQL = 'set #val = (Select top 1 ' + #field + ' from ' + #tbl_name + ')'
print #SQL
exec sp_executesql #query = #SQL, #params = N'#val varchar(255) OUTPUT', #val = #val OUTPUT
insert into #tbl ([FieldName],[FieldVal] ) values (#field, #val)
FETCH NEXT FROM mah_cursor INTO #field
END
CLOSE mah_cursor
DEALLOCATE mah_cursor
select * from #tbl
drop table #tbl
It loops through each value and adds it. The Fast_Forward feature optimizes the query for high performance