Select from insert - postgresql

Is it possible to select from an insert statement? For example:
SELECT id FROM (INSERT INTO table (col1, col2) VALUES (val1, val2));
Where id is an autoincrementing primary key.

It's not possible in such way, because INSERT doesn't return virtual table for SELECT. However you could get id's actual value using currval(regclass) sequence function as:
SELECT currval('yourTableName_id_seq'::regclass);
currval
---------
1
(1 row)
EDIT:
Use RETURNING clause (available since PostgreSQL 8.2):
INSERT INTO yourTableName (col1, col2) VALUES ('aaa', 'bbb') RETURNING id;
id
----
2
(1 row)

"SELECT id FROM mytable WHERE id IS NULL;
"id" has to be an auto_increment column to make it work.
It will return the last_insert_id just as last_insert_id() is expected to do.
example:
mysql> INSERT INTO orders (customer_cust_id, orderdatetime, message, taxrate, shippingprice)
-> SELECT '1', NOW(), null, taxrate, shippingprice FROM customer
-> WHERE cust_id='1';"
http://dev.mysql.com/doc/refman/5.0/en/insert-select.html

Related

Return the number of rows with a where condition after an INSERT INTO? postgresql

I have a table that regroups some users and which event (as in IRL event) they've joined.
I have set up a server query that lets a user join an event.
It goes like this :
INSERT INTO participations
VALUES(:usr,:event_id)
I want that statement to also return the number of people who have joined the same event as the user. How do I proceed? If possible in one SQL statement.
Thanks
You can use a common table expression like this to execute it as one query.
with insert_tbl_statement as (
insert into tbl values (4, 1) returning event_id
)
select (count(*) + 1) as event_count from tbl where event_id = (select event_id from insert_tbl_statement);
see demo http://rextester.com/BUF16406
You can use a function, I've set up next example, but keep in mind you must add 1 to the final count because still transaction hasn't been committed.
create table tbl(id int, event_id int);
✓
insert into tbl values (1, 2),(2, 2),(3, 3);
3 rows affected
create function new_tbl(id int, event_id int)
returns bigint as $$
insert into tbl values ($1, $2);
select count(*) + 1 from tbl where event_id = $2;
$$ language sql;
✓
select new_tbl(4, 2);
| new_tbl |
| ------: |
| 4 |
db<>fiddle here

Copy content in TSQL

