Insert into a row at specific position into SQL server table with PK - sql

I want to insert a row into a SQL server table at a specific position. For example my table has 100 rows and I want to insert a new row at position 9. But the ID column which is PK for the table already has a row with ID 9. How can I insert a row at this position so that all the rows after it shift to next position?

Relational tables have no 'position'. As an optimization, an index will sort rows by the specified key, if you wish to insert a row at a specific rank in the key order, insert it with a key that sorts in that rank position. In your case you'll have to update all rows with a value if ID greater than 8 to increment ID with 1, then insert the ID with value 9:
UPDATE TABLE table SET ID += 1 WHERE ID >= 9;
INSERT INTO TABLE (ID, ...) VALUES (9, ...);
Needless to say, there cannot possibly be any sane reason for doing something like that. If you would truly have such a requirement, then you would use a composite key with two (or more) parts. Such a key would allow you to insert subkeys so that it sorts in the desired order. But much more likely your problem can be solved exclusively by specifying a correct ORDER BY, w/o messing with the physical order of the rows.
Another way to look at it is to reconsider what primary key means: the identifier of an entity, which does not change during that entity lifetime. Then your question can be rephrased in a way that makes the fallacy in your question more obvious:
I want to change the content of the entity with ID 9 to some new
value. The old values of the entity 9 should be moved to the content
of entity with ID 10. The old content of entity with ID 10 should be
moved to the entity with ID 11... and so on and so forth. The old
content of the entity with the highest ID should be inserted as a new
entity.

Usually you do not want to use primary keys this way. A better approach would be to create another column called 'position' or similar where you can keep track of your own ordering system.
To perform the shifting you could run a query like this:
UPDATE table SET id = id + 1 WHERE id >= 9
This do not work if your column uses auto_increment functionality.

No, you can't control where the new row is inserted. Actually, you don't need to: use the ORDER BY clause on your SELECT statements to order the results the way you need.

DECLARE #duplicateTable4 TABLE (id int,data VARCHAR(20))
INSERT INTO #duplicateTable4 VALUES (1,'not duplicate row')
INSERT INTO #duplicateTable4 VALUES (2,'duplicate row')
INSERT INTO #duplicateTable4 VALUES (3,'duplicate rows')
INSERT INTO #duplicateTable4 VALUES (4,'second duplicate row')
INSERT INTO #duplicateTable4 VALUES (5,'second duplicat rows')
DECLARE #duplicateTable5 TABLE (id int,data VARCHAR(20))
insert into #duplicateTable5 select *from #duplicateTable4
delete from #duplicateTable4
declare #i int , #cnt int
set #i=1
set #cnt=(select count(*) from #duplicateTable5)
while(#i<=#cnt)
begin
if #i=1
begin
insert into #duplicateTable4(id,data) select 11,'indian'
insert into #duplicateTable4(id,data) select id,data from #duplicateTable5 where id=#i
end
else
insert into #duplicateTable4(id,data) select id,data from #duplicateTable5 where id=#i
set #i=#i+1
end
select *from #duplicateTable4

This kind of violates the purpose of a relational table, but if you need, it's not really that hard to do.
1) use ROW_NUMBER() OVER(ORDER BY NameOfColumnToSort ASC) AS Row to make a column for the row numbers in your table.
2) From here you can copy (using SELECT columnsYouNeed INTO ) the before and after portions of the table into two separate tables (based on which row number you want to insert your values after) using a WHERE Row < ## and Row >= ## statement respectively.
3) Next you drop the original table using DROP TABLE.
4) Then you use a UNION for the before table, the row you want to insert (using a single explicitly defined SELECT statement without anything else), and the after table. By now you have two UNION statements for 3 separate select clauses. Here you can just wrap this in a SELECT INTO FROM clause calling it the name of your original table.
5) Last, you DROP TABLE the two tables you made.
This is similar to how an ALTER TABLE works.

