INSERT into table with WITH clause not working in postgres - sql

Sorry for the followup question (from INSERT into table if doesn't exists and return id in both cases)
But I couldn't find any solution for my questions.
I have a feedback table whose columns are foreign key of other tables. For ex. scopeid is foregin key of id column in scope table, similarly userid is foreign key of id column from user table and so on.
So, I am trying to insert following data in the table:
scope: home_page,
username: abc
status: fixed
app: demoapp
So, to insert above data, I am trying to write subquery to get the id of each value and use that. Also if that value doesn't exists insert and use the new ID to insert that in feedback table.
So basically I am trying to insert into multiple table (if something doesnt exists) and use those ID to insert into final table which is feedback table.
Hope things are much clearer now.
Here is my feedback table:
id scopeid comment rating userid statusid appid
3 1 test 5 2 1 2
All the id columns are foreign key of other tables and so in my below query I am trying to get the id by name and if not exists add those.
Here is my final query:
INSERT INTO feedbacks (scopeid, comment, rating, userid, statusid, appid)
VALUES
(
-- GET SCOPE ID
(
WITH rows_exists AS (
SELECT id FROM scope
WHERE appid=2 AND NAME = 'application'),
row_new AS (INSERT INTO scope (appid, NAME) SELECT 2, 'application' WHERE NOT EXISTS (SELECT id FROM scope WHERE appid=2 AND name='application') returning id)
SELECT id FROM rows_exists UNION ALL SELECT id FROM row_new
),
-- Comment
'GOD IS HERE TO COMMENT',
-- rating
5,
-- userid
(
WITH rows_exists AS (
SELECT id FROM users
WHERE username='abc'),
row_new AS (INSERT INTO users (username) SELECT 'abc' WHERE NOT EXISTS (SELECT id FROM users WHERE username='abc') returning id)
SELECT id FROM rows_exists UNION ALL SELECT id FROM row_new
),
-- statusid
(SELECT id FROM status WHERE NAME='received'),
-- appid
(
WITH rows_exists AS (
SELECT id FROM apps
WHERE name='google'),
row_new AS (INSERT INTO apps (name) SELECT 'google' WHERE NOT EXISTS (SELECT id FROM apps WHERE NAME='google') returning id)
SELECT id FROM rows_exists UNION ALL SELECT id FROM row_new
)
)
But I get following Error:
with clause containing a data-modifying statement must be at the top level
Is that even possible what I am trying to achieve by this way or other method.

The following inserts ids that don't exist and then inserts the resulting id:
with s as (
select id
from scope
where appid = 2 AND NAME = 'application'
),
si as (
insert into scope (appid, name)
select v.appid, v.name
from (values (2, 'application')) v(appid, name)
where not exists (select 1 from scope s where s.appid = v.appid and s.name = v.name)
returning id
),
. . . similar logic for other tables
insert into feedback (scopeid, comment, . . . )
select (select id from s union all select id from is) as scopeid,
'test' as comment,
. . .;
You should be sure you have unique constraints in each of the table for the values you are looking for. Otherwise, you could have a race condition and end up inserting the same row multiple times in a multithreaded environment.

Related

Unique ID using function with every record in Insert statement

I have a statement in stored procedure
INSERT into table(ID, name, age)
SELECT fnGetLowestFreeID(), name, age
FROM #tempdata
The function fnGetLowestFreeID() gets the lowest free ID of the table table.
I want to insert unique ID with every record in the table. I have tried iteration and transaction. But they aren't fitting the scenario.
I cannot use Identity Column. I have this restriction of using IDs between 0-4 and assigning the lowest free ID using that function. In case of returned ID greater than 4, the function is returning an error. Suppose there are already 1 and 2 in the table. The function will return 0 and I have to assign this ID to the new record, 3 to the next record and so on on the basis of number of records in the #tempdata.
try this
CREATE TABLE dbo.Tmp_City(Id int NOT NULL IDENTITY(1, 1),
Name varchar(50) , Country varchar(50), )
OR
ALTER TABLE dbo.Tmp_City
MODIFY COLUMN Id int NOT NULL IDENTITY(1, 1)
OR
Create a Sequence and assign Sequence.NEXTVAL as ID
in the insert statement
You can make use of a rank function like row_number and do something like this.
INSERT into table(ID, name, age)
SELECT row_number() over (order by id) + fnGetLowestFreeID(), name, age
FROM #tempdata
Here are 3 scenarios-
1)Show the function which you are using
2) Doesn't make sense to use a function and make it unique
still- you can use rank-
INSERT into table(ID, name, age)
SELECT row_number() over (order by id) + fnGetLowestFreeID(), name, age
FROM #tempdata
3)Else, get rid of function and use max(id)+1 because you dont want to use identitiy column
You could use a Numbers table to join the query doing your insert. You can google the concept for more info, but essentially you create a table (for example with the name "Numbers") with a single column "nums" of some integer type, and then you add some amount of rows, starting with 0 or 1, and going as far as you need. For example, you could end with this table content:
nums
----
0
1
2
3
4
5
6
Then you can use such a table to modify your insert, you don't need the function anymore:
INSERT into table(ID, name, age)
SELECT t2.nums, t.name, t.age
FROM (
SELECT name, age, row_number() over (order by name) as seq
FROM #tempdata
) t
INNER JOIN (
SELECT n.nums, row_number() over (order by n.nums) as seq
FROM Numbers n
WHERE n.nums < 5 AND NOT EXISTS (
SELECT * FROM table WHERE table.ID = n.nums
)
) t2 ON t.seq = t2.seq
Now, this query leaves out one of your requirements, that would be launching an error when no slots are available, but that is easy to fix. You can do previously a query and test if the count of records in table plus the sum of records in #tempdata is higher than 5. If so, you launch the error as you know there would not be enough free slots for the records in #tempdata.
Side note: table looks like a terrible name for a table, I hope that in your real code you have a meaningful name :)