I need to copy content from one table to itself and related tables... Let me schematize the problem. Let's say I have two tables:
Order
OrderID : int
CustomerID : int
OrderName : nvarchar(32)
OrderItem
OrderItemID : int
OrderID : int
Quantity : int
With the PK being autoincremental.
Let's say I want to duplicate the content of one customer to another. How do I do that efficiently?
The problem are the PKs. I would need to map the values of OrderIDs from the original set of data to the copy in order to create proper references in OrderItem. If I just select-Insert, I won't be able to create that map.
Suggestions?
For duplicating one parent and many children with identities as the keys, I think the OUTPUT clause can make things pretty clean (SqlFiddle here):
-- Make a duplicate of parent 1, including children
-- Setup some test data
create table Parents (
ID int not null primary key identity
, Col1 varchar(10) not null
, Col2 varchar(10) not null
)
insert into Parents (Col1, Col2) select 'A', 'B'
insert into Parents (Col1, Col2) select 'C', 'D'
insert into Parents (Col1, Col2) select 'E', 'F'
create table Children (
ID int not null primary key identity
, ParentID int not null references Parents (ID)
, Col1 varchar(10) not null
, Col2 varchar(10) not null
)
insert into Children (ParentID, Col1, Col2) select 1, 'g', 'h'
insert into Children (ParentID, Col1, Col2) select 1, 'i', 'j'
insert into Children (ParentID, Col1, Col2) select 2, 'k', 'l'
insert into Children (ParentID, Col1, Col2) select 3, 'm', 'n'
-- Get one parent to copy
declare #oldID int = 1
-- Create a place to store new ParentID
declare #newID table (
ID int not null primary key
)
-- Create new parent
insert into Parents (Col1, Col2)
output inserted.ID into #newID -- Capturing the new ParentID
select Col1, Col2
from Parents
where ID = #oldID -- Only one parent
-- Create new children using the new ParentID
insert into Children (ParentID, Col1, Col2)
select n.ID, c.Col1, c.Col2
from Children c
cross join #newID n
where c.ParentID = #oldID -- Only one parent
-- Show some output
select * from Parents
select * from Children
Do you have to have the primary keys from table A as primaries in Table B? If not you can do a select statement with an insert into. Primary Key's are usually int's that start from an ever increasing seed (identity). Going around this and declaring an insert of this same data problematically has the disadvantage of someone thinking this is a distinct key set on this table and not a 'relationship' or foreign key value.
You can Select Primary Key's for inserts into other tables, just not themselves.... UNLESS you set the 'identity insert on' hint. Do not do this unless you know what this does as you can create more problems than it's worth if you don't understand the ramifications.
I would just do the ole:
insert into TableB
select *
from TableA
where (criteria)
Simple example (This assumes SQL Server 2008 or higher). My bad I did not see you did not list TSQL framework. Not sure if this will run on Oracle or MySql.
declare #Order Table ( OrderID int identity primary key, person varchar(8));
insert into #Order values ('Brett'),('John'),('Peter');
declare #OrderItem Table (orderItemID int identity primary key, OrderID int, OrderInfo varchar(16));
insert into #OrderItem
select
OrderID -- I can insert a primary key just fine
, person + 'Stuff'
from #Order
select *
from #Order
Select *
from #OrderItem
Add an extra helper column to Order called OldOrderID
Copy all the Order's from the #OldCustomerID to the #NewCustomerID
Copy all of the OrderItems using the OldOrderID column to help make the relation
Remove the extra helper column from Order
ALTER TABLE Order ADD OldOrderID INT NULL
INSERT INTO Order (CustomerID, OrderName, OldOrderID)
SELECT #NewCustomerID, OrderName, OrderID
FROM Order
WHERE CustomerID = #OldCustomerID
INSERT INTO OrderItem (OrderID, Quantity)
SELECT o.OrderID, i.Quantity
FROM Order o INNER JOIN OrderItem i ON o.OldOrderID = i.OrderID
WHERE o.CustomerID = #NewCustomerID
UPDATE Order SET OldOrderID = null WHERE OldOrderID IS NOT NULL
ALTER TABLE Order DROP COLUMN OldOrderID
IF the OrderName is unique per customer, you could simply do:
INSERT INTO [Order] ([CustomerID], [OrderName])
SELECT
2 AS [CustomerID],
[OrderName]
FROM [Order]
WHERE [CustomerID] = 1
INSERT INTO [OrderItem] ([OrderID], [Quantity])
SELECT
[o2].[OrderID],
[oi1].[Quantity]
FROM [OrderItem] [oi1]
INNER JOIN [Order] [o1] ON [oi1].[OrderID] = [o1].[OrderID]
INNER JOIN [Order] [o2] ON [o1].[OrderName] = [o2].[OrderName]
WHERE [o1].[CustomerID] = 1 AND [o2].[CustomerID] = 2
Otherwise, you will have to use a temporary table or alter the existing Order table like #LastCoder suggested.

T-SQL Delete Inserted Records

