Postgresql - Insert when select return something - sql

Is it possible to run select query, check if row exist and then insert some values? I would like to do that in one query. I think about SELECT .. CASE .. THEN, for example:
SELECT user_id, CASE when user_id > 0 then (INSERT INTO another_table ...) ELSE return 0 END
FROM users WHERE user_id = 10
Now I'm able to do that with 2 queries, first do SELECT and second INSERT values (if first query return something).
Thanks!

in general the construct is:
INSERT INTO another_table
SELECT value1,value2..etc
where exists (SELECT user_id FROM users WHERE user_id = 10)
or in this particular case:
INSERT INTO another_table
SELECT value1,value2..etc
FROM users WHERE user_id = 10
If no such user, no rows will be selected and so inserted

Related

How to correct my Snowflake Unique Constraint SQL statement?

I have a table that looks like:
ID|CREATED |VALUE
1 |1649122158|200
1 |1649122158|200
1 |1649122158|200
That I'd like to look like:
ID|CREATED |VALUE
1 |1649122158|200
And I run the following query:
DELETE FROM MY_TABLE T USING (SELECT ID,CREATED,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY CREATED DESC) AS RANK_IN_KEY FROM MY_TABLE T) X WHERE X.RANK_IN_KEY <> 1 AND T.ID = X.ID AND T.CREATED = X.CREATED
But it removes everything from MY_TABLE and not just other rows with the same value. This is more than just selecting distinct records, I'd like to enforce a unique constraint to get the latest value of ID and keep just one record for it, even if there were duplicates.
So
ID|CREATED |VALUE
1 |1649122158|200
1 |1649122159|300
2 |1649122158|200
2 |1649122158|200
3 |1649122170|500
3 |1649122160|200
Would become (using the same final unique constraint statement):
ID|CREATED |VALUE
1 |1649122159|300
2 |1649122158|200
3 |1649122170|500
How can I improve my logic to properly handle these unique constraint modifications?
Check out this post: https://community.snowflake.com/s/question/0D50Z00008EJgemSAD/how-to-delete-duplicate-records-
If all columns make up a unique records, the recommended solution is the insert all the records into a new table with SELECT DISTINCT * and do a swap. You could also do a INSERT OVERWRITE INTO the same table.
Something like INSERT OVERWRITE INTO tableA SELECT DISTINCT * FROM tableA;
The following setup should leave rows with id of 1 and 3. And not delete all rows as you say.
Schema
create table t (
id int,
created int ,
value int
);
insert into t values(1, 1649122158, 200);
insert into t values(1 ,1649122159, 300);
insert into t values(2 ,1649122158, 200);
insert into t values(2 ,1649122158, 200);
insert into t values(3 ,1649122170, 500);
insert into t values(3 ,1649122160, 200);
Delete statement
with x as (
SELECT
id, created,
row_number() over(partition by id) as r
FROM t
)
delete from t
using x
where x.id = t.id and x.r <> 1 and x.created = t.created
;
Output
select * from t;
1 1649122158 200
3 1649122170 500
The logic is such, that the table in the using clause is joined with the operated on table. Following the join logic, it just matches by some key. In your case, you have key as {id,created}. This key is duplicated for rows with id of 2. So the whole group is deleted.
I'm no savvy in database schemas. But as a thought, you may add a row with a rank to existing table. And after that you can proceed with deletion. This way you do not need to create other table and insert values to that. Be warned that data may become fragmented(physically, on disks). So you will need to run some kind of tune up later.
Update
You may find this almost one-liner interesting:
SO answer
I will duplicate code here, as it is so small and well written.
WITH
u AS (SELECT DISTINCT * FROM your_table),
x AS (DELETE FROM your_table)
INSERT INTO your_table SELECT * FROM u;

Insert with returning a subquery

