Postgres: WHERE... AND syntax problem - sql

Can anyone help with a Postgres syntax problem? I'm trying to insert a record, but before doing so, check it doesn't exist, using WHERE... AND.
=# \d domes_manor_place;
id | integer | not null default nextval('domes_manor_place_id_seq'::regclass)
manor_id | integer | not null
place_id | integer | not null
=# select * from domes_manor_place where place_id='13621';
24017 | 22276 | 13621
OK, so we know that there is no record with manor_id=22398 and place_id=13621. Let's try to insert it with our `WHERE... AND' syntax:
=# INSERT INTO domes_manor_place (manor_id, place_id) SELECT 22398, 13621
WHERE (22398 NOT IN (SELECT manor_id FROM domes_manor_place)) AND
(13621 NOT IN (SELECT place_id FROM domes_manor_place));
INSERT 0 0
It won't insert the record - so what's wrong with my syntax?

Try this:
WHERE (22398, 13621) NOT IN (SELECT manor_id, place_id FROM domes_manor_place)
By the way, a much better approach is to use a unique constraint on the pair of columns. This will cause the insert to fail if a row already exists.

You need a UNIQUE-constraint, the SELECT can't help because it can't see data that is not committed yet. Different transactions can insert new records at the same moment and these are all "unique"... NOT.

Related

SQL Insert only the rows that have data in a specific column

I am basically a noob at this and have gotten this far from Google searches alone. Access VBA and SQL inventory database.
I have a table that I populate by a barcode scanner that looks like the following;
PartNo | SerialNo | Qty | Vehicle
-------+----------+-----+---------
test | | 1 | H2
test2 | | 1 | H2
test3 | test3s/n | 1 | H2
test3 | test4s/n | 1 | H2
test | | 1 | H2
I am trying to update 2 tables from this, or insert if the PartNo doesn't exist.
tblPerm2 has PartNo as primary key
tblPerm1 has PartNo, SerialNo, Qty and Vehicle
PartNo must exist in tblPerm2 to be added to tblPerm1
I can get the PartNo inserted into tblPerm2 no problem, but I'm running into problems with tblPerm1.
I'm following user Parfait's example here, Update Existing Access Records from CSV Import , native to MS Access or in VB.NET
I've tried an Insert and and insert with a join. The code below adds everything to tblPerm1, including rows with no SerialNo. How can I insert only the rows from tblTemp that have a serial number?
INSERT INTO tblPerm1 (PartNo, SerialNo, Qty, Vehicle)
SELECT tblTemp.PartNo, tblTemp.SerialNo, tblTemp.Qty, tblTemp.Vehicle
FROM tblTemp
WHERE tblTemp.SerialNo IS NOT NULL;
I expect this to only insert the 2 'test3' rows, but all rows are inserted.
SELECT DISTINCT is the same, but only one entry for 'test'
Once this is done, I'll delete from tblTemp and continue on updating and inserting. Maybe there is a better way?
Thanks in advance
Are the SerialNo columns actually empty strings instead of NULL?
If this works, then yes they are:
INSERT INTO tblPerm1 (PartNo, SerialNo, Qty, Vehicle)
SELECT tblTemp.PartNo, tblTemp.SerialNo, tblTemp.Qty, tblTemp.Vehicle
FROM tblTemp
WHERE tblTemp.SerialNo <> '';
See How to check for Is not Null And Is not Empty string in SQL server? for more on checking for empty strings, with or without counting whitespace (though details may vary depending on what SQL server you are running).

Using ignore_duplicate on non primary key