INSERT INTO customers
(customer_id, last_name, first_name)
SELECT employee_number AS customer_id, last_name, first_name
FROM employees
WHERE employee_number < 1003;
FOR MORE REF: https://www.techonthenet.com/sql/insert.php

Related

Return inserted row

How do I return the row I just inserted including DB-generated identifier?
My SQL is just a standard dynamic SQL insert
insert into dbo.TableName (Col1, Col2) values (#Col1, #Col2);
I have tried select from inserted, but inserted object is not a known object
insert into dbo.TableName (Col1, Col2) values (#Col1, #Col2); select * from inserted;
I have tried using output, but I cannot do that, when there is a trigger on the table
insert into dbo.TableName (Col1, Col2) output inserted.* values (#Col1, #Col2);
Any ideas?
You could insert the row and then use SCOPE_IDENTITY() to get the ID of the row you inserted and return the row with that id from the table.
For example:
INSERT INTO tableA VALUES (a1, a2);
DECLARE #Id INT = SCOPE_IDENTITY();
SELECT * FROM tableA WHERE ID = #Id
You can use OUTPUT clause:
--Sample table
CREATE TABLE IdentityInsert
(
ID int IDENTITY,
A int
)
INSERT IdentityInsert OUTPUT inserted.* VALUES (3)
There is no way to ask SQL Server which row was inserted last unless you are doing so in the same batch as the insert. For example, if your table has an IDENTITY column, you can use SCOPE_IDENTITY() (never use ##IDENTITY, since that can be unreliable if you have or will ever add triggers to the source table):
INSERT dbo.table(column) SELECT 1;
SELECT SCOPE_IDENTITY();
More generally, you can use the OUTPUT clause, which doesn't rely on an IDENTITY column (but will still make it difficult to identify which row(s) the clause identifies if there is no PK):
INSERT dbo.table(column) OUTPUT inserted.* SELECT 1;
If you're not talking about the same batch, then the only real way to identify the last row inserted is to use a date/time column where the timestamp of insertion is recorded. Otherwise it is like you emptied a bag of marbles on the floor, then asked someone to enter the room and identify which one hit the floor last.
You may be tempted or even advised to use the IDENT_CURRENT() function, but I explain here why this is unreliable too.
You could add a column to track this going forward:
ALTER TABLE dbo.table ADD DateInserted DEFAULT CURRENT_TIMESTAMP;
Now you can find the last row(s) inserted by simply:
;WITH x AS (SELECT *, r = RANK() OVER (ORDER BY DateInserted DESC)
FROM dbo.table)
SELECT * FROM x WHERE r = 1;
(If you don't want ties, you can add a tie-breaking column to the ORDER BY, or you can simply change RANK() to ROW_NUMBER() if you don't care which of the tied rows you get.)
You might make the assumption that the last row inserted is the highest identity value, but this isn't necessarily the case. The identity can be reseeded and it can also be overridden using SET IDENTITY_INSERT ON;.

INSERT OR REPLACE multiple rows, but there is no unique or primary keys

Hi I'm running into the following problem on SQlite3
I have a simple table
CREATE TABLE TestTable (id INT, cnt INT);
There are some rows already in the table.
I have some data I want to be inserted into the table: {(id0, cnt0), (id1, cnt1)...}
I want to insert data into the table, on id conflict, update TestTable.cnt = TestTable.cnt + value.cnt
(values.cnt is cnt0, cnt1 ... basically my data to be inserted)
*** But the problem is, there is no primary or unique constraint on id, and I am not allowed to change it!
What I currently have :
In my program I loop through all the values
UPDATE TestTABLE SET count = count + value.cnt WHERE id = value.id;
if (sqlite3_changes() == 0)
INSERT INTO MyTable (id, cnt) values (value.id, value.cnt);
But the problem is, with a very large dataset, doing 2 queries for each data entry takes too long. I'm trying to bundle multiple entries together into one call.
Please let me know if you have questions about my description, thank you for helping!
If you are able to create temporary tables, then do the following. Although I don't show it here, I suggest wrapping all this in a transaction. This technique will likely increase efficiency even if you are also able to add a temporary unique index. (In that case you could use an UPSERT with source data in the temporary table.)
CREATE TEMP TABLE data(id INT, cnt INT);
Now insert the new data into the temporary table, whether by using the host-language data libraries or crafting an insert statement similar to
INSERT INTO data (id, cnt)
VALUES (1, 100),
(2, 200),
(5, 400),
(7, 500);
Now update all existing rows using the single UPDATE statement. SQLite does not have a convenient syntax for joining tables and/or providing a source query for an UPDATE statement. However, one can use nested statement to provide similar convenience:
UPDATE TestTable AS tt
SET cnt = cnt + ifnull((SELECT cnt FROM data WHERE data.id == tt.id), 0)
WHERE tt.id IN (SELECT id FROM data);
Note that the two nested queries are independent of each other. In fact, one could eliminate the WHERE clause altogether and get the same results for this simple case. The WHERE clause is simply to make it more efficient, only attempting to update matching id's. The other subquery in the SET clause also specifies a match on id, but alone it would still allow updates of rows that don't have a match, defaulting to a null value and being converted to 0 (by isnull() function) for a no-op. By the way, without the isnull() function, the sum would result in null and would overwrite non-null values.
Finally, insert only rows with non-existing id values:
INSERT INTO TestTable (id, cnt)
SELECT data.id, data.cnt
FROM data LEFT JOIN TestTable
ON data.id == TestTable.id
WHERE TestTable.id IS NULL;

SQL Insert from one TVP into two tables, using scope identity from first for second table

I have SQL TVP object with multiple records (for example 2 records).
I need to insert these records into two almost identical tables, the only difference is that second table has one more column which is foreign key pointing to first table. So it should loop TVP records and insert one by one into both tables, but getting scope_identity() of inserted record in first table and use it for record in second table.
1st iteration
insert into first table
get scope_identity() of inserted record
insert into second table (using scope indentity from first table to fill additional column)
And so on, depending on how many records are in TVP.
How can I achieve this?
Obviously I have left out a ton of code since we don't have your column and table names etc. You want an ID value in your TVP so you can count rows and use it in a where clause and while loop.
Declare #Var1 Int
Declare #YourTVP YourTVPName
Declare #RowCounter Int = 1
While (1=1)
Insert Into YourTable1 (Column1, ...)
Select (Column1, ...)
From #YourTVP
Where #RowCounter = SomeIDColumn
#Var1 = Select ##SCOPE_IDENTITY()
Insert Into YourTable2 (Column1, ...)
(#Var1, ...)
If (Some logic to Break your While loop)
Break
Else #RowCounter = #RowCounter + 1
End
Ok, let me be more clear. I will give demonstrative example::
I have TVP (let name it as PersonTVP) containing FirstName and LastName columns and assume PersonTVP has two records.
I have two tables, Person and PersonExtra. Person table has Id, FirstName and LastName columns, and PersonExtra has same columns + one additional column PersonId.
I need to insert data from PersonTVP into these two tables. Flow should be:
Take record from PersonTVP and insert into Person table
Get Scope_Identity() of inserted record (the value from Id column)
Insert same record into PersonExtra table and use Scope_Identity() for PersonId column (additional column)
And so on, loop as long as PersonTVP has records.

How to have SQL increase ID by 1 on a column using an insert statement

I am trying to insert multiple rows into SQL. The table contains an external ID column that when using the APP increases this ID by one. The external ID is not the Primary key, but another ID in the table. Currently last external ID is 544. I want to insert 1600 additional rows and have the external ID increase by 1 for every row inserted. I have tried the following, but all of the external IDs end up being 100.
INSERT INTO tableA (externalid,tableuiduid)
VALUES ((select ISNULL(MAX(EXTERNALID) +1, 0)from tableA),newid());
I have also tried this, but it ends up inserting a duplicate external ID, as there are gaps in the numbers.
INSERT INTO tableA (ExternalID,tableAuid)
VALUES ((select count (externalid) + from tableA),newid());
Please let me know what I need to use to have this increase by 1 and not insert a duplicate ID.
The way you're doing it now, depending on your RDBMS, you can use an INSERT INTO ... SELECT statement:
INSERT INTO tableA (externalid, tableuiduid)
select ISNULL(MAX(EXTERNALID) + 1, 0), newid()
from tableA;
But you'll need to execute that 1600 times.
If you have an auxilliary number table or use a recursive CTE, you could use that to generate 1600 rows at once, but without knowing your RDBMS a precise implementation is very difficult.
You could define the field as an automatically incrementing field or sequence, but I get the impression that that isn't a good idea because you're not always going to be determining what the externalid value is.
You should use the +1 outside of ISNULL. And use INSERT INTO .. SELECT.
Try this way:
DECLARE #Cnt as int
SET #Cnt = 0
WHILE (#Cnt < 1600)
BEGIN
INSERT INTO tableA (externalid,tableuiduid)
select ISNULL(MAX(EXTERNALID),0) + 1,newid()
from tableA
SET #Cnt = #Cnt + 1
END
First create a sequence.
create sequence seq1
start with 544
increment by 1
maxvalue 99999;
Then insert.
INSERT INTO tableA (externalid)
VALUES (seq1.nextval())

Row number in Sybase tables

Sybase db tables do not have a concept of self updating row numbers. However , for one of the modules , I require the presence of rownumber corresponding to each row in the database such that max(Column) would always tell me the number of rows in the table.
I thought I'll introduce an int column and keep updating this column to keep track of the row number. However I'm having problems in updating this column in case of deletes. What sql should I use in delete trigger to update this column?
You can easily assign a unique number to each row by using an identity column. The identity can be a numeric or an integer (in ASE12+).
This will almost do what you require. There are certain circumstances in which you will get a gap in the identity sequence. (These are called "identity gaps", the best discussion on them is here). Also deletes will cause gaps in the sequence as you've identified.
Why do you need to use max(col) to get the number of rows in the table, when you could just use count(*)? If you're trying to get the last row from the table, then you can do
select * from table where column = (select max(column) from table).
Regarding the delete trigger to update a manually managed column, I think this would be a potential source of deadlocks, and many performance issues. Imagine you have 1 million rows in your table, and you delete row 1, that's 999999 rows you now have to update to subtract 1 from the id.
Delete trigger
CREATE TRIGGER tigger ON myTable FOR DELETE
AS
update myTable
set id = id - (select count(*) from deleted d where d.id < t.id)
from myTable t
To avoid locking problems
You could add an extra table (which joins to your primary table) like this:
CREATE TABLE rowCounter
(id int, -- foreign key to main table
rownum int)
... and use the rownum field from this table.
If you put the delete trigger on this table then you would hugely reduce the potential for locking problems.
Approximate solution?
Does the table need to keep its rownumbers up to date all the time?
If not, you could have a job which runs every minute or so, which checks for gaps in the rownum, and does an update.
Question: do the rownumbers have to reflect the order in which rows were inserted?
If not, you could do far fewer updates, but only updating the most recent rows, "moving" them into gaps.
Leave a comment if you would like me to post any SQL for these ideas.
I'm not sure why you would want to do this. You could experiment with using temporary tables and "select into" with an Identity column like below.
create table test
(
col1 int,
col2 varchar(3)
)
insert into test values (100, "abc")
insert into test values (111, "def")
insert into test values (222, "ghi")
insert into test values (300, "jkl")
insert into test values (400, "mno")
select rank = identity(10), col1 into #t1 from Test
select * from #t1
delete from test where col2="ghi"
select rank = identity(10), col1 into #t2 from Test
select * from #t2
drop table test
drop table #t1
drop table #t2
This would give you a dynamic id (of sorts)