I am trying to insert a record in an m:n table (User-Group Relation) and return the group when the user successfully joined.
But I can't manage to return the whole group after the insert.
with "group" as (
SELECT * from "group" where code = 'tohubo' LIMIT 1
)
insert into group_users__user_groups ("group_users", "user_groups")
select id from "group", 1
returning (SELECT * from "group")
With that query I currently get the error message
subquery must return only one column
I also tried to just return *, but then I only get the content of group_users__user_groups.
I also tried to add an additional Select at the end:
with "found_group" as (
SELECT * from "group" where code = 'tohubo' LIMIT 1
)
insert into group_users__user_groups ("group_users", "user_groups")
select 1, id from "found_group";
Select * from "found_group";
But then the WITH part is not defined in the second query:
Kernel error: ERROR: relation "found_group" does not exist
The returning clause can only return data that was affected by the insert.
And you can only have one "final" statement in a CTE, not an insert and a select.
But you can simply move the insert into a second cte, and then have a single SELECT at the end that returns the data that was found
with found_group as (
SELECT *
from "group"
where code = 'tohubo'
LIMIT 1
), inserted as (
insert into group_users__user_groups (group_users, user_groups)
select 1, id
from found_group
)
Select *
from found_group;

HSQLDB LIKE query fails with sharp-s

I'm unable to write a working LIKE query for a field containing the German sharp-s (ß) in a case-insensitive text field.
Using HSQLDB 2.2.9, create a table with a case sensitive field and a case insensitive field.
CREATE CACHED TABLE MYTABLE (MYKEY LONGVARCHAR NOT NULL, PRIMARY KEY (MYKEY));
ALTER TABLE MYTABLE ADD COLUMN SEN LONGVARCHAR;
ALTER TABLE MYTABLE ADD COLUMN INSEN VARCHAR_IGNORECASE;
Write 2 records.
INSERT INTO MYTABLE (MYKEY, SEN, INSEN) VALUES ('1', 'Strauß', 'Strauß');
INSERT INTO MYTABLE (MYKEY, SEN, INSEN) VALUES ('2', 'Strauss', 'Strauss');
Verify.
SELECT * FROM MYTABLE
KEY, SEN, INSEN
'1', 'Strauß', 'Strauß'
'2', 'Strauss', 'Strauss'
The problem query:
SELECT * FROM MYTABLE WHERE INSEN LIKE '%ß%'
WRONG, RETURNS RECORD 2 NOT RECORD 1
These queries work as expected:
SELECT * FROM MYTABLE WHERE SEN LIKE '%ß%'
OK, RETURNS RECORD 1
SELECT * FROM MYTABLE WHERE UCASE(INSEN) LIKE '%ß%'
OK, RETURNS RECORDS 1 AND 2
SELECT * FROM MYTABLE WHERE UCASE(SEN) LIKE '%ß%'
OK, RETURNS NOTHING
SELECT * FROM MYTABLE WHERE SEN='Strauß'
OK, RETURNS RECORD 1
SELECT * FROM MYTABLE WHERE INSEN='Strauß'
OK, RETURNS RECORD 1
SELECT * FROM MYTABLE WHERE SEN='Strauss'
OK, RETURNS RECORD 2
SELECT * FROM MYTABLE WHERE INSEN='Strauss'
OK, RETURNS RECORD 2
Thanks!

Return id if a row exists, INSERT otherwise

