SQL insert from select, but set some values by hand - sql

Let's say that I have table with A LOT of columns. I have one column with primary key that has autoincrement set to 1. I want to insert a new row and in this new row I have following requirements:
The row must have generated ID
All non-specified columns have to be copied from row with id='9999'
I have to be able to set some values of columns by hand (for example columns name and age
I have tried:
Insert Into demo_table
Select * From demo_table Where id='9999';
However, I get this error:
An explicit value for the identity column in table 'demo_table' can only be specified when a column list is used and IDENTITY_INSERT is ON.
What do I need:
I want to duplicate a row -> let the id be set by database (I have PK and autoincrement configured) -> set some columns by hand -> have other column's values duplicated, without specifying column names (as I have a lot of columns and their names could change in future.)
Form of solution:
I would prefer if I was able to achive this using only one query. If necessary, I have stored procedures available.
My question:
Is this even possible? How could I achive such query/procedure?

There is a way to build sql query by table schema:
USE <databaseName>
DECLARE
#SourceTableName NVARCHAR(255) = <TableName>,
#SqlQuery NVARCHAR(MAX)
IF OBJECT_ID('tempdb.dbo.#IdentityCols', 'U') IS NOT NULL
DROP TABLE #IdentityCols;
CREATE TABLE #IdentityCols(
ColumnName NVARCHAR(255)
)
INSERT INTO #IdentityCols
SELECT
--TABLE_NAME,
COLUMN_NAME
FROM
INFORMATION_SCHEMA.COLUMNS
WHERE
COLUMNPROPERTY(object_id(TABLE_SCHEMA+'.'+TABLE_NAME), COLUMN_NAME, 'IsIdentity') = 1 AND TABLE_NAME = #SourceTableName
UNION
SELECT
--o.name,
c.name
FROM
sys.objects o inner join
sys.columns c on o.object_id = c.object_id
WHERE
c.is_identity = 1 AND o.name = #SourceTableName
--STRING_AGG in SQL SERVER 2017 and greater. As aproach for early versions is cursor or loop
SELECT #SqlQuery = 'SELECT ' + STRING_AGG(COLUMN_NAME, ',') + ' FROM ' + #SourceTableName
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME=#SourceTableName AND COLUMN_NAME NOT IN (Select ColumnName FROM #IdentityCols)
exec sp_executesql #SqlQuery
For more information you can see this questions:
How can I show the table structure in SQL Server query?
How do you determine what SQL Tables have an identity column programmatically
How to concatenate text from multiple rows into a single text string in SQL Server
SQL Server Loop through Table Rows without Cursor
SQL Server loop - how do I loop through a set of records

For anyone interested, how I've solved this problem:
After I've read your comments (thanks btw) and some threads online, I've realized why I cannot do what I asked. However, I've come seen solution to similar problem somewhere, where they wanted to select * except one specific column, they solved it like this:
copied the entire table
selected from there.
I've come up with similar solution to my problem
-- check data, remove after testing
select * from demo_table order by ID desc;
-- create table with one column I want to duplicate
select * into Temp_demo_table from demo_table where ID=9999;
-- drop id, so it does not get included in the inter-table insert
alter table Temp_demo_table
drop column ID;
-- update columns that I need to modify, doesn't have to have WHERE clause, becuase there's only one row there
update Temp_demo_table set MyCustomProperty='abc', name=NULL, age=NULL
-- insert the desired and edited row
insert into demo_table
select * from Temp_demo_table;
-- drop the temp table
drop table Temp_demo_table;
-- check data, remove after testing
select * from demo_table order by ID desc;
I realize how inefficient this is, however the function (on my api) executing this command will not be called so often (max 100 times per day). I believe that this query could be optimized, however I do not have sufficient knowledge to do it at this moment (100% going to put it in my TODO :D).
Edit-1:
Just found out that you can write queries in oracle db like this:
(select * from demo_table) - (select name, age from demo_table)
I currentlly don't know if I can apply this to sql server, however as soon as I have an access to mssql, I'll try it out and keep this answear updated!

Related

simple UPDATE query on large table has bad performance