Returning PK from table, where FK appears more than once

I was asked to return all property_id's (PK), where the customer owns more than one property (property_id, FK). Here is the table for visual reference:
How would I do this in valid SQL syntax? My thought process went something like this:
SELECT property_id FROM CustomerProperties
WHERE COUNT(property_id) > 1
Which is clearly not valid, but that's my OOP thought process.
If I understand your question correctly, the expected results from your sample table image would be 1, 4.
Something like this should get you started.
DECLARE #Test TABLE
(
PropertyId INT PRIMARY KEY,
CustomerId INT
);
INSERT #Test ( PropertyId, CustomerId )
VALUES ( 1, 1 ), ( 2, 2 ), (3, 3 ), ( 4, 1 );
SELECT
PropertyId
FROM
#Test
WHERE
CustomerId IN
(
SELECT
CustomerId
FROM
#Test
GROUP BY
CustomerId
HAVING COUNT(*) > 1
);
The second part of the query finds customers that have more than one property.
Group by the column you want to be unique and select only those groups having a count > 1
SELECT property_id
FROM CustomerProperties
GROUP BY property_id
HAVING COUNT(*) > 1
When you group then all aggregate functions like COUNT() apply to each group and not the complete result set. And the HAVING clause is used for for group wide conditions while the WHERE clause is for record conditions.

INSERT INTO ... FROM SELECT ... RETURNING id mappings

