Earlz
Earlz

Reputation: 63845

Clone a row with a primary key in SQL?

I am attempting to make some cross-DB(SQL Server and PostgreSQL) compatible SQL. What I am needing to do is clone a row. It is preferable to do it locally on the SQL server without having to pull the entire row down to our client and reinsert it. Also, we prefer to not have to create a new SQL query from scratch putting in column names dynamically, but this is an option I suppose.

For example: We have an address table in it is a table like so: address_rid: integer, primary key, auto-incrementing, not null address1: varchar(100) address2: varchar(100)

Well, our goal is to just be able to clone one of these rows so that address1 and address2 are the same, yet the address_rid would be set to the next value as usual.

How would you go about doing this?

Also, I have seen Quickest way to clone row in SQL

which has


INSERT INTO PrimKeys 
SELECT 'PrimKey2' AS PrimKey,* 
  FROM PrimKeys 
 WHERE PrimKey='PrimKey1'

I'm having trouble getting something like this to work on postgres and also to use something like default on the primary key that gets inserted.

edit: also, I'd just as well take 2 separate queries that will do this on SQL Server and Postgres, though I'd prefer to just have to maintain 1 query.

and I am using the C# ado.net as my client.

Upvotes: 3

Views: 2182

Answers (3)

user749687
user749687

Reputation:

A bit late to the party, but this function may help people.

Create aggregate function to delimit text values in result, comma delimiter will be use to get column list.

CREATE OR REPLACE FUNCTION concat_delimited( TEXT, TEXT, TEXT ) RETURNS TEXT AS $$
  SELECT $1 || (CASE WHEN $1 = '' THEN '' ELSE $3 END) || $2;
$$
LANGUAGE SQL STRICT IMMUTABLE;

CREATE AGGREGATE delimited_list_of ( TEXT, TEXT ) (
  SFUNC = concat_delimited,
  STYPE = TEXT,
  INITCOND = ''
);

Create function to duplicate records

 CREATE OR REPLACE FUNCTION  duplicate(_schema text,_tbl text, _id integer)
 RETURNS integer AS 
 $BODY$
 DECLARE cols text;
   sql  text;
   result integer;
 BEGIN
   SELECT into  cols  delimited_list_of(column_name, ',') 
   FROM   information_schema.columns 
   WHERE  table_schema =  _schema  AND table_name = _tbl  AND  column_name != 'id';

   raise notice 'Cols: %', cols;
   sql := 'insert into ' || quote_ident(_schema) || '.' || quote_ident(_tbl) || '('  
       || cols ||  ') SELECT ' || cols || ' FROM ' || quote_ident(_schema) || '.' 
       || quote_ident(_tbl) || ' WHERE id = ' || _id || ' RETURNING id' ;

    raise notice 'Query: %', sql;
    EXECUTE sql  into result;
    RETURN result;
 END;
 $BODY$
   LANGUAGE  plpgsql VOLATILE  COST 100;

From there just pass in the schema name, table name and id of the record to be duplicated cloned and it will return the id of the created record. E.g

select  duplicate('_schema','test_tbl',500001);

you will need to change the field name id to whatever you primary/unique field name is.

Upvotes: 0

OMG Ponies
OMG Ponies

Reputation: 332601

Postgres, like Oracle, uses sequences to generate sequencial numeric values for artificial/surrogate keys. In order to get the next value, you need to use:

NEXTVAL(your_sequence_name) 

...in your INSERT statement in order to set the value for the primary key. You won't be able to omit it from your INSERT statement, unless for some odd reason the column is nullable. So on Postgres you need to use:

INSERT INTO PrimKeys 
SELECT NEXTVAL(your_sequence_name),
       [rest of columns, because you can't include the pk col/value]
  FROM PrimKeys 
 WHERE PrimKey='PrimKey1'

Equivalent SQL Server Functionality

SQL Server (and MySQL) allow you to either not specify the column, or supply NULL as a placeholder for the primary key column and will provide the value for you. That's as close as you get.

Reference:

Upvotes: 2

Joshua Cauble
Joshua Cauble

Reputation: 1349

I'm not sure about PostgreSQL but if it supports the auto-increment like most systems do then just leave the primary key field blank. However this does mean you need to specify the column names.

Also, when you say clone it sounds like you want a duplicate of your record but in your description the address1 and address2 columns are on the same row of data.

If you want to replicate your address1 to the address2 column you can use a simple update statement.

If you are trying to clone the entire row to have two records that are exactly the same then you could use something like this and it should work on both environments.

INSERT INTO Addresses
( address1, address2 )
SELECT address1, address2
  FROM Addresses
 WHERE addressId = ID

Now the next question is, Are you cloning from one DB type to the other? If so then you will need to pull it into your app anyway. If you are then why don't you use something like nHibernate to abstract the db layer away from you.

If the goal is to just make a copy then you can do it straight in the db with the sql above.

While this sql is probably the easiest (and you could reflect it from the db if needed using system / master tables) you would still need to specify the column names. The primary reason is that as soon as you add the * wildcard it would still try to include your original primary key column in the select list which would then have 1 extra column to what you have.

Another issue to be aware of when using wildcards in insert statements is that your column orders HAVE TO MATCH otherwise you will get very unexpected results.

Upvotes: 3

Related Questions