I need to do the following update query through a stored procedure:
UPDATE table1
SET name = #name (this is the stored procedure inputparameter)
WHERE name IS NULL
Table1 has no indexes or keys, 5 columns which are 4 integers and 1 varchar (updatable column 'name' is the varchar column)
The NULL records are about 15.000.000 rows that need updating. This takes about 50 minutes, which I think is too long.
I'm running an Azure SQL DB Standard S6 (400DTU's).
Can anyone give me an advise to improve performance?
As you don't have any keys, or indexes, I can suggest following approach.
1- Create a new table using INTO (which will copy the data) like following query.
SELECT
CASE
WHEN NAME IS NULL THEN #name
ELSE NAME
END AS NAME,
<other columns >
INTO dbo.newtable
FROM table1
2- Drop the old table
drop table table1
3- Rename the new table to table1
exec sp_rename 'dbo.newtable', 'table1'
Another approach can be using batch update, sometime you get better performance compared to bulk update (You need to test by adjusting the batch size).
WHILE EXISTS (SELECT 1 FROM table1 WHERE name is null)
BEGIN
UPDATE TOP (10000) table1
SET name = #name
WHERE n ame is null
END
can you do with following method ?
UPDATE table1
SET name = ISNULL(name,#name)
for null values it will update with #name and rest will be updated with same value.
No. You are updating 15,000,000 rows which is going to take a long time. Each update has overhead for finding the row and logging the value.
With so many rows to update, it is unlikely that the overhead is finding the rows. If you add an index on name, the update is going to actually have to update the index as well as updating the original values.
If your concern is locking the database, you can set up a loop where you do something like this over and over:
UPDATE TOP (100000) table1
SET name = #name (this is the stored procedure inputparameter)
WHERE name IS NULL;
100,000 rows should be about 30 seconds or so.
In this case, an index on name does help. Otherwise, each iteration of the loop would in essence be reading the entire table.

SQL Server - Select INTO statement stored in sys.tables

I know how to find the CREATE statement for a table in SQL Server but is there any place that stores the actual SQL code if I use SELECT INTO ... to create a table and if so how do I access it?
I see two ways of creating tables with SELECT INTO.
First: You know the Schema, then you can declare a #Table Variable and perform the Select INSERT
Second: You can create a temp table:
SELECT * INTO #TempTable FROM Customer
There are some limitations on the second choice:
- You need to drop the temp table afterwards.
- If there is a VARCHAR Column and the maximum number of characters of that given SELECT is 123 characters (example), and then you try to insert into the TEMP table afterwards with a greater number of characters, it will throw an error.
My recommendation is always declare a table in order to use, it makes it clear what is the intentions and increases readability.

SQL Trigger to copy data to table with more number of columns

I have a table (tableA) that has about 150 columns and I would like to copy data from tableA to another table (table B) upon any new data that is inserted to table A. tableB has the same table structure as tableA with a few additional columns.
I am new to triggers and I was reading an example in the following link: https://www.codeproject.com/Articles/25600/Triggers-SQL-Server
Since I have tableA which has 150 columns, is there any easier way to specify the columns in the trigger rather that declaring 150 variables, selecting 150 variables to equal to the inserted value, and then inserting it to tableB. This makes the trigger code really long. I am wondering if there is any easier way to specify the 150 columns.
Thanks
A simple copy trigger should look something like this:
CREATE TRIGGER MyTrigger ON MyTable
FOR INSERT
AS
BEGIN
INSERT INTO MyOtherTable (Column1, Column2, Column3... Column150)
SELECT (Column1, Column2, Column3... Column150)
FROM inserted;
END
Having a table with 150 columns is a problem and not good design at all. That said, it's what you have to deal with. You will need to list out each one individually in both the INSERT and SELECT statements. That will be a one time pain, but it's really the best/only way of doing this. By doing this, you ensure the right data gets into the right column, and nothing breaks if/when columns are added to either table.
To help with the tedious part of creating the list of columns, you can use some SQL like this to generate the list for you:
DECLARE #sql nvarchar(max);
SET #sql = N'';
SELECT #sql += QUOTENAME(COLUMN_NAME) + N', '
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'MyTable'
SELECT #sql;
Just substitute the name of the table with the subset of columns you wish to copy for "MyTable" and you should be good. This of course, assumes the matching columns have the exact same names in both tables. If there are any differences, you'll need to manually align the ones that should get the appropriate data.

Execute SQL-Query stored in column while selecting

I have two tables:
Table A contains the columns ID and Query.
Table B contains the columns ID, Value and Category.
In every row of table A (without any exceptions) the column Query contains an SQL query as nvarchar, which looks something like SELECT * FROM B WHERE Category = 1.
I cannot figure out, how i can execute all Queries of table A and merge those results and return them (inside a select statement).
I already tried to iterate through all rows in table A, inserting the results of EXEC(Query) into a temporary table #C and finally execute SELECT * FROM #C. But i was not successful with this solution.
EDIT
Just for clarification: The columns and their data types of the resultsets of the queries - stored in table A - are identical. Table B just has a column with datatype [timestamp]. The exception i receive is:
Cannot insert an explicit value into a timestamp column. Use INSERT with a column list to exclude the timestamp column, or insert a DEFAULT into the timestamp column
This is too long for a comment.
You can run all the queries using a T-SQL statement. That is, a WHILE loop with a cursor or similar mechanism to run each query using dynamic SQL.
Putting the results into a single table is problematic. All the queries would need to return the same columns in the same order with the same types. It doesn't look to me like SELECT * FROM B and SELECT * FROM #C would meet these conditions.
You probably need to think more about what you really need to accomplish.
I think this should get you what you want if I understand the question.
DECLARE #sqlStatement NVARCHAR(MAX) = '';
SELECT #sqlStatement += CONCAT(Query,' UNION ALL ')
FROM TableA
;
SET #sqlStatement = LEFT(#sqlStatement, LEN(#sqlStatement) - 10 );
EXEC sp_executesql #sqlStatement;

How do you copy a record in a SQL table but swap out the unique id of the new row?

This question comes close to what I need, but my scenario is slightly different. The source table and destination table are the same and the primary key is a uniqueidentifier (guid). When I try this:
insert into MyTable
select * from MyTable where uniqueId = #Id;
I obviously get a primary key constraint violation, since I'm attempting to copy over the primary key. Actually, I don't want to copy over the primary key at all. Rather, I want to create a new one. Additionally, I would like to selectively copy over certain fields, and leave the others null. To make matters more complex, I need to take the primary key of the original record, and insert it into another field in the copy (PreviousId field).
I'm sure there is an easy solution to this, I just don't know enough TSQL to know what it is.
Try this:
insert into MyTable(field1, field2, id_backup)
select field1, field2, uniqueId from MyTable where uniqueId = #Id;
Any fields not specified should receive their default value (which is usually NULL when not defined).
Ok, I know that it's an old issue but I post my answer anyway.
I like this solution. I only have to specify the identity column(s).
SELECT * INTO TempTable FROM MyTable_T WHERE id = 1;
ALTER TABLE TempTable DROP COLUMN id;
INSERT INTO MyTable_T SELECT * FROM TempTable;
DROP TABLE TempTable;
The "id"-column is the identity column and that's the only column I have to specify. It's better than the other way around anyway. :-)
I use SQL Server. You may want to use "CREATE TABLE" and "UPDATE TABLE" at row 1 and 2.
Hmm, I saw that I did not really give the answer that he wanted. He wanted to copy the id to another column also. But this solution is nice for making a copy with a new auto-id.
I edit my solution with the idéas from Michael Dibbets.
use MyDatabase;
SELECT * INTO #TempTable FROM [MyTable] WHERE [IndexField] = :id;
ALTER TABLE #TempTable DROP COLUMN [IndexField];
INSERT INTO [MyTable] SELECT * FROM #TempTable;
DROP TABLE #TempTable;
You can drop more than one column by separating them with a ",".
The :id should be replaced with the id of the row you want to copy.
MyDatabase, MyTable and IndexField should be replaced with your names (of course).
I'm guessing you're trying to avoid writing out all the column names. If you're using SQL Management Studio you can easily right click on the table and Script As Insert.. then you can mess around with that output to create your query.
Specify all fields but your ID field.
INSERT INTO MyTable (FIELD2, FIELD3, ..., FIELD529, PreviousId)
SELECT FIELD2, NULL, ..., FIELD529, FIELD1
FROM MyTable
WHERE FIELD1 = #Id;
I have the same issue where I want a single script to work with a table that has columns added periodically by other developers. Not only that, but I am supporting many different versions of our database as customers may not all be up-to-date with the current version.
I took the solution by Jonas and modified it slightly. This allows me to make a copy of the row and then change the primary key before adding it back into the original source table. This is also really handy for working with tables that do not allow NULL values in columns and you don't want to have to specify each column name in the INSERT.
This code copies the row for 'ABC' to 'XYZ'
SELECT * INTO #TempRow FROM SourceTable WHERE KeyColumn = 'ABC';
UPDATE #TempRow SET KeyColumn = 'XYZ';
INSERT INTO SourceTable SELECT * FROM #TempRow;
DELETE #TempRow;
Once you have finished the drop the temp table.
DROP TABLE #TempRow;
I know my answer is late to the party. But the way i solved is bit different than all the answers.
I had a situation, i need to clone a row in a table except few columns. Those few will have new values. This process should support automatically for future changes to the table. This implies, clone the record without specifying any column names.
My approach is to,
Query Sys.Columns to get the full list of columns for the table and include the names
of columns to skip in where clause.
Convert that in to CSV as column names.
Build Select ... Insert into script based on this.
declare #columnsToCopyValues varchar(max), #query varchar(max)
SET #columnsToCopyValues = ''
--Get all the columns execpt Identity columns and Other columns to be excluded. Say IndentityColumn, Column1, Column2
Select #columnsToCopyValues = #columnsToCopyValues + [name] + ', ' from sys.columns c where c.object_id = OBJECT_ID('YourTableName') and name not in ('IndentityColumn','Column1','Column2')
Select #columnsToCopyValues = SUBSTRING(#columnsToCopyValues, 0, LEN(#columnsToCopyValues))
print #columnsToCopyValues
Select #query = CONCAT('insert into YourTableName (',#columnsToCopyValues,', Column1, Column2) select ', #columnsToCopyValues, ',''Value1'',''Value2'',', ' from YourTableName where IndentityColumn =''' , #searchVariable,'''')
print #query
exec (#query)
insert into MyTable (uniqueId, column1, column2, referencedUniqueId)
select NewGuid(), // don't know this syntax, sorry
column1,
column2,
uniqueId,
from MyTable where uniqueId = #Id
If "key" is your PK field and it's autonumeric.
insert into MyTable (field1, field2, field3, parentkey)
select field1, field2, null, key from MyTable where uniqueId = #Id
it will generate a new record, copying field1 and field2 from the original record
My table has 100 fields, and I needed a query to just work. Now I can switch out any number of fields with some basic conditional logic and not worry about its ordinal position.
Replace the below table name with your table name
SQLcolums = "SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE (TABLE_NAME = 'TABLE-NAME')"
Set GetColumns = Conn.Execute(SQLcolums)
Do WHILE not GetColumns.eof
colName = GetColumns("COLUMN_NAME")
Replace the original identity field name with your PK field name
IF colName = "ORIGINAL-IDENTITY-FIELD-NAME" THEN ' ASSUMING THAT YOUR PRIMARY KEY IS THE FIRST FIELD DONT WORRY ABOUT COMMAS AND SPACES
columnListSOURCE = colName
columnListTARGET = "[PreviousId field name]"
ELSE
columnListSOURCE = columnListSOURCE & colName
columnListTARGET = columnListTARGET & colName
END IF
GetColumns.movenext
loop
GetColumns.close
Replace the table names again (both target table name and source table name); edit your where conditions
SQL = "INSERT INTO TARGET-TABLE-NAME (" & columnListTARGET & ") SELECT " & columnListSOURCE & " FROM SOURCE-TABLE-NAME WHERE (FIELDNAME = FIELDVALUE)"
Conn.Execute(SQL)
You can do like this:
INSERT INTO DENI/FRIEN01P
SELECT
RCRDID+112,
PROFESION,
NAME,
SURNAME,
AGE,
RCRDTYP,
RCRDLCU,
RCRDLCT,
RCRDLCD
FROM
FRIEN01P
There instead of 112 you should put a number of the maximum id in table DENI/FRIEN01P.
Here is how I did it using ASP classic and couldn't quite get it to work with the answers above and I wanted to be able to copy a product in our system to a new product_id and needed it to be able to work even when we add in more columns to the table.
Cn.Execute("CREATE TEMPORARY TABLE temprow AS SELECT * FROM product WHERE product_id = '12345'")
Cn.Execute("UPDATE temprow SET product_id = '34567'")
Cn.Execute("INSERT INTO product SELECT * FROM temprow")
Cn.Execute("DELETE temprow")
Cn.Execute("DROP TABLE temprow")