Tricky problem with primary key in INSERT statement - sql

I have table in a SQL Server database with autoincrementing primary key [ID]. Is there some way to include the [ID] column in the INSERT statement so that the database would ignore it? Some trick with table configuration?
I am not working on PC (on Omron NJ PLC), so I can't write statements myself. Instead they are mapped from Structs. And, if it is possible, I want to use same Structs for both INSERT and SELECT (where I need [ID] for a later UPDATE). Also I have no desire of generating index myself. Although it would be lesser evil.

In SQL Server, you need to provide a value for columns that are inserted. There is a special value called DEFAULT that inserts the default value. However, it cannot be used with IDENTITY columns.
The normal insert method is to simply leave out the column:
insert into t (<all columns but id>)
values (<all values for other columns>);
Even a trigger on the tables doesn't get around this limitation, but there is a trick you can use:
Create a view on the table selecting all columns.
Create an instead of insert trigger on the table.
Insert into the view instead of the table.
This looks like:
create view v_t as
select * from t;
create trigger trig_v on v_t instead of insert as
begin
insert into t ( . . . ) -- all columns except id
select . . . -- all columns except id
from inserted;
end;
insert into v_t -- I recommend listing the columns but not required
values (NULL, . . . );
Here is a db<>fiddle.

If you want to insert explicit value for the ID on an identity, you can use the SET INSERT_IDENTITY ON statement before your insert. After that you will enable again the identity by the statement SET INSERT_IDENTITY OFF.
Hope this helps.

Related

How to insert an entry with an incremented value if the Table is empty [SQL]

i do have a Table: Example with an entry, that looks as the following:
Example
entryID: 0, number:1;
if you were to insert a new entry, with entryID, incremented, you would do something like:
INSERT INTO "Example" VALUES(SELECT MAX(entryID)+1 FROM "Example"), 2);
Question: What if the Example-Table is empty? How do you add logic to your sql to check, If there aren't any than add entryID = 0?
First, this code does not work in any database:
INSERT INTO "Example"
VALUES (SELECT MAX(entryID)+1 FROM "Example"), 2);
It is missing the parentheses around the subquery. This is more naturally written as INSERT . . . SELECT, rather than INSERT . . . VALUES. The narrow answer to your question is COALESCE():
INSERT INTO "Example" (EntryId, Number)
SELECT COALESCE(MAX(entryID) + 1, 1), 2
FROM "Example";
Note the column list in the INSERT. It is a best practice to always include those.
This is the narrow answer, because this is not the right way to have an incrementing id. You don't specify your database, but basically most databases have a syntax for incremental ids -- usually using syntax such as identity, auto_increment, serial, or generated always as. This is the right way to add an incremental id.
Why is it the right way? Your method can result in duplicates in a multi-threaded environment, where two inserts are made at the same time.

Creating New GUID automatically while inserting a new row to an existing table Not Working

I have an existing table in MS SQL called myTab.
It has the following fields
empno(PK) nchar(10),
age int
Now, i want to add a myGUID column and fill it up with a GUID whenever i insert a new row as well as Updating existing rows.
So i added the statement
ALTER TABLE myTab ADD myGUID uniqueidentifier DEFAULT NewId() NOT NULL;
Updating existing rows works correctly.
But, when i try to insert values,
INSERT INTO myTab VALUES ( 1000, 22 );
It fails, and gives the following message
**Column name or number of supplied values does not match table definition.**
When i do
insert into sourav_test2 values (20055711,23,NEWID());
The above statement works.
I want a GUID to be filled without changing the insert statement. Is it possible via a Trigger or a Function?
Always list the columns you are inserting!
INSERT INTO myTab (empno, age)
VALUES ('1000', 22);
Also use correct types for the values. Unmentioned columns will be assigned their default values, or NULL if there is no explicit default.
Your table has three columns, so if you leave out the column list, then the insert expects three values. You can still set a default, if you want by using the DEFAULT keyword in the VALUES clause:
INSERT INTO myTab (empno, age, myGUID)
VALUES ('1000', 22, DEFAULT);
Sourav's question about triggers got me thinking, so I tried a little test. Why?
Imagine a scenario where an application has already been written with thousands of INSERT statements that leave off the column list. In this case, if you could write an INSTEAD OF INSERT trigger that provides the column list, you could hopefully save yourself from correcting thousands of INSERT statements due to a newly added column.
Off the top of my head, I admittedly did not know if this could work.
So I wrote this little test:
CREATE TABLE tt (ColA varchar(1));
INSERT INTO tt VALUES ('a');
ALTER TABLE tt
ADD ColB uniqueidentifier DEFAULT NEWID();
GO
CREATE TRIGGER tr_tt
ON tt
INSTEAD OF INSERT
AS
INSERT INTO tt (ColA)
SELECT ColA FROM inserted;
GO
INSERT INTO tt VALUES ('a');
SELECT * FROM tt;
DROP TABLE tt;
I also tried a variation of the TRIGGER with the following INSERT just to be thorough:
INSERT INTO tt (ColA, ColB)
SELECT ColA, NEWID() FROM inserted;
The result was the same in both cases: The same error as reported in the question. So to answer the question:
Can't we use a trigger here which can do it?
The answer is NO. Even if you put an INSTEAD OF INSERT TRIGGER on the table, the parser will still not let you write an INSERT..VALUES() statement unless the number and order of VALUES exactly matches the definition of the table. A TRIGGER cannot be used to get around it.
Sooner or later, lazy coding exacts its price.

