SQL Unpivot With Every Column - sql

I have the below query which works well for the 4 columns chosen, but the issue is that I need to select every column of the table (there is about 50 columns). Is there an easier way to do this instead without including each of the 50 columns in the SELECT and IN statements. I also realize that there could be a data type issue. There is only 1 row of data returned.
SELECT g.property,
g.value
FROM (SELECT applicationversion,
ftpservername,
smtpservername,
Cast(numberofservers AS NVARCHAR(max)) AS numberofservers
FROM globals) Person
UNPIVOT (value
FOR property IN (applicationversion,
ftpservername,
smtpservername,
numberofservers)) AS g;

Please try this:
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols1 AS NVARCHAR(MAX)
DECLARE #cols2 AS NVARCHAR(MAX)
SELECT #cols1= ISNULL(#cols1 + ',','') + QUOTENAME(name)
FROM (select c.name from sys.tables t
inner join sys.columns c on c.object_id = t.object_id
where t.name = 'globals'
) cols1
SELECT #cols2= ISNULL(#cols2 + ',cast(','cast(') + QUOTENAME(name) + ' as nvarchar(max))'+ QUOTENAME(name)
FROM (select c.name from sys.tables t
inner join sys.columns c on c.object_id = t.object_id
where t.name = 'globals'
) cols2
SET #sql =
N'SELECT g.property, g.value
FROM (SELECT ' + #cols2 + '
FROM globals) Person
UNPIVOT (value
FOR property IN (' + #cols1 +')) AS g; '
EXEC sp_executesql #sql
It's not beautiful and can certainly be improved, but it should work.

Slight improvement to jpw's version
DECLARE #sql AS NVARCHAR(MAX)
DECLARE #cols1 AS NVARCHAR(MAX)
DECLARE #cols2 AS NVARCHAR(MAX)
SELECT #cols1= ISNULL(#cols1 + ',','') + QUOTENAME(name), #cols2= ISNULL(#cols2 + ',cast(','cast(') + QUOTENAME(name) + ' as nvarchar(max))'+ QUOTENAME(name)
FROM (select c.name from sys.tables t
inner join sys.columns c on c.object_id = t.object_id
where t.name = 'globals'
) cols1
SET #sql =
N'SELECT g.property, g.value
FROM (SELECT ' + #cols2 + '
FROM globals) Person
UNPIVOT (value
FOR property IN (' + #cols1 +')) AS g; '
EXEC sp_executesql #sql

Related

Need all columns on one row

