Create table with column names from another tables column data - sql

I have table with a single column like this:
---------
| col |
---------
| A |
| B |
| C |
---------
I want to create a new table with the following column names like this:
-------------------
| A | B | C |
-------------------
Any suggestions? Thanks a lot.

One way is to use dynamic sql.
Assuming data type int for all columns, you can do something like this:
Create and populate sample table (Please save us this step in your future questions):
DECLARE #T table
(
col char(1)
)
INSERT INTO #T VALUES ('a'), ('b'), ('c')
Build the dynamic sql:
DECLARE #Sql nvarchar(max) = 'CREATE TABLE YourNewTableName ('
SELECT #Sql = #Sql + col +' int,'
FROM #T
SET #Sql = LEFT(#Sql, LEN(#Sql) - 1) +');'
--Print #sql
Execute it (You will probably want to print it before to make sure it's correct):
EXEC(#Sql)

Related

SQL Server Create table to autoconvert int -> decimal

I'm not sure if this is possible but here goes.
I have financial data stored in a csv format. The data unfortunately lacks any decimal points in the dollar fields. so $100.00 is stored as '00000010000'. Its also stored as a string. In my current setup I upload the csv file into a staging table with all columns set to varchar(x).
I know that if I try to insert this value into an integer column it will automatically convert it to 10000 of type integer, but that means I am missing my decimal place.
Is there anyway I can create a table such that inserting an integer stored as a string or integer automatically converts it to an decimal with 2 places behind the decimal????
EX: '000010000' -> 100.00
I know I can cast the column to a decimal and divide the existing value by 100.... but this table has 100+ columns with 60+ of them needing to be recast. This is also only table 1 of 6. I want to avoid creating commands to individually changing the relevant columns. Not all columns containing a number need the decimal treatment.
Why not just a basic query? You have to do the math in two steps because '00000010000' is large to fit into a basic numeric. But by multiplying by 1 it will implicitly convert to an int and then it is simple to divide by 100. Notice it needs be 100.0 so it will implicitly convert it to a numeric and not an int. Here are a couple example values.
select convert(numeric(9, 2), ('00000010000' * 1) / 100.0)
select convert(numeric(9, 2), ('00000010123' * 1) / 100.0)
Just a little helper stored procedure, to convert imported table to required format.
You specify imported table name, converted table name, which will be created and comma separated list of columns which are not required to be converted.
SQL Fiddle
MS SQL Server 2014 Schema Setup:
create table source (
id int not null identity(1,1),
c1 varchar(100) not null,
c2 varchar(100) not null,
c3 varchar(100) not null,
c4 varchar(100) not null,
c5 varchar(100) not null,
c6 varchar(100) not null
);
insert source (c1, c2, c3, c4, c5, c6) values
('a', '000001000', '000001000', '000001000', '000001000', 'b'),
('c', '000002001', '000002002', '000002003', '200020002', 'd'),
('e', '000003002', '000003002', '000003003', '300030003', 'f'),
('g', '000004003', '000004002', '000004003', '400040004', 'h'),
('i', '000005004', '000005002', '000005003', '500050005', 'j')
;
create procedure convert_table
#source varchar(max),
#dest varchar(max),
#exclude_cols varchar(max)
as
begin
declare
#sql varchar(max) = 'select ',
#col_name varchar(max)
if #exclude_cols not like ',%' set #exclude_cols = ',' + #exclude_cols
if #exclude_cols not like '%,' set #exclude_cols = #exclude_cols + ','
declare c cursor for
select column_name
from information_schema.columns
where table_name = #source
open c
fetch next from c into #col_name
while ##fetch_status = 0
begin
if #exclude_cols like '%,' + #col_name + ',%'
set #sql = #sql + #col_name + ','
else
set #sql = #sql + 'convert(numeric(11, 2), ' + #col_name + ') / 100 as ' + #col_name + ','
fetch next from c into #col_name
end
close c
deallocate c
set #sql = substring(#sql, 1, len(#sql) -1)
set #sql = #sql + ' into ' + #dest + ' from ' + #source
--print(#sql)
exec(#sql)
end
;
exec convert_table #source = 'source', #dest = 'dest', #exclude_cols = 'id,c1,c6'
Query 1:
select * from dest
Results:
| id | c1 | c2 | c3 | c4 | c5 | c6 |
|----|----|-------|-------|-------|------------|----|
| 1 | a | 10 | 10 | 10 | 10 | b |
| 2 | c | 20.01 | 20.02 | 20.03 | 2000200.02 | d |
| 3 | e | 30.02 | 30.02 | 30.03 | 3000300.03 | f |
| 4 | g | 40.03 | 40.02 | 40.03 | 4000400.04 | h |
| 5 | i | 50.04 | 50.02 | 50.03 | 5000500.05 | j |
So i didn't find the overly simple answer i was hoping for but i did find another way to accomplish my goal. If i set all columns that should have a decimal place to a decimal type I can then use the system tables and (Tsql?) to modify all columns of type decimal to equal itself/100.0.
DECLARE #tableName varchar(10)
SET #tableName = 'test21'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = ' + c.name + '/100.0 ;'
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 ('decimal')
exec(#sql)
I have to dynamically construct the command within SQL based on information within the system tables and then execute it. .

SQL Columns to Rows

From my database table(Customer) I need to select one record and display the result by interchanging columns to rows.
EG:
actual result
| ID | Name | Age |
| 1 | Tom | 25 |
expected output
| Name | Value|
| ID | 1 |
| Name | Tom |
| Age | 25 |
Other details:
Customer table has different number of colums in different databases
I need to do this inside a function (So I cannot use dynamic queries, UNPIVOT)
Please advice me.
This uses CROSS APPLY with VALUES to perform unpivot
--Set up test data
CREATE TABLE dbo.TEST(ID INT IDENTITY (1,1),Name VARCHAR(20),Age TINYINT)
INSERT INTO dbo.TEST VALUES
('Shaggy',32)
,('Fred',28)
,('Velma',26)
,('Scooby',7)
DECLARE #table VARCHAR(255) = 'Test'
DECLARE #schema VARCHAR(255) = 'dbo'
DECLARE #ID INT = 2
--Create a VALUES script for the desired table
DECLARE #col VARCHAR(1000)
SELECT
#col = COALESCE(#col,'') + '(''' + c.name + ''' ,CAST(A.[' + c.name + '] AS VARCHAR(20))),'
FROM
sys.objects o
INNER JOIN sys.columns c
ON
o.object_id = c.object_id
WHERE
o.name = #table
AND
SCHEMA_NAME(o.schema_id) = #schema
ORDER BY
c.column_id
--Remove trailing ,
SET #col = LEFT(#col,LEN(#col)-1)
--Build Script for unpivoting data.
DECLARE #str VARCHAR(2000) = '
SELECT
CAST(C.Col AS VARCHAR(20)) AS [Name]
,CAST(C.Val AS VARCHAR(20)) AS [Value]
FROM
[' + #schema + '].[' + #table + '] A
CROSS APPLY (VALUES ' + #col + ') C(Col,Val)
WHERE
A.ID = ''' + CAST(#ID AS VARCHAR(8)) + ''''
--Run Script
EXEC (#str)

How to sum dynamic columns in SQL Server?

Assume I have a table with 3 columns. Is there possible sum of each column without specifying name of the column?
And is there possible create a table with dynamic name of the column, and then sum of each column?
UPDATE: Here is my sample.
First, I do a query and get the result like this:
---------
| Col |
---------
| DLX |
| SUI |
| PRE |
| TWQ |
---------
The number of row maybe different each time, and then I create a table with columns from rows above like this:
---------------------------------
| DLX | SUI | PRE | TWQ |
---------------------------------
And then I fill data the table from another table. After all, I sum each column. Because I will not know exactly name of the column, so I need sum of each column without specifying name of the column.
If your table is small (i.e. 10 columns) I would just do it manually. But if it's like 20+ columns, I would employ some dynamic sql.
To answer your question directly, yes, you can dynamically create a table with dynamic column names using dynamic sql.
Here's one way to do it:
You can use INFORMATION_SCHEMA.COLUMNS View to get all the column names and put them in a temp table.
SELECT NAME INTO #COLUMNS
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = YourTable
Next, create a temp table to store your sums.
CREATE TABLE #SUMS (
COLUMN_NAME NVARCHAR(MAX),
SUM_COLUMN INT
)
You can then use dynamic sql and a loop to sum each column.
WHILE EXISTS(SELECT TOP 1 * FROM #COLUMNS)
BEGIN
DECLARE #COLUMN NVARCHAR(MAX) = (SELECT TOP 1 * FROM #COLUMNS)
DECLARE #DYNAMICSQL NVARCHAR(MAX) = N'SELECT ' + #COLUMN + ' AS COLUMN_NAME, SUM(' + #COLUMN + ') FROM YourTable'
INSERT INTO #SUMS
EXEC SP_EXECUTESQL #DYNAMICSQL
DELETE FROM #COLUMNS
WHERE NAME = #COLUMN
END
Then you would need another dynamic sql and loop to loop through the new table and create a temp table with the column names you want using the sum values and the table name you want.
You should be able to do that using the code already supplied above.
At first I thought about pivoting but then came up with this:
DECLARE #Table TABLE ( --Table to generate input you need
[Col] nvarchar(3)
)
DECLARE #query nvarchar(max) -- a variable that will store dynamic SQL query
DECLARE #table_name nvarchar(max) = 'Temp' --A name of table to create
INSERT INTO #Table VALUES
('DLX'),
('SUI'),
('PRE'),
('TWQ')
SELECT #query = ISNULL(#query,'CREATE '+#table_name+' TABLE (') + QUOTENAME([Col]) + ' nvarchar(max),'
FROM #Table
SELECT #query = SUBSTRING(#query,1,LEN(#query)-1) +')'
EXEC sp_executesql #query
That will execute a query (PRINT #query to see the result below):
CREATE Temp TABLE ([DLX] nvarchar(max),[SUI] nvarchar(max),[PRE] nvarchar(max),[TWQ] nvarchar(max))
Which will create a temp table for you.
Then you can insert in that table in the pretty same way.

Dropping a column in sql table based on its value not column name

I have a temp table with its column_names are created dynamically and values come from XML but some columns have NULL value and I would like to drop them from the table.
the command:
ALTER TABLE ##temptable
DROP COLUMN COLUMN_NAME
works perfect if I knew the column_name but since column_names are created dynamically, I would not know them. example:
animal | human | things | stars | cars
5 | name | null | bright | null
so the columns 'things' and 'cars' that created dynamically needed to be dropped, so it would look like this:
animal | human | stars
5 | name | bright
is there way to do that (I don't want to create view)?
You can use the following. Of course, you should be adding more validation to be sure it doesn't fail.
create table #tmp (user_id int, col1 int, col2 int)
declare #column_to_drop nvarchar(100) = 'col1'
declare #sql nvarchar(max) = 'ALTER TABLE #tmp DROP COLUMN ' + QUOTENAME(#column_to_drop)
exec sp_executesql #sql
select * from #tmp
Good luck.
LATER EDIT:
You can try this. The only thing needed to know is temp table name.
CREATE TABLE #tmp (a nvarchar(max), b nvarchar(10), c nvarchar(100), d nvarchar(1000), e datetime, f int)
INSERT #tmp VALUES ('1', null, '2', null, GETDATE(), null)
SELECT *
FROM #tmp
SELECT name, CAST(0 AS BIT) checked
INTO #col_names
FROM tempdb.sys.columns
WHERE object_id = OBJECT_ID('tempdb..#tmp')
DELETE C
FROM ( SELECT *
FROM #tmp
FOR XML PATH(''), TYPE) AS T(XMLCol)
CROSS APPLY T.XMLCol.nodes('*') AS n(Col)
INNER JOIN #col_names C
ON c.name = Col.value('local-name(.)', 'varchar(max)')
DECLARE #sql NVARCHAR(MAX)
SELECT #sql = COALESCE(#sql + ', ' + QUOTENAME(name), QUOTENAME(name))
FROM #col_names
SET #sql = 'ALTER TABLE #tmp DROP COLUMN ' + #sql
PRINT #sql
EXEC (#sql)
SELECT *
FROM #tmp
DROP TABLE #tmp
DROP TABLE #col_names

Select a non-empty value for each column in each table in each database

There might be a better way to do this. But I'm trying to find columns that might contain personal information.
Problem is that the tables are poorly named (non-english, abbreviations). So I'm running this dynamic script, that will return all tables in all databases and their columns.
USE master;
DECLARE #SQL varchar(max)
SET #SQL=';WITH cteCols (dbName, colName) AS (SELECT NULL, NULL '
SELECT #SQL=#SQL+'UNION
SELECT
'''+d.name COLLATE Czech_CI_AS +'.''+sh.name COLLATE Czech_CI_AS +''.''+o.name COLLATE Czech_CI_AS ''dbSchTab''
, c.name COLLATE Czech_CI_AS ''colName''
FROM ['+d.name+'].sys.columns c
JOIN ['+d.name+'].sys.objects o ON c.object_id=o.object_id
JOIN ['+d.name+'].sys.schemas sh ON o.schema_id=sh.schema_id
WHERE o.[type] = ''U'' COLLATE Czech_CI_AS'
FROM sys.databases d
SET #SQL = #SQL + ')
SELECT
*
FROM cteCols cs
ORDER BY 1;'
EXEC (#SQL);
Result:
+---------------------+------------+
| DatabaseSchemaTable | ColumnName |
+---------------------+------------+
| dev1.dbo.Users | Col1 |
| dev1.dbo.Users | Col2 |
| dev1.dbo.Users | Col3 |
| dev1.dbo.Users | Col4 |
+---------------------+------------+
But because of the poor column naming, I can't tell what data is in these columns. I'd like to select a TOP (1) non NULL value from each column, but I'm struggling.
Required result:
+---------------------+------------+--------------+
| DatabaseSchemaTable | ColumnName | ColumnValue |
+---------------------+------------+--------------+
| dev1.dbo.Users | Col1 | 20 |
| dev1.dbo.Users | Col2 | 2018-02-06 |
| dev1.dbo.Users | Col3 | 202-555-0133 |
| dev1.dbo.Users | Col4 | John Doe |
+---------------------+------------+--------------+
Ideas I had:
I would need to either transpose each of the tables (probably not a
job for PIVOT)
I could join with the table dynamically and only display the current column. But I can't use dynamic column in correlated subquery.
Any ideas?
I would create a temporary table such as ##cols, and then use this temporary table to loop through the table, running update queries on the table itself. Mind you, we have a lot of spaces and other potentially troublesome characters in our field names. Therefore I updated your cte with some QUOTENAMEs around the field / table / schema / db names.
USE master;
DECLARE #SQL varchar(max);
SET #SQL=';WITH cteCols (dbName, colName, top1Value) AS (SELECT NULL, NULL, CAST(NULL AS VARCHAR(MAX)) '
SELECT #SQL=#SQL+' UNION
SELECT
'''+QUOTENAME(d.[name]) COLLATE Czech_CI_AS +'.''+QUOTENAME(sh.name) COLLATE Czech_CI_AS +''.''+QUOTENAME(o.name) COLLATE Czech_CI_AS ''dbSchTab''
, QUOTENAME(c.name) COLLATE Czech_CI_AS ''colName'', CAST(NULL AS VARCHAR(MAX)) AS ''top1Value''
FROM ['+d.[name]+'].sys.columns c
JOIN ['+d.[name]+'].sys.objects o ON c.object_id=o.object_id
JOIN ['+d.[name]+'].sys.schemas sh ON o.schema_id=sh.schema_id
WHERE o.[type] = ''U'' COLLATE Czech_CI_AS'
FROM sys.databases d;
SET #SQL = #SQL + ')
SELECT
*
INTO ##Cols
FROM cteCols cs
ORDER BY 1;'
EXEC (#SQL);
DECLARE #colName VARCHAR(255), #dbName VARCHAR(255), #SQL2 NVARCHAR(MAX);
DECLARE C CURSOR FOR SELECT [colName],[dbName] FROM ##Cols;
OPEN C;
FETCH NEXT FROM C INTO #colName, #dbName;
WHILE ##FETCH_STATUS=0
BEGIN
SET #SQL2='UPDATE ##Cols SET [top1Value] = (SELECT TOP 1 x.'+#colName+' FROM '+#dbName+' x WHERE x.'+#colName+' IS NOT NULL) WHERE [colName]='''+#colName+''' AND [dbName]='''+#dbName+''''
EXEC sp_executesql #SQL2
FETCH NEXT FROM C INTO #colName, #dbName
END;
CLOSE C;
DEALLOCATE C;
SELECT * FROM ##Cols;
It's not pretty, but it'd suit your needs.
You might try this:
--In this table we write our findings
CREATE TABLE ##TargetTable(ID INT IDENTITY, TableName VARCHAR(500), FirstRowXML XML);
--the undocumented sp "MSforeachtable" allows to create a statement where the
--question mark is a place holder for the actual table
--(SELECT TOP 1 * FROM ? FOR XML PATH('row')) will create one single XML with all first row's values
EXEC sp_MSforeachtable 'INSERT INTO ##TargetTable(TableName,FirstRowXML) SELECT ''?'', (SELECT TOP 1 * FROM ? FOR XML PATH(''row''))';
--Now it is easy to get what you want
SELECT ID
,TableName
,col.value('local-name(.)','nvarchar(max)') AS colname
,col.value('text()[1]','nvarchar(max)') AS colval
FROM ##TargetTable
CROSS APPLY FirstRowXML.nodes('/row/*') A(col);
GO
DROP TABLE ##TargetTable
Just use SELECT TOP X to get more than one row...
UPDATE
The following will create a table with all columns of all tables of all databases and fetch one value per row.
CREATE TABLE ##TargetTable(ID INT IDENTITY
,TABLE_CATALOG VARCHAR(300),TABLE_SCHEMA VARCHAR(300),TABLE_NAME VARCHAR(300),COLUMN_NAME VARCHAR(300)
,DATA_TYPE VARCHAR(300),CHARACTER_MAXIMUM_LENGTH INT, IS_NULLABLE VARCHAR(10),Command VARCHAR(MAX),OneValue NVARCHAR(MAX));
EXEC sp_MSforeachdb
'USE ?;
INSERT INTO ##TargetTable(TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,IS_NULLABLE,Command)
SELECT ''?''
,c.TABLE_SCHEMA
,c.TABLE_NAME
,c.COLUMN_NAME
,c.DATA_TYPE
,c.CHARACTER_MAXIMUM_LENGTH
,c.IS_NULLABLE
, CASE WHEN c.IS_NULLABLE=''YES''
THEN ''SELECT CAST(MAX('' + QUOTENAME(c.COLUMN_NAME) + '') AS NVARCHAR(MAX))''
ELSE ''SELECT TOP 1 CAST('' + QUOTENAME(c.COLUMN_NAME) + '' AS NVARCHAR(MAX))''
END
+ '' FROM '' + QUOTENAME(''?'') + ''.'' + QUOTENAME(c.TABLE_SCHEMA) + ''.'' + QUOTENAME(c.TABLE_NAME)
FROM INFORMATION_SCHEMA.COLUMNS c
INNER JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_CATALOG=t.TABLE_CATALOG AND c.TABLE_SCHEMA=t.TABLE_SCHEMA AND c.TABLE_NAME=T.TABLE_NAME AND t.TABLE_TYPE=''BASE TABLE''
WHERE c.DATA_TYPE NOT IN(''BINARY'',''VARBINARY'',''IMAGE'',''NTEXT'')';
DECLARE #ID INT,#Command VARCHAR(MAX);
DECLARE cur CURSOR FOR SELECT ID,Command FROM ##TargetTable
OPEN cur;
FETCH NEXT FROM cur INTO #ID,#Command;
WHILE ##FETCH_STATUS=0
BEGIN
SET #Command = 'UPDATE ##TargetTable SET OneValue=(' + #Command + ') WHERE ID=' + CAST(#ID AS VARCHAR(100))
PRINT #command;
EXEC(#Command);
FETCH NEXT FROM cur INTO #ID,#Command;
END
CLOSE cur;
DEALLOCATE cur;
GO
SELECT * FROM ##TargetTable;
GO
DROP TABLE ##TargetTable;