Inserting to one table, insert the ID to second table

Is it possible to populate a second table when I insert into the first table?
Insert post to table1 -> table 2 column recieves table1 post's unique id.
What I got so far, am I on the right track?
CONSTRAINT [FK_dbo.Statistics_dbo.News_News_NewsID] FOREIGN KEY ([News_NewsID]) REFERENCES [dbo].[News] ([NewsID])
Lots of ways:
an insert trigger
read SCOPE_IDENTITY() after the first insert, and use it to do a second
use the output clause to do an insert
Examples:
1:
create trigger Foo_Insert on Foo after insert
as
begin
set nocount on
insert Bar(fooid)
select id from inserted
end
go
insert Foo (Name)
values ('abc');
2:
insert Foo (Name)
values ('abc');
declare #id int = SCOPE_IDENTITY();
insert Bar(fooid)
select #id
3:
insert Bar(fooid)
select id from (
insert Foo (Name)
output inserted.id
values ('abc')) x
The only thing I can think of is that you can use a trigger to accomplish this. There is nothing "built in" to SQL Server that would do it. Why not just do it from your .NET code?
Yes it is, it sounds like you want a SQL Trigger, this would allow you to trigger logic based on actions on one table, to perform other actions in the DB. Here's another article on creating Simple SQL Triggers
SQL Server 2008 - Help writing simple INSERT Trigger
A Word of caution, this will do all the logic of updating the new table, outside of any C# code you write, it might sound nice to not have to manage it upfront, but you also lose control over when and if it happens.
So if you need to do something different later, now you have to update your regular code, as well as the trigger code. This type of logic can definitely grow, in large systems, and become a nightmare to maintain. Consider this, the alternative would be to build a method that adds the id to the new table after it inserts into the first table.
While i don't know what you're using to do your inserts assuming it's a SQL Command you can get back the ID on an identity column from the insert using Scope_Identity, found here
How to insert a record and return the newly created ID using a single SqlCommand?
if it's EF or some other ORM tool, they should either automatically update the entity, or have other mechanisms to deliver this data.

Delete a column from a table without changing the environment

