I am trying to build a query using dynamic sql which looks like following,
'select dense_rank () over(partition by column1 order by column1),
dense_rank () over(partition by column1,column2 order by column2),
dense_rank () over(partition by column1,column2,column3 order column3) from tablename'
I have used STUFF to build the query but able to create query only with one columns and could not repeat columns i.e.'partition by column1, column2'.
How to achieve this using STUFF or is there other way to do it ?
You can do it with two queries that concatenates string. One outer that builds the column list and one inner that builds the column list for the partition by clause.
Sample table:
create table T(C1 int, C2 int, C3 int);
Code:
declare #SQL nvarchar(max);
with C as
(
select C.name,
-- Use order by to control order of columns
row_number() over(order by C.column_id) as rn
from sys.columns as C
where object_id = object_id('T') -- Specify the name of the table
-- Optionally filter out any columns that
-- should not be included
)
select #SQL = 'select '+
stuff((
select ',dense_rank() over(partition by '+
stuff((
select ','+c2.name
from C as C2
where C2.rn <= C.rn
order by C2.rn
for xml path(''), type
).value('text()[1]', 'nvarchar(max)'), 1, 1, '') +
' order by '+C.name+')'
from C
for xml path(''), type
).value('text()[1]', 'nvarchar(max)'), 1, 1,'')+
' from T';
print #SQL;
--exec (#SQL);
Result:
select dense_rank() over(partition by C3 order by C3),
dense_rank() over(partition by C3,C2 order by C2),
dense_rank() over(partition by C3,C2,C1 order by C1)
from T
Note: STUFF (Transact-SQL) does not concatenate strings. It is used to insert one string into another string. In the code above, stuff is used to remove the leading comma in the concatenated string by inserting an empty string to the first position and overwriting 1 character. The actual concatenation is done by for xml.
Declare #col_name varchar(100)
Declare #colnm varchar(max)
Declare #multicol int
Declare #sqlstr varchar(max)
Declare Cur_1 cursor
for
Select name
from sys.columns
where object_id=object_id('tablename')
order by name
OPEN Cur_1
FETCH NEXT FROM Cur_1
INTO #col_name
set #colnm=''
set #multicol=0
WHILE ##FETCH_STATUS = 0
BEGIN
if #multicol=1
begin
set #colnm=#colnm + ',' + #col_name
end
else
begin
set #colnm= #colnm+ #col_name
end
if #multicol=0
begin
set #sqlstr='Select dense_rank () over(partition by ' + #colnm + ' order by ' + #colnm + '),'
end
else
begin
set #sqlstr=#sqlstr + char(13) + char(10) + 'dense_rank () over(partition by ' + #colnm + ' order by ' + #colnm + '),'
end
FETCH NEXT FROM Cur_1
INTO #col_name
set #multicol=1
END
CLOSE Cur_1;
DEALLOCATE Cur_1;
set #sqlstr=left(#sqlstr,len(#sqlstr)-1) + + char(13) + char(10) + 'from tablename'
print #sqlstr
Declare #QueryString Varchar(Max)
Declare #TblNm Varchar(100)
Set #TblNm='[dbo].[SampleTable]'
Set #QueryString=
(
Select ',dense_rank () over(partition by '+ ColNm + ' Order by [' + name + '])' + char(10) AS [text()]
From
(
SELECT name,ColNm = STUFF(
(
SELECT ',[' + name + ']'
FROM sys.columns
where object_id=object_id(#TblNm) and column_id<=Tbl1.column_id
FOR XML PATH ('')
), 1, 1, ''
)
FROM sys.columns Tbl1
where object_id=object_id(#TblNm)
) ColDtl
For XML PATH ('')
)
Set #QueryString='Select '
+ substring(#QueryString,2,len(#QueryString))
+ ' from ' + #TblNm
Select #QueryString as ExecString
--Exec (#QueryString)
Related
Well I'm Creating a Table Based on Result Set Of Databases On Server which contain Multiple Databases.
My table will Create a list of column which contain Database_Name
FOR EG:
DECLARE #strt INT,#End INT,#Database NVARCHAR(20), #ColumnDeclaration VARCHAR(2000),#SqlSelect NVARCHAR(MAX),#column_Name NVARCHAR(255)
SELECT * INTO #T FROM SYS.DATABASES
ORDER BY NAME
SELECT #ColumnDeclaration=STUFF(( SELECT ', ' + Name + ' NVARCHAR(255)'
FROM #T
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)'), 1, 1, '')
SET #SqlSelect=' CREATE TABLE Temp_Comp (' + #ColumnDeclaration + ');'
PRINT #SqlSelect
EXEC (#SqlSelect)
SELECT * FROM Temp_Comp
I wan't Insert the Data INTO Temp_Comp Table a specific Value from
the database whose Name will be in Temp_comp Table
Not sure what are you trying to achieve, but I assume that you want something like:
DECLARE #strt INT,#End INT,#Database NVARCHAR(20), #ColumnDeclaration NVARCHAR(MAX),#SqlSelect NVARCHAR(MAX),#column_Name NVARCHAR(255)
SELECT * INTO #T
FROM SYS.DATABASES
ORDER BY NAME;
SELECT #ColumnDeclaration=STUFF(( SELECT ', ' + QUOTENAME(Name) + ' NVARCHAR(255)'
FROM #T
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(max)'), 1, 1, '');
SET #SqlSelect=' CREATE TABLE Temp_Comp (col_desc NVARCHAR(200), ' + #ColumnDeclaration + ');';
SELECT #SqlSelect;
EXEC (#SqlSelect);
SET #SQLSelect = ' INSERT INTO Temp_Comp(col_desc, ' + REPLACE(#ColumnDeclaration, 'NVARCHAR(255)', '') + ')' +
' SELECT col,'+ REPLACE(#ColumnDeclaration, 'NVARCHAR(255)', '') +' FROM (' +
' SELECT name, sub.* FROM sys.databases' +
' OUTER APPLY (SELECT ''database_id'', CAST(database_id AS NVARCHAR(MAX)) UNION ALL SELECT ''compatibility_level'', CAST(compatibility_level AS NVARCHAR(MAX)) ) sub(col, val)) src' +
' PIVOT (MAX(val) FOR name IN ('+ REPLACE(#ColumnDeclaration, 'NVARCHAR(255)', '') +') ) piv';
SELECT #SQLSelect;
EXEC (#SQLSelect);
DBFiddle
Result:
In order to INSERT you could use dynamic PIVOT. To add more values just extend OUTER APPLY part.
Keep in mind that you've defined columns as ' NVARCHAR(255)' so you may need to convert values before insert.
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'
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
I have a scenario inside a stored procedure where a temporary table will be generated with an unknown number of columns (Column1.....ColumnN). One of the columns will be the total\sum of few of the other columns.
The clients requirement is to show the percentage value of each column in comparison to the total column
(C1*100)/Total as P1 ,(C2*100)/Total as P2.....
I have really been unable to find a solution to this problem other than doing it in the front end using LINQ. I am wondering if there is any way to achieve this in SQL as that would give me performance benefits.The last thing I want to do is to loop through the rows and columns in C# which will hammer the server.
I had done, I just change according to you and you can read the comment for better understand. I feel the schemaname is dbo, else change it.
-------------1. first step --------------
--create table for exercise
CREATE TABLE [dbo].[tblTest](
[ID] [int] NULL,
[isTrue] [bit] NULL
) ON [PRIMARY]
--insert date
insert into tblTest values(1,'true'),(2,'false'),(3,'false'),(4,'true'),(5,'false')
select * from tbltest
-------------2. second step --------------
--now start to get column name one by one
DECLARE #TableName nvarchar(256) = '[dbo].[tblTest]',
#SearchStr nvarchar(128)='id', #SearchStr2 nvarchar(110) --this is used to get only particular column result, to check remove uncomment in cursor
SET #SearchStr2 = QUOTENAME('%' + #SearchStr + '%','''')
DECLARE #Columnname varchar(100) ,#ColumnIndex int --, #PurchaseQty int -- declare temp variable which you u
CREATE TABLE #Results (ColumnName nvarchar(370), ColumnValue nvarchar(3630), ColIndex int)
DECLARE getItemID CURSOR
FOR
select column_name, ordinal_position from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME = PARSENAME(#TableName, 1)
OPEN getItemID
FETCH NEXT FROM getItemID INTO #Columnname, #ColumnIndex
WHILE ##FETCH_STATUS = 0
BEGIN
--select #Columnname, #ColumnIndex ;
INSERT INTO #Results
EXEC
(
'SELECT ''' + #ColumnName + ''', LEFT(' + #ColumnName + ', 3630) , '+ #ColumnIndex +'
FROM ' + #TableName + ' (NOLOCK) '
--remove this to get only particular column entry
--+' WHERE ' + #ColumnName + ' LIKE ' + #SearchStr2
)
FETCH NEXT FROM getItemID INTO #Columnname, #ColumnIndex
END
CLOSE getItemID
DEALLOCATE getItemID
select * from #Results
drop table #Results
DECLARE #cols AS NVARCHAR(max),
#calCols AS NVARCHAR(max),
#query AS NVARCHAR(max)
SELECT *
INTO #temptable
FROM (SELECT journeyid,
notchl,
Cast(Sum(Datediff(second, starttime, endtime)) AS FLOAT) AS
Duration
FROM (SELECT notchlog.*,
CASE
WHEN ( Isnumeric(notch) = 1
AND notch < 0 ) THEN 'DYN'
WHEN notch = 'I' THEN 'IDLE'
WHEN notch = 'C' THEN 'COASTING'
ELSE 'N' + notch
END AS NotchL
FROM notchlog)Sub1
GROUP BY journeyid,
notchl)SUB1
SELECT #cols = Stuff(( SELECT ',' + Quotename(notchl)
FROM #temptable
GROUP BY notchl
--order by value
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '')
SELECT #calCols = Stuff((SELECT ',' + 'ROUND(' + Quotename(notchl)
+ '*100/RunningTime,2) as '
+ Quotename(notchl)
FROM #temptable
GROUP BY notchl
--order by value
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
, 1, 1, '')
SET #query =N'Select * INTO #ResultTable FROM( SELECT Journeyid, '
+ #cols + ' from ( select Journeyid, NotchL, Duration from #TempTable Group By JourneyId,NotchL,Duration ) x pivot ( max(x.duration) for NotchL in ('
+ #cols
+ ') ) p ) Sub2 select NL.JourneyId,RunningTime,'
+ #calCols
+ N' from #ResultTable R INNER Join (Select JourneyID,Sum(DateDiff(second,starttime,endtime)) as RunningTime FROM NotchLog Group By JourneyID)NL ON NL.JourneyID=R.JourneyId INNER Join Journeys J ON J.JourneysID=R.JourneyID Drop Table #ResultTable '
EXEC Sp_executesql
#query;
DROP TABLE #temptable
So I looked this up and this question is very similar but it's missing a key piece: SQL Server count number of distinct values in each column of a table
So in that question they want the distinct count for each column. What I am looking to do is to get a count of each distinct value for each column in a table (and I'm doing this for all the tables in a particular database which is why I'm looking to try to automate this as much as possible). Currently my code looks like this which I have to run for each column:
select mycol1, COUNT(*) as [Count]
from mytable
group by mycol1
order by [Count] desc
Ideally my output would look like this:
ColumnName1 Count
val1 24457620
val2 17958530
val3 13350
ColumnName2 Count
val1 24457620
val2 17958530
val3 13350
val4 12
and so on for all the columns in the table
This answer below (provided by #beargle) from that previous question is really close to what I'm looking to do but I can't seem to figure out a way to get it to work for what I am trying to do so I would appreciate any help.
DECLARE #Table SYSNAME = 'TableName';
-- REVERSE and STUFF used to remove trailing UNION in string
SELECT REVERSE(STUFF(REVERSE((SELECT 'SELECT ''' + name
+ ''' AS [Column], COUNT(DISTINCT('
+ QUOTENAME(name) + ')) AS [Count] FROM '
+ QUOTENAME(#Table) + ' UNION '
-- get column name from sys.columns
FROM sys.columns
WHERE object_id = Object_id(#Table)
-- concatenate result strings with FOR XML PATH
FOR XML PATH (''))), 1, 7, ';'));
You could use:
DECLARE #Table SYSNAME = 'TableName';
DECLARE #SQL NVARCHAR(MAX) = ''
SELECT #SQL = STUFF((SELECT ' UNION SELECT ''' + name
+ ''' AS [Column], '
+ 'CAST(' + QUOTENAME(Name)
+ ' AS NVARCHAR(MAX)) AS [ColumnValue], COUNT(*) AS [Count] FROM '
+ QUOTENAME(#Table) + ' GROUP BY ' + QUOTENAME(Name)
FROM sys.columns
WHERE object_id = Object_id(#Table)
-- concatenate result strings with FOR XML PATH
FOR XML PATH ('')), 1, 7, '');
EXECUTE sp_executesql #SQL;
Which will produce SQL Like the following for a table with two columns (Column1 and Column2)
SELECT 'Column1' AS [Column],
CAST([Column1] AS NVARCHAR(MAX)) AS [ColumnValue],
COUNT(*) AS [Count]
FROM [TableName]
GROUP BY [Column1]
UNION
SELECT 'Column2' AS [Column],
CAST([Column2] AS NVARCHAR(MAX)) AS [ColumnValue],
COUNT(*) AS [Count]
FROM [TableName]
GROUP BY [Column2]
EDIT
If you want a new result set for each column then use:
DECLARE #Table SYSNAME = 'TableName';
DECLARE #SQL NVARCHAR(MAX) = '';
SELECT #SQL = (SELECT ' SELECT ' + QUOTENAME(Name)
+ ', COUNT(*) AS [Count] FROM '
+ QUOTENAME(#Table) + ' GROUP BY ' + QUOTENAME(Name) + ';'
FROM sys.columns
WHERE object_id = Object_id(#Table)
-- concatenate result strings with FOR XML PATH
FOR XML PATH (''));
EXECUTE sp_executesql #SQL;
Which would produce SQL Like:
SELECT [Column1],
COUNT(*) AS [Count]
FROM [callsupplier]
GROUP BY [Column1];
SELECT [Column2],
COUNT(*) AS [Count]
FROM [callsupplier]
GROUP BY [Column2];
thought i would take a stab at this whilst waiting for a backup to restore
hope this does what you require
create Table #Temp
(tableName varchar(100),
columnName varchar(100),
value varchar(1000),
distinctItems int)
Declare #tabName as varchar(100)
Declare #colName as varchar(100)
Declare #tabid as int
Declare cursorTables Cursor
for
select t.object_id , t.name , c.name from sys.tables t inner join sys.columns c on t.object_id = c.object_id
open cursorTables
Fetch Next from cursorTables into
#tabid,#tabName,#colName
while ##Fetch_Status = 0
Begin
declare #query as nVarchar(1000)
set #query = 'Insert into #Temp SELECT ''' + #tabName + ''' , '''+ #colName +''', ' + #colName + ', COUNT([' + #colName +']) AS Expr1 FROM [' + #tabName+ '] group by [' + #colName + ']'
print #query
exec sp_executesql #query
Fetch Next from cursorTables into
#tabid,#tabName,#colName
End
Close cursorTables
Deallocate cursorTables
select * from #temp
drop table #temp
produces some not very useful results on PK values and i suspect it would not work on columns greater than varchar(1000) but works on a fe of my dbs
This version makes a good snippet:
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += 'SELECT ''' + t.name + ''', ''' + c.name + ''', ' + c.name + ', COUNT(' + c.name + ') AS C FROM ' + QUOTENAME(s.name) + '.' + QUOTENAME(t.name) + ' GROUP BY ' + c.name + ';' + CHAR(13)
FROM sys.tables AS t
INNER join sys.columns c on t.object_id = c.object_id
INNER JOIN sys.schemas AS s ON t.[schema_id] = s.[schema_id]
WHERE s.name LIKE 'stage' AND t.name LIKE 'table' AND c.name LIKE '%whatever%';
--PRINT #sql;
EXEC sp_executesql #sql