I know the title may seem strange but this is what I want to do:
I have table with many records.
I want to get some of this records and insert them in other table. Something like this:
INSERT INTO TableNew SELECT * FROM TableOld WHERE ...
The tricky part is that I want this rows that I have inserted to be deleted form the origin table as well.
Is there a easy way to do this, because the only think that I have managed to do is to use a temporary table for saving the selected records and then to put them in the second table and delete rows that match with them from the first table. It is a solution, but with so many records (over 3 millions and half) I am looking for some other idea...
In 2005+ use OUTPUT clause like this:
DELETE FROM TableOld
OUTPUT DELETED.* INTO TableNew
WHERE YourCondition
It will be performed in single transaction and either completed or roll back simultaneously
You can use the insert ... output clause to store the ID's of the copied rows in a temporary table. Then you can delete the rows from the original table based on the temporary table.
declare #Table1 table (id int, name varchar(50))
declare #Table2 table (id int, name varchar(50))
insert #Table1 (id,name)
select 1, 'Mitt'
union all select 2, 'Newt'
union all select 3, 'Rick'
union all select 4, 'Ron'
declare #copied table (id int)
insert #Table2
(id, name)
output inserted.id
into #copied
select id
, name
from #Table1
where name <> 'Mitt'
delete #Table1
where id in
(
select id
from #copied
)
select *
from #Table1
Working example at Data Explorer.
You should do some thing like this:
INSERT INTO "table1" ("column1", "column2", ...)
SELECT "column3", "column4", ...
FROM "table2"
WHERE ...
DELETE FROM "table1"
WHERE ...

What's wrong with this T-SQL MERGE statement?