I'm working on Oracle SQL database, quite big database. One of (among 150 tables) this table has to be changed because it's redundant (it can be generated through a join). I have been asked to delete a column from this table, to get rid of the redundancy. The problem is that now I have to change code everywhere someone made a insert/update/etc on this table (and don't forget the constraint!). I thought "I can make a view that do the right join" so the problem it's solved for all the select, but it's not working for the insert, because I'm updating 2 tables... Is there a way to solve this problem?
My goal is to rename my original table original_table in original_table_smaller (with one less column) and create a view (or something like a view) called original_table that work like the original table.
Is this possible?
As your view will contain one column that is not present in the real table, you will need to use an instead of trigger to make the view updateable.
Something like this:
create table smaller_table
(
id integer not null primary key,
some_column varchar(20)
);
create view real_table
as
select id,
some_column,
null as old_column
from smaller_table;
Now your old code would run something like this:
insert into real_table
(id, some_column, old_column)
values
(1, 'foo', 'bar');
which results in:
ORA-01733: virtual column not allowed here
To get around this, you need an INSTEAD OF trigger:
create or replace trigger comp_trigger
instead of insert on smaller_table
begin
insert into old_table
(id, some_column)
values
(:new.id, :new.some_column);
end;
/
Now the value for the "old_column" will be ignored. You need something similar for updates as well.
If your view contains a join, then you can handle that situation as well in the trigger. Simply do an update/insert according to the data to two different tables
For more details and examples, see the manual
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/triggers.htm#i1006376
It is possible to insert/update on views.
You might want to check with USER_UPDATABLE_COLUMNS which columns you can insert to the view.
Check with this query:
select * from user_updatable_columns where table_name = 'VIEW_NAME';
Oracle has two different ways of making views updatable:-
The view is "key preserved" with respect to what you are trying to update. This means the primary key of the underlying table is in the view and the row appears only once in the view. This means Oracle can figure out exactly which underlying table row to update OR
You write an instead of trigger.

Insert into ... Select *, how to ignore identity?

I have a temp table with the exact structure of a concrete table T. It was created like this:
select top 0 * into #tmp from T
After processing and filling in content into #tmp, I want to copy the content back to T like this:
insert into T select * from #tmp
This is okay as long as T doesn't have identity column, but in my case it does. Is there any way I can ignore the auto-increment identity column from #tmp when I copy to T? My motivation is to avoid having to spell out every column name in the Insert Into list.
EDIT: toggling identity_insert wouldn't work because the pkeys in #tmp may collide with those in T if rows were inserted into T outside of my script, that's if #tmp has auto-incremented the pkey to sync with T's in the first place.
SET IDENTITY_INSERT ON
INSERT command
SET IDENTITY_INSERT OFF
As identity will be generated during insert anyway, could you simply remove this column from #tmp before inserting the data back to T?
alter table #tmp drop column id
UPD: Here's an example I've tested in SQL Server 2008:
create table T(ID int identity(1,1) not null, Value nvarchar(50))
insert into T (Value) values (N'Hello T!')
select top 0 * into #tmp from T
alter table #tmp drop column ID
insert into #tmp (Value) values (N'Hello #tmp')
insert into T select * from #tmp
drop table #tmp
select * from T
drop table T
See answers here and here:
select * into without_id from with_id
union all
select * from with_id where 1 = 0
Reason:
When an existing identity column is selected into a new table, the new column inherits the IDENTITY property, unless one of the following conditions is true:
The SELECT statement contains a join, GROUP BY clause, or aggregate function.
Multiple SELECT statements are joined by using UNION.
The identity column is listed more than one time in the select list.
The identity column is part of an expression.
The identity column is from a remote data source.
If any one of these conditions is true, the column is created NOT NULL instead of inheriting the IDENTITY property. If an identity column is required in the new table but such a column is not available, or you want a seed or increment value that is different than the source identity column, define the column in the select list using the IDENTITY function. See "Creating an identity column using the IDENTITY function" in the Examples section below.
All credit goes to Eric Humphrey and bernd_k
Not with SELECT * - if you selected every column but the identity, it will be fine. The only way I can see is that you could do this by dynamically building the INSERT statement.
Just list the colums you want to re-insert, you should never use select * anyway. If you don't want to type them ,just drag them from the object browser (If you expand the table and drag the word, columns, you will get all of them, just delete the id column)
INSERT INTO #Table
SELECT MAX(Id) + ROW_NUMBER() OVER(ORDER BY Id)
set identity_insert on
Use this.
Might an "update where T.ID = #tmp.ID" work?
it gives me a chance to preview the data before I do the insert
I have joins between temp tables as part of my calculation; temp tables allows me to focus on the exact set data that I am working with. I think that was it. Any suggestions/comments?
For part 1, as mentioned by Kolten in one of the comments, encapsulating your statements in a transaction and adding a parameter to toggle between display and commit will meet your needs. For Part 2, I would needs to see what "calculations" you are attempting. Limiting your data to a temp table may be over complicating the situation.