I've got table:
ID (identity, PK), TaskNr, OfferNr
I want to do insert ignore statement but sadly it's not working on MSSQL, so there's a IGNORE_DUP switch. But I need to check duplicates using TaskNr column. Is there any chance to do that?
Edit:
Sample data:
ID (identity, PK), TaskNr, OfferNr
1 BP1234 XAS
2 BD123 JFRT
3 1122AH JDA33
4 22345_a MD_3
Trying to do:
insert ignore into Sample_table (TaskNr, OfferNr) values (BP1234, DFD,)
Should ignore that row and go to next value of insert statement. ID is autoincremented but unique value should be checked using TaskNr column.
SQL Server does not support insert ignore. That is MySQL functionality.
You can do what you want as:
insert ignore into Sample_table (TaskNr, OfferNr)
select x.TaskNr, x.OfferNr
from (select 'BP1234' as TaskNr, 'DFD' as OfferNr) x
where not exists (select 1
from Sample_Table st
where st.TaskNr = x.TaskNr and st.OfferNr = x.OfferNr
);
You can try two options:
insert into ... where not exists ()
t-sql merge statement (https://learn.microsoft.com/en-us/sql/t-sql/statements/merge-transact-sql)

SQL Multiple Row Insert w/ multiple selects from different tables

I am trying to do a multiple insert based on values that I am pulling from a another table. Basically I need to give all existing users access to a service that previously had access to a different one. Table1 will take the data and run a job to do this.
INSERT INTO Table1 (id, serv_id, clnt_alias_id, serv_cat_rqst_stat)
SELECT
(SELECT Max(id) + 1
FROM Table1 ),
'33', --The new service id
clnt_alias_id,
'PI' --The code to let the job know to grant access
FROM TABLE2,
WHERE serv_id = '11' --The old service id
I am getting a Primary key constraint error on id.
Please help.
Thanks,
Colin
This query is impossible. The max(id) sub-select will evaluate only ONCE and return the same value for all rows in the parent query:
MariaDB [test]> create table foo (x int);
MariaDB [test]> insert into foo values (1), (2), (3);
MariaDB [test]> select *, (select max(x)+1 from foo) from foo;
+------+----------------------------+
| x | (select max(x)+1 from foo) |
+------+----------------------------+
| 1 | 4 |
| 2 | 4 |
| 3 | 4 |
+------+----------------------------+
3 rows in set (0.04 sec)
You will have to run your query multiple times, once for each record you're trying to copy. That way the max(id) will get the ID from the previous query.
Is there a requirement that Table1.id be incremental ints? If not, just add the clnt_alias_id to Max(id). This is a nasty workaround though, and you should really try to get that column's type changed to auto_increment, like Marc B suggested.

Is it possible to use a PG sequence on a per record label?

Does PostgreSQL 9.2+ provide any functionality to make it possible to generate a sequence that is namespaced to a particular value? For example:
.. | user_id | seq_id | body | ...
----------------------------------
- | 4 | 1 | "abc...."
- | 4 | 2 | "def...."
- | 5 | 1 | "ghi...."
- | 5 | 2 | "xyz...."
- | 5 | 3 | "123...."
This would be useful to generate custom urls for the user:
domain.me/username_4/posts/1
domain.me/username_4/posts/2
domain.me/username_5/posts/1
domain.me/username_5/posts/2
domain.me/username_5/posts/3
I did not find anything in the PG docs (regarding sequence and sequence functions) to do this. Are sub-queries in the INSERT statement or with custom PG functions the only other options?
You can use a subquery in the INSERT statement like #Clodoaldo demonstrates. However, this defeats the nature of a sequence as being safe to use in concurrent transactions, it will result in race conditions and eventually duplicate key violations.
You should rather rethink your approach. Just one plain sequence for your table and combine it with user_id to get the sort order you want.
You can always generate the custom URLs with the desired numbers using row_number() with a simple query like:
SELECT format('domain.me/username_%s/posts/%s'
, user_id
, row_number() OVER (PARTITION BY user_id ORDER BY seq_id)
)
FROM tbl;
db<>fiddle here
Old sqlfiddle
Maybe this answer is a little off-piste, but I would consider partitioning the data and giving each user their own partitioned table for posts.
There's a bit of overhead to the setup as you will need triggers for managing the DDL statements for the partitions, but would effectively result in each user having their own table of posts, along with their own sequence with the benefit of being able to treat all posts as one big table also.
General gist of the concept...
psql# CREATE TABLE posts (user_id integer, seq_id integer);
CREATE TABLE
psql# CREATE TABLE posts_001 (seq_id serial) INHERITS (posts);
CREATE TABLE
psql# CREATE TABLE posts_002 (seq_id serial) INHERITS (posts);
CREATE TABLE
psql# INSERT INTO posts_001 VALUES (1);
INSERT 0 1
psql# INSERT INTO posts_001 VALUES (1);
INSERT 0 1
psql# INSERT INTO posts_002 VALUES (2);
INSERT 0 1
psql# INSERT INTO posts_002 VALUES (2);
INSERT 0 1
psql# select * from posts;
user_id | seq_id
---------+--------
1 | 1
1 | 2
2 | 1
2 | 2
(4 rows)
I left out some rather important CHECK constraints in the above setup, make sure you read the docs for how these kinds of setups are used
insert into t values (user_id, seq_id) values
(4, (select coalesce(max(seq_id), 0) + 1 from t where user_id = 4))
Check for a duplicate primary key error in the front end and retry if needed.
Update
Although #Erwin advice is sensible, that is, a single sequence with the ordering in the select query, it can be expensive.
If you don't use a sequence there is no defeat of the nature of the sequence. Also it will not result in a duplicate key violation. To demonstrate it I created a table and made a python script to insert into it. I launched 3 parallel instances of the script inserting as fast as possible. And it just works.
The table must have a primary key on those columns:
create table t (
user_id int,
seq_id int,
primary key (user_id, seq_id)
);
The python script:
#!/usr/bin/env python
import psycopg2, psycopg2.extensions
query = """
begin;
insert into t (user_id, seq_id) values
(4, (select coalesce(max(seq_id), 0) + 1 from t where user_id = 4));
commit;
"""
conn = psycopg2.connect('dbname=cpn user=cpn')
conn.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE)
cursor = conn.cursor()
for i in range(0, 1000):
while True:
try:
cursor.execute(query)
break
except psycopg2.IntegrityError, e:
print e.pgerror
cursor.execute("rollback;")
cursor.close()
conn.close()
After the parallel run:
select count(*), max(seq_id) from t;
count | max
-------+------
3000 | 3000
Just as expected. I developed at least two applications using that logic and one of then is more than 13 years old and never failed. I concede that if you are Facebook or some other giant then you could have a problem.
Yes:
CREATE TABLE your_table
(
column type DEFAULT NEXTVAL(sequence_name),
...
);
More details here:
http://www.postgresql.org/docs/9.2/static/ddl-default.html