I wrote the following query:
IF OBJECT_ID ('tempdb..#ColumnsType') IS NOT NULL DROP TABLE #ColumnsType
DECLARE #vQuery NVARCHAR(MAX) =''
IF OBJECT_ID ('tempdb..#random') IS NOT NULL DROP TABLE #random
CREATE TABLE #random (
ColumnID INT PRIMARY KEY IDENTITY(1,1) NOT NULL
, randomname VARCHAR(50)
, randomvalue INT)
INSERT INTO #random (randomname, randomvalue)
VALUES ('a3', 123)
, ('bla', 4325)
, ('another_bla', 5643)
, ('end_here', 3)
select *
from #random
CREATE TABLE #ColumnsType (
ColumnID INT PRIMARY KEY IDENTITY(1,1) NOT NULL
, ColumnName sysname
, DataType sysname
)
INSERT INTO #ColumnsType (ColumnName, DataType)
SELECT [name],
system_type_id
FROM Tempdb.Sys.Columns
WHERE Object_ID = Object_ID('tempdb..#random')
AND system_type_id = 56
DECLARE #i INT = (SELECT MIN(ColumnID) FROM #random);
DECLARE #maxId INT = (SELECT MAX(ColumnID) FROM #random);
DECLARE #ColumnName VARCHAR(200);
DECLARE #DataType VARCHAR(200);
WHILE #i <= #maxId
BEGIN
SET #ColumnName = (SELECT ColumnName FROM #ColumnsType WHERE ColumnId = #i)
-- SET #DataType = (SELECT DataType FROM #ColumnsType WHERE ColumnId = #i)
SELECT #vQuery =
'SELECT
MIN(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_MinValue
, MAX(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_MaxValue
, AVG(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName + '_AvgValue
, STDEV(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_StandardDeviation
, SUM(TRY_CONVERT(NUMERIC(30, 4), ' +#ColumnName+ ')) AS ' +#ColumnName+ '_TotalSum
FROM tempdb..#random' -- +#Schema+'.'+#Table+ ''
EXEC sp_executesql #vQuery
PRINT #vQuery
SET #i = #i + 1
END
For the sake of demonstration I create temp table with random values. I perform profiling on part of the columns which are consisting only of numeric values. To filter the columns I get their names and filter by type, using Tempdb.Sys.Columns. In normal case with my original data, I use INFORMATION_SCHEMA.COLUMNS but I think this is not that important.
The query returns the following:
The result is presented on two rows. What I'd like to do is to have this result on one row. The idea is to pivot the one row result after and to receive the following result:
As I mentioned, you need to not use a loop, use a set based method and UNION ALL your dynamic statements. I assume here, as well, that you are using a recent version of SQL Server. If not, you'll need to replace STRING_AGG with the old FOR XML PATH (and STUFF) method.
This should be enough to get you started:
USE Sandbox;
GO
CREATE TABLE dbo.YourTable (Col1 int,
Col2 varchar(10));
GO
DECLARE #SchemaName sysname = N'dbo',
#TableName sysname = N'YourTable';
DECLARE #SQL nvarchar(MAX),
#CRLF nchar(2) = NCHAR(13) + NCHAR(10);
DECLARE #Delimiter nvarchar(50) = #CRLF + N'UNION ALL' + #CRLF;
SELECT #SQL = STRING_AGG(CONVERT(nvarchar(MAX),N'SELECT MIN(') + QUOTENAME(c.[name]) + N') AS ' + QUOTENAME(c.[name] + N'_MIN') + N',' + #CRLF +
N' MAX(' + QUOTENAME(c.[name]) + N') AS ' + QUOTENAME(c.[name] + N'_MAX') + #CRLF +
N'FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name])
,#Delimiter) WITHIN GROUP (ORDER BY c.column_id)
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] = #SchemaName
AND t.[name] = #TableName
PRINT #SQL;
EXEC sys.sp_executesql #SQL;
GO
DROP TABLE dbo.YourTable;
Here is a very clean and nice solution which is what I was looking for:
I choose the columns, from specific table, scan it once and perform multiple calculations on it
I use dynamic query and make it one statement for all columns.
It works really fast. It took a little over 5 minutes to return result for table with 50 mil rows.
The only thing that is left to do is to UNPIVOT in order to insert results in a table that I want.
DECLARE
#q1 NVARCHAR(MAX)
, #q2 NVARCHAR(MAX)
, #q3 NVARCHAR(500)
, #schema VARCHAR(50) = '' -- choose schema
, #table VARCHAR(200) = '' -- choose table
SET #Q1 = 'SELECT ' + '''' + #table + '''' + ' as tableName, '
SET #Q3 = ' FROM ' + #schema + '.' + #table
SELECT #q2 = COALESCE(#q2 + ', ', '')
+ ' max(' + columnName + ') as ' + columnName + '_max, '
+ ' min(' + columnName + ') as ' + columnName + '_min, '
+ ' avg(' + columnName + ') as ' + columnName + '_avg, '
+ ' stdev(' + columnName + ') as ' + columnName + '_stdev, '
+ ' sum(' + columnName + ') as ' + columnName + '_sum '
FROM (
SELECT s.[name] as schemaName, t.[name] as tableName, c.[name] as columnName, st.[name] as typeName
FROM sys.schemas s
INNER JOIN sys.tables t ON s.schema_id = t.schema_id
INNER JOIN sys.columns c ON t.object_id = c.object_id
INNER JOIN sys.types st ON st.user_type_id = c.user_type_id
WHERE 1=1
AND s.[name] = #schema
AND t.[name] = #table
AND st.[name] IN ('') -- choose columns of specific data type, that you want to profile
) data
SELECT #q1 = #q1 + #q2 + #q3
EXEC sys.sp_executesql #Q1
Enjoy!

Global Update (All Rows) On One SQL Table With The Same Criteria

IS there a way to run this across every column in a table:
UPDATE dbo.stage_a
SET Statement_Name= NULL
WHERE Statement_Name='""';
I am trying to tidy up after a data import.
Dynamic query plus Information_schema.columns. Try this.
DECLARE #cols NVARCHAR(max)='UPDATE dbo.stage_a set '
SELECT #cols += COLUMN_NAME + '=case when ' + COLUMN_NAME
+ ' = '""' then null else '+COLUMN_NAME+' end,'
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'stage_a'
AND TABLE_SCHEMA = 'dbo'
SELECT #cols = LEFT(#cols, Len(#cols) - 1)
PRINT #cols
EXEC Sp_executesql #cols
UPDATE Customers
SET ContactName='Alfred Schmidt', City='Hamburg'
WHERE CustomerName='Alfreds Futterkiste';
Like this example you should specify all coloumns.(separated by commas)
If you want to replace all coloumns with NULL why don't you delete the rows:
DELETE FROM Customers
WHERE CustomerName='Alfreds Futterkiste'
You meed to first get the all columns list by using the INFORMATION_SCHEMA.COLUMNS
DECLARE #tableName varchar(10)
SET #tableName = 'mm'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = NULL WHERE ' + c.name + '= ''';'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE t.name = #tableName AND y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
EXEC (#sql)
Query is not tested may need to tweak as per your need

SQL Query for select from all column with same column name

I have multiple column with same name from multiple table,
table_1 has column X, table_2 has column x and more.
How to select query for X column from all table without union. If use union, i must write every table name on query statement, i don't do this. I want do like :
Select X from (All table with containing X) where X value (not X name) = 'ABC'.
I use Microsoft SQL.
Is it possible ? thanks
You can do that using dynamic SQL and build the query this way:
declare #sql as nvarchar(max) = N''
declare #columnname as nvarchar(100) = N'X'
declare #columnvalue as nvarchar(100) = N'ABC'
select #sql = #sql + N' UNION ALL SELECT ' + QUOTENAME(c.name) + N' FROM '
+ QUOTENAME(s.name) + N'.'
+ QUOTENAME(t.name)
+ N' WHERE ' + QUOTENAME(c.name) + N' = ''' + #columnvalue + ''''
from sys.columns c
inner join sys.tables t on c.object_id = t.object_id
inner join sys.schemas s on t.schema_id = s.schema_id
where c.name = #columnname
set #sql = STUFF(#sql, 1, 10, N'')
exec sp_executesql #sql
Try the following it will be useful to you.
Select a.x,b.x
from table_name a,table_name1 b
where a.x = b.x
and a.x = 'ABC';

get a count of each value from every column in a table SQL Server

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

Dynamic update statement with variable column names

We're looking to do an update in several SQL Server databases to change all NULL values in a certain table to be empty strings instead of NULL. We're potentially going to be doing this across hundreds of databases. The table name will always be the same, but the column names are variable based on how the front-end application is configured (don't judge... I didn't create this system).
Is there a way to do an update on all of these columns without knowing the column names ahead of time?
You can pass the name of the column in dynamic sql:
declare #sql nvarchar (1000);
set #sql = N'update table set ' + #column_name + '= ''''';
exec sp_executesql #sql;
You can look in the sys.columns table and join on the table name or object_id.
DECLARE #OBJ_ID INT
SELECT #OBJ_ID = OBJECT_ID
FROM SYS.tables
WHERE name = 'YOURTABLE'
SELECT * FROM SYS.columns
WHERE OBJECT_ID = #OBJ_ID
You could use the name field from the sys.columns query as a basis to perform the update on.
Assuming you want all columns of varchar/char types only (or change the type filter to whatever you need):
DECLARE #tableName varchar(10)
SET #tableName = 'yourtablenamehere'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = '''' WHERE ' + c.name + ' IS NULL ;'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE t.name = #tableName AND y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
EXEC (#sql)
This can be achieved with cursors. You first select the column names like #Darren mentioned, then you Set a Cursor with those values and loop:
Open oColumnsCursor
Fetch Next From oColumnscursor
Into #ColumnName
While ##FETCH_STATUS=0
Begin
Set #oQuery = 'Update [DB]..[Table] Set [' + #ColumnName + '] = ''NewValue'' Where [' + #ColumnName + '] = ''OldValue'''
Execute(#oQuery)
Fetch Next From oColumnscursor Into #ColumnName
Set #oCount = #oCount + 1
End
Close oColumnsCursor;
Deallocate oColumnsCursor;
This will work when you know the Table Name:
DECLARE #tableName varchar(10)
SET #tableName = 'Customers'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = ISNULL('+ c.name +','''');'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
AND t.name = #tableName;
EXEC(#sql);
And this will iterate all Tables and all Columns in a Db:
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + t.name + ' SET ' + c.name + ' = ISNULL('+ c.name +','''');'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE y.name IN ('varchar', 'nvarchar', 'char', 'nchar');
EXEC(#sql);
Below is the procedure.
ALTER PROCEDURE [dbo].[util_db_updateRow]
#colval_name NVARCHAR (30), -- column and values e.g. tax='5.50'
#idf_name NVARCHAR (300), -- column name
#idn_name NVARCHAR (300), -- column value
#tbl_name NVARCHAR (100) -- table name
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
-- construct SQL
SET #sql = 'UPDATE ' + #tbl_name + ' SET ' + #colval_name +
' WHERE ' + #idf_name + '=' + #idn_name;
-- execute the SQL
EXEC sp_executesql #sql
SET NOCOUNT OFF
RETURN
END
Below is the stored procedure where you can pass Schema Name, Table Name and list of column names separted by comma.It works only in Sql Server 2016 or higher.
CREATE OR ALTER PROCEDURE UpdateData
(#SchemaName NVARCHAR(Max),#TableName NVARCHAR(MAX),#ColumnNames NVARCHAR(MAX))
AS
BEGIN
DECLARE #DynamicSql NVARCHAR(MAX);
SET #DynamicSql = 'UPDATE ' +'[' +#SchemaName+'].' + '[' +#TableName+']' +' SET ' + STUFF((SELECT ', [' + C.name + '] = ' + '''NEW_VALUE'''
FROM sys.columns C
INNER JOIN sys.tables T ON T.object_id = C.object_id
INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
WHERE
T.name = #TableName
AND S.Name = #SchemaName
AND [C].[name] in (SELECT VALUE FROM string_split(#ColumnNames,','))
FOR XML PATH('')), 1,1, '')
print #DynamicSql;
EXEC (#DynamicSql);
END