I'm using PostgreSQL 9.3.
I want to duplicate some of the db records. Since I'm using an auto-increment pk id for the table, I want to get back the id mappings from the generated ids of duplicated records to the original ones. For example, say I have a table posts with 2 records in it:
[{'id': 1, 'title': 'first'}
, {'id': 2. 'title': 'second'}]
With SQL:
INSERT INTO posts (title) SELECT title FROM posts RETURNING id, ??
I expect to see mappings like:
[{'id': 3, 'from_id': 1}
, {'id': 4, 'from_id': 2}]
Any idea on how to fill in the question marks above to make it work? Thanks a lot!
This would be simpler for UPDATE, where additional rows joined into the update are visible to the RETURNING clause:
Return pre-UPDATE column values using SQL only
The same is currently not possible for INSERT. The manual:
The expression can use any column names of the table named by table_name
table_name being the target of the INSERT command.
You can use (data-modifying) CTEs to get this to work.
Assuming title to be unique per query, else you need to do more:
WITH sel AS (
SELECT id, title
FROM posts
WHERE id IN (1,2) -- select rows to copy
)
, ins AS (
INSERT INTO posts (title)
SELECT title FROM sel
RETURNING id, title
)
SELECT ins.id, sel.id AS from_id
FROM ins
JOIN sel USING (title);
If title is not unique per query (but at least id is unique per table):
WITH sel AS (
SELECT id, title, row_number() OVER (ORDER BY id) AS rn
FROM posts
WHERE id IN (1,2) -- select rows to copy
ORDER BY id
)
, ins AS (
INSERT INTO posts (title)
SELECT title FROM sel ORDER BY id -- ORDER redundant to be sure
RETURNING id
)
SELECT i.id, s.id AS from_id
FROM (SELECT id, row_number() OVER (ORDER BY id) AS rn FROM ins) i
JOIN sel s USING (rn);
This second query relies on the undocumented implementation detail that rows are inserted in the order provided. It works in all current versions of Postgres and is probably not going to break.
db<>fiddle here
Old sqlfiddle
if id column of posts is serial type, it's generated like nextval('posts_id_seq'::regclass),
you can manually call this function for every new row
with
sel as (
SELECT id, title, nextval('posts_id_seq'::regclass) new_id
FROM posts
WHERE id IN (1,2)
),
ins as (
INSERT INTO posts (id, title)
SELECT new_id, title
FROM sel
)
SELECT id, new_id
FROM sel
it'l works with any data, include non-unique title
The simplest solution IMHO would be to simply add a column to your table where you could put id of the row that was cloned.

Remove Duplicate Row Data

I need to remove duplicates from my table (user_info). I always want to remove the row with the id column that is lower of the two rows being returned from my select/having query below. Any ideas on how to write the delete statement to remove the duplicates (lower user_info.id column) from the results of my select/having query below? I'm using Oracle 11g.
user_info table structure:
id (unique primary key number 10 generated by sequence)
user_id (number 10)
first_name (varchar2)
last_name (varchar2)
data example:
id user_id
______ ___________
37265 1455
265798 1455
sql to show duplicates:
select user_id, count(*)
from user_info
group by user_id
HAVING count(*) > 1
You can use the following query:
DELETE
FROM user_info
WHERE id NOT IN
(SELECT MAX(id)
FROM user_info
GROUP BY user_id);
This query will delete all the duplicate rows except the user_id row with maximum id.
Here's a SQL Fiddle which demonstrates the delete.
Start with this to show you only the duplicates
Select user_id, count(*) NumRows, Min(Id) SmallestId, Max(Id) LargestId
From user_info
Group by user_id
HAVING count(*) > 1
This will show you the min and max for each user_id (with the same value for SmallestId and LargestId if there are no duplicates.
Select user_id, count(*) NumRows, Min(Id) SmallestId, Max(Id) LargestId
From user_info
Group by user_id
For a User, you want to keep the MaxId and Delete everything else. So you can write a DELETE statement to be
DELETE From user_info
Where Id Not IN
(
Select Max(Id)
From user_info
Group by user_id
)
This will get the
drop table test;
/
create table test
(
ids number,
user_id number
);
/
insert into test
values(37265,1455);
/
insert into test
values(265798,1455);
/
select * from test;
delete from test t
where t.ids < (select max(ids) from test t1 where T1.USER_ID= T.USER_ID)
This query employs the sub query to do the same !

How to get one unique record from the same list of records from table? No Unique constraint in the table

I have one query in SQL Server output,
Suppose i have one table (Ex.StudentMaster) having some fields-No unique constraints.
For Ex. RollNumber and Name
The table has same same data. For ex:
RollNo Name
1 Yoko
1 Yoko
1 Yoko
I want to get only third record. How can i identify this unique record?
Any row is a third row :-)
create table test
(
n int,
name varchar(30)
);
insert into test values(1,'yoko'),(1,'yoko'),(1,'yoko');
select ROW_NUMBER() over(order by name) as ordinal, * from test;
Deleting the "third" row :-)
with a as
(
select ROW_NUMBER() over(order by name) as ordinal, * from test
)
delete from a where a.ordinal = 3
Deleting the last row:
with a as
(
select ROW_NUMBER() over(order by name) as ordinal, * from test
)
delete from a where a.ordinal = (select MAX(ordinal) from a)
You can use DISTINCT which return's distinct combination's of columns.
SELECT DISTINCT RollNo, Name
FROM mytable