I am new to MERGE, and I'm sure I have some error in my code.
This code will run and create my scenario:
I have two tables, one that is called TempUpsert that fills from a SqlBulkCopy operation (100s of millions of records) and a Sales table that holds the production data which is to be indexed and used.
I wish to merge the TempUpsert table with the Sales one
I am obviously doing something wrong as it fails with even the smallest example
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TempUpsert]') )
drop table TempUpsert;
CREATE TABLE [dbo].[TempUpsert](
[FirstName] [varchar](200) NOT NULL,
[LastName] [varchar](200) NOT NULL,
[Score] [int] NOT NULL
) ON [PRIMARY] ;
CREATE TABLE [dbo].[Sales](
[FullName] [varchar](200) NOT NULL,
[LastName] [varchar](200) NOT NULL,
[FirstName] [varchar](200) NOT NULL,
[lastUpdated] [date] NOT NULL,
CONSTRAINT [PK_Sales] PRIMARY KEY CLUSTERED
(
[FullName] ASC
)
---- PROC
CREATE PROCEDURE [dbo].[sp_MoveFromTempUpsert_to_Sales]
(#HashMod int)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
MERGE Sales AS trget
USING (
SELECT
--- Edit: Thanks to Mikal added DISTINCT
DISTINCT
FirstName, LastName , [Score], LastName+'.'+FirstName AS FullName
FROM TempUpsert AS ups) AS src (FirstName, LastName, [Score], FullName)
ON
(
src.[Score] = #hashMod
AND
trget.FullName=src.FullName
)
WHEN MATCHED
THEN
UPDATE SET trget.lastUpdated = GetDate()
WHEN NOT MATCHED
THEN INSERT ([FullName], [LastName], [FirstName], [lastUpdated])
VALUES (FullName, src.LastName, src.FirstName, GetDate())
OUTPUT $action, Inserted.*, Deleted.* ;
--print ##rowcount
END
GO
--- Insert dummie data
INSERT INTO TempUpsert (FirstName, LastName, Score)
VALUES ('John','Smith',2);
INSERT INTO TempUpsert (FirstName, LastName, Score)
VALUES ('John','Block',2);
INSERT INTO TempUpsert (FirstName, LastName, Score)
VALUES ('John','Smith',2); --make multiple on purpose
----- EXECUTE PROC
GO
DECLARE #return_value int
EXEC #return_value = [dbo].[sp_MoveFromTempUpsert_to_Sales]
#HashMod = 2
SELECT 'Return Value' = #return_value
GO
This returns:
(1 row(s) affected)
(1 row(s) affected)
(1 row(s) affected)
Msg 2627, Level 14, State 1, Procedure sp_MoveFromTempUpsert_to_Sales, Line 12
Violation of PRIMARY KEY constraint 'PK_Sales'. Cannot insert duplicate key in object
'dbo.Sales'. The statement has been terminated.
(1 row(s) affected)
What am I doing wrong please?
Greatly appreciated
The first two rows in your staging table will give you the duplicate PK. violation. Conc is the PK and you insert tmain+dmain with the same value twice.
In Summation
MERGE requires its input (Using) to be duplicates free
the Using is a regular SQL statement, so you can use Group By, distinct and having as well as Where clauses.
My final Merge looks like so :
MERGE Sales AS trget
USING (
SELECT FirstName, LastName, Score, LastName + '.' + FirstName AS FullName
FROM TempUpsert AS ups
WHERE Score = #hashMod
GROUP BY FirstName, LastName, Score, LastName + '.' + FirstName
) AS src (FirstName, LastName, [Score], FullName)
ON
(
-- src.[Score] = #hashMod
--AND
trget.FullName=src.FullName
)
WHEN MATCHED
THEN
UPDATE SET trget.lastUpdated = GetDate()
WHEN NOT MATCHED
THEN INSERT ([FullName], [LastName], [FirstName], [lastUpdated])
VALUES (FullName, src.LastName, src.FirstName, GetDate())
OUTPUT $action, Inserted.*, Deleted.* ;
--print ##rowcount
END
And it works!
Thanks to you all :)
Without DISTINCT or proper AGGREGATE function in subquery used in USING part of MERGE there will be two rows which suits criteria used in ON part of MERGE, which is not allowed. (Two John.Smith)
AND
Move the condition src.[Score] = #hashMod inside the subquery,
instead if ON clause not succeed, for example John.Smith have score of 2, and #HashMod = 1 - then if you already have the row with John.Smith in target table - you'll get an error with Primary Key Constraint

SELECT or INSERT a row in one command

I'm using PostgreSQL 9.0 and I have a table with just an artificial key (auto-incrementing sequence) and another unique key. (Yes, there is a reason for this table. :)) I want to look up an ID by the other key or, if it doesn't exist, insert it:
SELECT id
FROM mytable
WHERE other_key = 'SOMETHING'
Then, if no match:
INSERT INTO mytable (other_key)
VALUES ('SOMETHING')
RETURNING id
The question: is it possible to save a round-trip to the DB by doing both of these in one statement? I can insert the row if it doesn't exist like this:
INSERT INTO mytable (other_key)
SELECT 'SOMETHING'
WHERE NOT EXISTS (SELECT * FROM mytable WHERE other_key = 'SOMETHING')
RETURNING id
... but that doesn't give the ID of an existing row. Any ideas? There is a unique constraint on other_key, if that helps.
Have you tried to union it?
Edit - this requires Postgres 9.1:
create table mytable (id serial primary key, other_key varchar not null unique);
WITH new_row AS (
INSERT INTO mytable (other_key)
SELECT 'SOMETHING'
WHERE NOT EXISTS (SELECT * FROM mytable WHERE other_key = 'SOMETHING')
RETURNING *
)
SELECT * FROM new_row
UNION
SELECT * FROM mytable WHERE other_key = 'SOMETHING';
results in:
id | other_key
----+-----------
1 | SOMETHING
(1 row)
No, there is no special SQL syntax that allows you to do select or insert. You can do what Ilia mentions and create a sproc, which means it will not do a round trip fromt he client to server, but it will still result in two queries (three actually, if you count the sproc itself).
using 9.5 i successfully tried this
based on Denis de Bernardy's answer
only 1 parameter
no union
no stored procedure
atomic, thus no concurrency problems (i think...)
The Query:
WITH neworexisting AS (
INSERT INTO mytable(other_key) VALUES('hello 2')
ON CONFLICT(other_key) DO UPDATE SET existed=true -- need some update to return sth
RETURNING *
)
SELECT * FROM neworexisting
first call:
id|other_key|created |existed|
--|---------|-------------------|-------|
6|hello 1 |2019-09-11 11:39:29|false |
second call:
id|other_key|created |existed|
--|---------|-------------------|-------|
6|hello 1 |2019-09-11 11:39:29|true |
First create your table ;-)
CREATE TABLE mytable (
id serial NOT NULL,
other_key text NOT NULL,
created timestamptz NOT NULL DEFAULT now(),
existed bool NOT NULL DEFAULT false,
CONSTRAINT mytable_pk PRIMARY KEY (id),
CONSTRAINT mytable_uniq UNIQUE (other_key) --needed for on conflict
);
you can use a stored procedure
IF (SELECT id FROM mytable WHERE other_key = 'SOMETHING' LIMIT 1) < 0 THEN
INSERT INTO mytable (other_key) VALUES ('SOMETHING')
END IF
I have an alternative to Denis answer, that I think is less database-intensive, although a bit more complex:
create table mytable (id serial primary key, other_key varchar not null unique);
WITH table_sel AS (
SELECT id
FROM mytable
WHERE other_key = 'test'
UNION
SELECT NULL AS id
ORDER BY id NULLS LAST
LIMIT 1
), table_ins AS (
INSERT INTO mytable (id, other_key)
SELECT
COALESCE(id, NEXTVAL('mytable_id_seq'::REGCLASS)),
'test'
FROM table_sel
ON CONFLICT (id) DO NOTHING
RETURNING id
)
SELECT * FROM table_ins
UNION ALL
SELECT * FROM table_sel
WHERE id IS NOT NULL;
In table_sel CTE I'm looking for the right row. If I don't find it, I assure that table_sel returns at least one row, with a union with a SELECT NULL.
In table_ins CTE I try to insert the same row I was looking for earlier. COALESCE(id, NEXTVAL('mytable_id_seq'::REGCLASS)) is saying: id could be defined, if so, use it; whereas if id is null, increment the sequence on id and use this new value to insert a row. The ON CONFLICT clause assure
that if id is already in mytable I don't insert anything.
At the end I put everything together with a UNION between table_ins and table_sel, so that I'm sure to take my sweet id value and execute both CTE.
This query needs to search for the value other_key only once, and is a "search this value" not a "check if this value not exists in the table", that is very heavy; in Denis alternative you use other_key in both types of searches. In my query you "check if a value not exists" only on id that is a integer primary key, that, for construction, is fast.
Minor tweak a decade late to Denis's excellent answer:
-- Create the table with a unique constraint
CREATE TABLE mytable (
id serial PRIMARY KEY
, other_key varchar NOT NULL UNIQUE
);
WITH new_row AS (
-- Only insert when we don't find anything, avoiding a table lock if
-- possible.
INSERT INTO mytable ( other_key )
SELECT 'SOMETHING'
WHERE NOT EXISTS (
SELECT *
FROM mytable
WHERE other_key = 'SOMETHING'
)
RETURNING *
)
(
-- This comes first in the UNION ALL since it'll almost certainly be
-- in the query cache. Marginally slower for the insert case, but also
-- marginally faster for the much more common read-only case.
SELECT *
FROM mytable
WHERE other_key = 'SOMETHING'
-- Don't check for duplicates to be removed
UNION ALL
-- If we reach this point in iteration, we needed to do the INSERT and
-- lock after all.
SELECT *
FROM new_row
) LIMIT 1 -- Just return whatever comes first in the results and allow
-- the query engine to cut processing short for the INSERT
-- calculation.
;
The UNION ALL tells the planner it doesn't have to collect results for de-duplication. The LIMIT 1 at the end allows the planner to short-circuit further processing/iteration once it knows there's an answer available.
NOTE: There is a race condition present here and in the original answer. If the entry does not already exist, the INSERT will fail with a unique constraint violation. The error can be suppressed with ON CONFLICT DO NOTHING, but the query will return an empty set instead of the new row. This is a difficult problem because getting that info from another transaction would violate the I in ACID.