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

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.

Related

Dynamic SQL - Use declared VARCHAR in SET SQL string

How to use the declared variable #CodeID inside the SQL string? When I run following statement I get the "Invalid object name (..)" error.
WHILE #FolderID <= #FolderMaxID
BEGIN
SELECT #Db = Db
FROM #Folders
WHERE ID = #FolderID
SET #Sql = N'
DECLARE #CodeID NVARCHAR(256)
SELECT TOP(1) #CodeID=CodeType
FROM ' + #Db + '.bla.Field
WHERE Name= ''Example''
SELECT DISTINCT C.Name
FROM ' + #Db + '.Document
INNER JOIN ' + #Db + '.bla.Code_#CodeID C ON D.ID = C.ID'
EXEC ( #Sql )
SET #FolderID = #FolderID + 1
END
It looks to me that you need two levels of dynamic SQL, with the first level inserting the database name (from #folders), and the second level inserting a constructed table name (based on the CodeType column of the database-local bla.Field table).
I do not know of any way to parameterize database names or table names using sp_executesql, so I'm sticking with build-up dynamic SQL and EXEC (). (If someone makes a case for preferring sp_executesql over EXEC when not useing parameters, then it may be worth the switch.)
Try something like:
WHILE #FolderID <= #FolderMaxID
BEGIN
SELECT #Db = Db
FROM #Folders
WHERE ID = #FolderID
SET #Sql = N'
DECLARE #CodeID NVARCHAR(256)
SELECT TOP(1) #CodeID=CodeType
FROM ' + QUOTENAME(#Db) + '.bla.Field
WHERE Name= ''Example''
DECLARE #Sql2 NVARCHAR(MAX) = N''
SELECT DISTINCT C.Name
FROM ' + QUOTENAME(#Db) + '.bla.Document D
INNER JOIN ' + QUOTENAME(#Db) + '.bla.'' + QUOTENAME(''Code_'' + #CodeID) + '' C ON D.ID = C.ID
''
EXEC #sql2
'
EXEC ( #Sql )
SET #FolderID = #FolderID + 1
END
This implements dynamic SQL within dynamic SQL. Doubled quotes in the outer sql template become single quotes in the inner sql. The original posted code seemed to be missing a schema qualifier and alias for the Document table, so I inserted them ("bla" and "D"). I also added QUOTENAME around the injected names as suggested by Larnu.
The first level of dynamic sql would generate something like:
SELECT TOP(1) #CodeID=CodeType
FROM [db1].bla.Field
WHERE Name= 'Example'
DECLARE #Sql2 NVARCHAR(MAX) = N'
SELECT DISTINCT C.Name
FROM [db1].bla.Document D
INNER JOIN [db1].bla.' + QUOTENAME('Code_' + #CodeID) + ' C ON D.ID = C.ID
'
EXEC #sql2
The second level would generate something like:
SELECT DISTINCT C.Name
FROM [db1].bla.Document D
INNER JOIN [db1].bla.[Code_Table1] C ON D.ID = C.ID
Note that each loop iteration will generate a separate result. If you wish to combine results, you will need to define a #temp table, insert the individual results into that table, and then select the combined results at the end of your script.
Note that I haven't tested the specific code above, so it might need some debugging (add "PRINT #sql2" before the EXEC) if it doesn't work straight out.
ADDENDUM
Per #trenton-ftw comments below, an out parameter can be used to capture the result of the first query so that it may be included in the second query without the need for nesting. Two executions are still required. Below is a revised example.
DECLARE #Folders TABLE (ID INT IDENTITY(1,1), Db sysname)
INSERT #Folders VALUES ('db1'), ('db2')
DECLARE #SearchName NVARCHAR(256) = 'Example'
DECLARE #Db sysname
DECLARE #Sql NVARCHAR(MAX)
DECLARE #CodeID NVARCHAR(256)
DECLARE #FolderMaxID INT = (SELECT MAX(ID) FROM #Folders)
DECLARE #FolderID INT = 1
WHILE #FolderID <= #FolderMaxID
BEGIN
SELECT #Db = Db
FROM #Folders
WHERE ID = #FolderID
SET #Sql = N'
SET #CodeID = #SearchName + ''-Test''
--SELECT TOP(1) #CodeID = CodeType
--FROM ' + QUOTENAME(#Db) + '.bla.Field
--WHERE Name = #SearchName'
PRINT #Sql
EXEC sp_executesql #Sql,
N'#SearchName NVARCHAR(256), #CodeID NVARCHAR(256) OUTPUT',
#SearchName, #CodeID OUTPUT
SET #Sql = N'
--SELECT DISTINCT C.Name
--FROM ' + QUOTENAME(#Db) + '.bla.Document D
-- INNER JOIN ' + QUOTENAME(#Db) + '.bla.' + QUOTENAME('Code_' + #CodeID) + ' C ON D.ID = C.ID'
PRINT #Sql
EXEC sp_executesql #sql
SET #FolderID = #FolderID + 1
END
For demo purposes, I also parameterized the search name as an input parameter and added some temporary code to make it stand-alone testable. A final version would uncomment the actual sql, and remove the print statements and the test #CodeID assignemnt.

How to make 0 in all column in a specific table where value is null using MSSQL

Let the table name is "MyTable"
My current data looks like:
Following, I need after a query on above table:
Actually I need to update all column where value is "NULL", in a single query.
Use ISNULL if you want to see the NULL as 0. Like this
SELECT ISNULL(Column1,0) FROM YourTable
or what you need is to update the value as 0 if NULL and keep the value as it is otherwise. these use a case in the update statement. Like this
Update YourTable
SET Column1 = CASE WHEN Column1 IS NULL THEN 0 ELSE Column1 END,
Column2 = CASE WHEN Column2 IS NULL THEN 0 ELSE Column2 END
and so on for the rest of the columns. Or this is also possible
Update YourTable
SET Column1 = ISNULL(Column1,0),
cOLUMN2 = ISNULL(Column2,0)
You can use something like this.
It is elegant, but it will update all columns in the table. Huge tables might kill the server
DECLARE #TableName sysname = 'tablename'
Declare #UptQuery varchar(max)
Select #UptQuery = stuff(T.X.query('name').value('.', 'varchar(max)'), 1, 1, '')
from
(Select ','+name + '=ISNULL('+name+', 0)' name from
sys.columns where object_id = object_id(#TableName) for xml path(''), type) T(X)
exec ('Update ' + #TableName + ' set ' + #UptQuery)
Old answer
It will go through all columns for a table and update everything with 0 if it is null. It is a lot of updates, and I think it is still better to design the table correctly from the start.
DECLARE #TableName sysname = 'tablename'
Declare #ColName sysname
Select name into #temp from sys.columns where object_id = object_id(#TableName)
while(0 < (Select count(1) from #temp))
BEGIN
SET ROWCOUNT 1
Select #ColName = name from #temp
SET ROWCOUNT 0
exec('Update ' + #TableName + ' set ' + #ColName + ' = ISNULL('+#ColName+', 0) where ' + #ColName + ' is null')
delete #temp where name = #ColName
END
Update table
Set column1 = coalesce (column1,0), ....
declare #tableName varchar(30)
set #tableName='MyTable'
DECLARE #MakeString AS NVARCHAR(MAX)
SELECT #MakeString=
(SELECT cname + ',' AS 'data()'
FROM ( select COLUMN_NAME +'= isnull(['+COLUMN_NAME+'],0)' as cname from INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tableName
) as ccc
FOR XML PATH(''))
SET #MakeString = LEFT(#MakeString, LEN(#MakeString) - 1)
DECLARE #Sql AS NVARCHAR(MAX)
set #Sql='Update '+#tableName+'
SET '+#MakeString+''
EXEC(#Sql);
UPDATE Table SET ColumnName1=0 WHERE ColumnName1 IS NULL
...

How do I use loop to generate column names dynamically?

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'

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 Server : query to insert data into table from another table with different struct

I have two tables in two different databases.
My first table is an older version and has fewer columns than the second table.
I want to copy the contents of my old table to my new table.
In each database table there are several distribution in this case.
How can I do to quickly copy data from old tables to the new without having to write the column names manually for each table?
Thanks!
You can "avoid writing the column names manually" in SSMS by dragging and dropping the "Columns" folder under the table in the Object Explorer over to a query window (just hold the dragged item over whitespace or the character position where you want the names to appear). All the column names will be displayed separated by commas.
You could also try something like this to get just the list of columns that are common between two tables (then writing the INSERT statement is trivial).
SELECT
Substring((
SELECT
', ' + S.COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS S
INNER JOIN INFORMATION_SCHEMA.COLUMNS D
ON S.COLUMN_NAME = D.COLUMN_NAME
WHERE
S.TABLE_SCHEMA = 'dbo'
AND S.TABLE_NAME = 'Source Table'
AND D.TABLE_SCHEMA = 'dbo'
AND D.TABLE_NAME = 'Destination Table'
FOR XML PATH(''), TYPE
).value('.[1]', 'nvarchar(max)'), 3, 21474783647)
;
You could also create an SSIS package that simply moves all the data from one table to the other. Column names that match would automatically be linked up. Depending on your familiarity with SSIS, this could take you 2 minutes, or it could take you 2 hours.
The following code should do the work.
Basically what it does is:
1. Collects column names from both tables.
2. Intersects the column names in order to filter out columns that exists only in 1 table.
3. Get a string which is the column names delimited by a comma.
4. Using the string from stage #3 creating the insert command.
5. Executing the command from stage #4.
--BEGIN TRAN
DECLARE #oldName NVARCHAR(50) = 'OldTableName', #newName NVARCHAR(50) = 'newTableName'
DECLARE #oldDBName NVARCHAR(50) = '[OldDBName].[dbo].['+#oldName+']', #newDBName NVARCHAR(50) = '[newDBName].[dbo].['+#newName+']'
/*This table variable will have columns that exists in both table*/
DECLARE #tCommonColumns TABLE(
ColumnsName NVARCHAR(max) NOT NULL
);
INSERT INTO #tCommonColumns
SELECT column_name --,*
FROM information_schema.columns
WHERE table_name = #oldName
AND COLUMNPROPERTY(object_id(#oldName), column_name, 'IsIdentity') = 0 --this will make sure you ommit IDentity columns
INTERSECT
SELECT column_name --, *
FROM information_schema.columns
WHERE table_name = #newName
AND COLUMNPROPERTY(object_id(#newName), column_name,'IsIdentity') = 0--this will make sure you ommit IDentity columns
--SELECT * FROM #tCommonColumns
/*Get the columns as a comma seperated string */
DECLARE #columns NVARCHAR(max)
SELECT DISTINCT
#columns = STUFF((SELECT ', ' + cols.ColumnsName
FROM #tCommonColumns cols
FOR XML Path('')),1,1,'')
FROM #tCommonColumns
PRINT #columns
/*Create tyhe insert command*/
DECLARE #InserCmd NVARCHAR(max)
SET #InserCmd =
'INSERT INTO '+#newDBName +' ('+#columns +')
SELECT '+#columns +' FROM '+#oldDBName
PRINT #InserCmd
/*Execute the command*/
EXECUTE sp_executesql #InserCmd
--ROLLBACK
Please note that this script might fail if you have FOREIGN KEY Constraints That are fulfiled in the old table but not in the new table.
Edit:
The query was updated to omit Identity columns.
Edit 2:
query updated for supporting different databases for the tables (make sure you set the #oldName ,#newName, #oldDBName, #newDBName variables to match actual credentials).
Thanks all !
I propose that it's more generic :)
--BEGIN TRAN
DECLARE #Tablename NVARCHAR(50)
SET #Tablename = 'tableName'
DECLARE #Schemaname NVARCHAR(50)
SET #Schemaname = 'schemaName'
DECLARE #Datasource NVARCHAR(50)
SET #Datasource = 'dataSource'
DECLARE #Datadest NVARCHAR(50)
SET #Datadest = 'dataDestination'
/*This table variable will have columns that exists in both table*/
DECLARE #tCommonColumns TABLE(
ColumnsName NVARCHAR(max) NOT NULL
);
--INSERT INTO #tCommonColumns
DECLARE #sql NVARCHAR(max)
SET #sql = 'SELECT column_name
FROM ' + #Datasource + '.information_schema.columns
WHERE table_name = ''' + #Tablename + '''
AND COLUMNPROPERTY(object_id(''' + #Datasource + '.' + #Schemaname + '.' + #Tablename + '''), column_name, ''IsIdentity'') = 0' --this will make sure you ommit IDentity columns
SET #sql = #sql + ' INTERSECT
SELECT column_name
FROM ' + #Datadest + '.information_schema.columns
WHERE table_name = ''' + #Tablename + '''
AND COLUMNPROPERTY(object_id(''' + #Datadest + '.' + #Schemaname + '.' + #Tablename + '''), column_name, ''IsIdentity'') = 0' --this will make sure you ommit IDentity columns'
INSERT INTO #tCommonColumns EXECUTE sp_executesql #sql
-- SELECT * FROM #tCommonColumns
/*Get the columns as a comma seperated string */
DECLARE #columns NVARCHAR(max)
SELECT DISTINCT
#columns = STUFF((SELECT ', ' + cols.ColumnsName
FROM #tCommonColumns cols
FOR XML Path('')),1,1,'')
FROM #tCommonColumns
--PRINT #columns
/*Create tyhe insert command*/
DECLARE #InserCmd NVARCHAR(max)
SET #InserCmd =
'INSERT INTO '+#Datadest+'.'+#Schemaname+'.'+#Tablename +' ('+#columns +')
SELECT '+#columns +' FROM '+#Datasource+'.'+#Schemaname+'.'+#Tablename
PRINT #InserCmd
/*Execute the command*/
--EXECUTE sp_executesql #InserCmd
--ROLLBACK
Something like this:
Insert into dbo.Newtbl
SELECT * FROM dbo.OldTbl