SQL conditional row insert

Is it possible to insert a new row if a condition is meet?
For example, i have this table with no primary key nor uniqueness
+----------+--------+
| image_id | tag_id |
+----------+--------+
| 39 | 8 |
| 8 | 39 |
| 5 | 11 |
+----------+--------+
I would like to insert a row if a combination of image_id and tag_id doesn't exists
for example;
INSERT ..... WHERE image_id!=39 AND tag_id!=8
I think you're saying: you need to avoid duplicate rows in this table.
There are many ways of handling this. One of the simplest:
INSERT INTO theTable (image_id, tag_id) VALUES (39, 8)
WHERE NOT EXISTS
(SELECT * FROM theTable
WHERE image_id = 39 AND tag_id = 8)
As #Henrik Opel pointed out, you can use a check constraint on the combined columns, but then you have to have a try/catch block somewhere else, which adds irrelevant complexity.
Edit to explain that comment...
I'm assuming this is a table mapping a many-to-many relationship between Movies and Tags. I realize you're probably using php, but I hope the C# pseudocode below is clear enough anyway.
If I have a Movie class, the most natural way to add a tag is an AddTag() method:
class Movie
{
public void AddTag(string tagname)
{
Tag mytag = new Tag(tagname); // creates new tag if needed
JoinMovieToTag(this.id, mytag.id);
}
private void JoinMovieToTag(movie_id, tag_id)
{
/* database code to insert record into MovieTags goes here */
/* db connection happens here */
SqlCommand sqlCmd = new SqlCommand("INSERT INTO theTable... /* etc */");
/* if you have a check constraint on Movie/Tag, this will
throw an exception if the row already exists */
cmd.ExecuteNonQuery();
}
}
There's no practical way to check for duplicates earlier in the process, because another user might Tag the Movie at any moment, so there's no way around this.
Note: If trying to insert a dupe record means there's a bug, then throwing an error is appropriate, but if not, you don't want extra complexity in your error handler.
Try using a database trigger for an efficient way to add rows to a different table based on updates to a table.
I'm assuming you mean you want to insert a row if it does not contain those values.
I don't have MySQL in front of me, but in general SQL this should work:
INSERT INTO a_table (image_id, tag_id) SELECT ? image_id, ? tag_id WHERE image_id!=39 AND tag_id!=8
If you mean to insert the row only if no such row exists at all, then you can do this:
INSERT INTO a_table (image_id, tag_id) SELECT ? image_id, ? tag_id WHERE not exists (SELECT 1 from a_table WHERE image_id!=39 AND tag_id!=8)
If you're using InnoDB with transcations, then you can simply query for the data first, and in your code subsequently execute the insert if now rows were already found with the values. Alternatively, add a unique constaint over both columns, and try the insert. If will fail, if it already exists, which you can ignore. (This is less preferred than my first approach.)
Is fare to give credit to Henrik Opel as he spotted what we all overlooked including me, a simple unique constraint on the two columns. Is ultimately the best solution.
To avoid duplicate row insertion use the below mentioned query:
INSERT INTO `tableName` ( `image_id`, `tag_id`)
SELECT `image_id`, `tag_id` FROM `tableName`
WHERE NOT EXISTS (SELECT 1
FROM `tableName`
WHERE `image_id` = '39'
AND `tag_id` = '8'
);