I'm writing a function in node.js to query a PostgreSQL table.
If the row exists, I want to return the id column from the row.
If it doesn't exist, I want to insert it and return the id (insert into ... returning id).
I've been trying variations of case and if else statements and can't seem to get it to work.
A solution in a single SQL statement. Requires PostgreSQL 8.4 or later though.
Consider the following demo:
Test setup:
CREATE TEMP TABLE tbl (
id serial PRIMARY KEY
,txt text UNIQUE -- obviously there is unique column (or set of columns)
);
INSERT INTO tbl(txt) VALUES ('one'), ('two');
INSERT / SELECT command:
WITH v AS (SELECT 'three'::text AS txt)
,s AS (SELECT id FROM tbl JOIN v USING (txt))
,i AS (
INSERT INTO tbl (txt)
SELECT txt
FROM v
WHERE NOT EXISTS (SELECT * FROM s)
RETURNING id
)
SELECT id, 'i'::text AS src FROM i
UNION ALL
SELECT id, 's' FROM s;
The first CTE v is not strictly necessary, but achieves that you have to enter your values only once.
The second CTE s selects the id from tbl if the "row" exists.
The third CTE i inserts the "row" into tbl if (and only if) it does not exist, returning id.
The final SELECT returns the id. I added a column src indicating the "source" - whether the "row" pre-existed and id comes from a SELECT, or the "row" was new and so is the id.
This version should be as fast as possible as it does not need an additional SELECT from tbl and uses the CTEs instead.
To make this safe against possible race conditions in a multi-user environment:
Also for updated techniques using the new UPSERT in Postgres 9.5 or later:
Is SELECT or INSERT in a function prone to race conditions?
I would suggest doing the checking on the database side and just returning the id to nodejs.
Example:
CREATE OR REPLACE FUNCTION foo(p_param1 tableFoo.attr1%TYPE, p_param2 tableFoo.attr1%TYPE) RETURNS tableFoo.id%TYPE AS $$
DECLARE
v_id tableFoo.pk%TYPE;
BEGIN
SELECT id
INTO v_id
FROM tableFoo
WHERE attr1 = p_param1
AND attr2 = p_param2;
IF v_id IS NULL THEN
INSERT INTO tableFoo(id, attr1, attr2) VALUES (DEFAULT, p_param1, p_param2)
RETURNING id INTO v_id;
END IF;
RETURN v_id:
END;
$$ LANGUAGE plpgsql;
And than on the Node.js-side (i'm using node-postgres in this example):
var pg = require('pg');
pg.connect('someConnectionString', function(connErr, client){
//do some errorchecking here
client.query('SELECT id FROM foo($1, $2);', ['foo', 'bar'], function(queryErr, result){
//errorchecking
var id = result.rows[0].id;
};
});
Something like this, if you are on PostgreSQL 9.1
with test_insert as (
insert into foo (id, col1, col2)
select 42, 'Foo', 'Bar'
where not exists (select * from foo where id = 42)
returning foo.id, foo.col1, foo.col2
)
select id, col1, col2
from test_insert
union
select id, col1, col2
from foo
where id = 42;
It's a bit longish and you need to repeat the id to test for several times, but I can't think of a different solution that involves a single SQL statement.
If a row with id=42 exists, the writeable CTE will not insert anything and thus the existing row will be returned by the second union part.
When testing this I actually thought the new row would be returned twice (therefor a union not a union all) but it turns out that the result of the second select statement is actually evaluated before the whole statement is run and it does not see the newly inserted row. So in case a new row is inserted, it will be taken from the "returning" part.
create table t (
id serial primary key,
a integer
)
;
insert into t (a)
select 2
from (
select count(*) as s
from t
where a = 2
) s
where s.s = 0
;
select id
from t
where a = 2
;

SQL - Is there a query that will do "foreach A in table, if !B, insert B"?

I have a table with 2 columns:
nid realm
1 domain_id
1 domain_site
2 domain_id
3 domain_id
I want every entry to have 1 entry for domain id, and 1 for domain site. So I want to end up with:
nid realm
1 domain_id
1 domain_site
2 domain_id
2 domain_site
3 domain_id
3 domain_site
If I was doing this in PHP, I'd just foreach through the whole list and insert the extra line whenever it didn't exist. Unfortunately I only have PHPmyAdmin access to this DB. Is there a way to do this in straight SQL?
(If it makes a difference: The table has about 3000 rows currently, of which I think about 2000 will need the extra line inserted. Also, this is a one-time thing so it does not need to be optimized/uber-slick.)
INSERT IGNORE INTO `table`
SELECT `alt1`.`nid`, `alt2`.`realm`
FROM `table` AS `alt1`, `table` AS `alt2`
I think this will do it, but I don't have a place to test it right now and I'm used to Sql Server rather than MySQL:
INSERT INTO `table`
SELECT id.nid, r.realm
FROM (SELECT nid FROM `table` GROUP BY nid) id
CROSS JOIN (SELECT realm FROM `table` GROUP BY realm) r
LEFT JOIN `table` t ON t.nid=id.nid AND t.realm=r.realm
WHERE t.realm IS NULL
insert into MyTable
(nid, realm)
select nid, 'domain_id'
from MyTable m where not exists (
select 1
from MyTable
where MyTable.nid = m.nid and realm = 'domain_id'
)
union all
select nid, 'domain_site'
from MyTable m where not exists (
select 1
from MyTable
where MyTable.nid = m.nid and realm = 'domain_site'
)
If you have a UNIQUE constraint over (nid, realm), you could do this:
INSERT IGNORE INTO nidTable (nid, realm)
SELECT nid, 'domain_site'
FROM nidTable WHERE realm = 'domain_id';