INSERT OR REPLACE WHERE ROWID Causes Error - sqlite

The following code:
$stm = $sql->prepare('INSERT OR REPLACE INTO "vehicle_service_invoice" (
invoice, "date", unit, odometer, sublet, sub, po, categories
) VALUES (
:invoice, :date, :unit, :odometer, :sublet, :sub, :po, :categories
) WHERE rowid = :rowid;'
);
$stm->bindParam(':invoice', $_POST['invoice']);
$stm->bindParam(':date', $_POST['date']);
$stm->bindParam(':unit', $_POST['unit']);
$stm->bindParam(':odometer', $_POST['odometer']);
$stm->bindParam(':sublet', $_POST['sublet']);
$stm->bindParam(':sub', $_POST['sub']);
$stm->bindParam(':po', $_POST['po']);
$stm->bindParam(':categories', $categories);
$stm->bindParam(':rowid', $_POST['rowid']);
$stm->execute();
Produces the following query:
INSERT OR REPLACE INTO "vehicle_service_invoice" (
invoice,
"date",
unit,
odometer,
sublet,
sub,
po,
categories
) VALUES (
7230,
'2013-02-07',
558,
34863,
0,
0,
1486347,
5
) WHERE rowid = 1
That produces the following error:
ERROR: near "WHERE": syntax error.
What I am trying to do is make a single path for both my INSERT and UPDATE logic to follow, so after I found out that I could do INSERT OR REPLACE, I figured I could just update the information based on the ROWID of each item. To me the syntax looks correct, what am I doing wrong?
It should be noted that I don't care about changing ROWID values as I understand that is a tripping point on doing INSERT OR REPLACE statements. Everything is joined together in other queries based off of the INVOICE column. I only want to use the ROWID to refer to that row.

In an INSERT statement, a WHERE clause does not make sense.
The INSERT OR REPLACE works as follows: a record with the specified values is inserted into the table.
If this would result in a UNIQUE constraint violation, the old record is deleted.
To replace a record that might already exist, the colum(s) that identify that record must be part of the values to be inserted.
In other words, you must insert the rowid value:
INSERT OR REPLACE INTO vehicle_service_invoice(rowid, ...) VALUES (1, ...)

Related

Multiple SQLite unique columns as one

Given the following example:
CREATE TABLE shapes(
shape_id INTEGER PRIMARY KEY,
background_color TEXT,
foreground_color TEXT,
UNIQUE(background_color,foreground_color)
);
background_color AND foreground_color need to be unique to insert but I don't want that. consider the row exists:
black|blue
and I try to insert:
white|blue
it will insert but if I try to insert another row containing:
black|blue
it will ignore the insert.
Will a primary key of the two cols accomplish this? If so, do I need to also use INSERT OR IGNORE?
First remove the UNIQUE constraint from the definition of the table.
What you can do is create a unique index not on the columns but on the min and max values of the 2 columns:
CREATE UNIQUE INDEX idx_shapes_colors ON shapes(
MIN(background_color, foreground_color),
MAX(background_color, foreground_color)
);
Then, when you try to insert the same combination of colors, like:
INSERT INTO shapes (background_color, foreground_color) VALUES ('black', 'blue');
INSERT INTO shapes (background_color, foreground_color) VALUES ('blue', 'black');
the 2nd statement will fail with an error message:
UNIQUE constraint failed: index 'idx_shapes_colors'
or, if you use INSERT OR IGNORE:
INSERT OR IGNORE INTO shapes (background_color, foreground_color) VALUES ('blue', 'black');
there will be no error but the statement will not insert the row.
See the demo.

sqlite3: INSERT OR IGNORE OR UPDATE for multiple unique columns

My simplified database looks like this:
CREATE TABLE my_table(hash TEXT, timestamp DATE, value1 TEXT, value2 REAL, UNIQUE(hash, timestamp));
The hash column is an MD5 hash of everything in the row.
Problem
A script will put the following example row in the table:
INSERT INTO my_table VALUES("7494ab07987ba112bd5c4f9857ccfb3f", "2019-12-19", "temp", 1);
Then the script will run an hour later and value2 may or may not have changed. This is equivalent to hash changing or remaining the same. If the hash has changed for the given date I want to update the row with the new values.
For a concrete example, below are the three possible statements that could run 1 hour after the statement above.
(case 1) INSERT INTO my_table VALUES("7494ab07987ba112bd5c4f9857ccfb3f", "2019-12-19", "temp", 1);
or...
(case 2) INSERT INTO my_table VALUES("64a5dcbe3f4af29ca58b8d99b1c3a9f2", "2019-12-19", "temp", 2);
or...
(case 3) INSERT INTO my_table VALUES("514292c37cc92b2ee2cd797328bed2d5", "2019-12-20", "temp 2", 3);
If (case 1) runs, the INSERT statement should be IGNOREd.
If (case 2) runs, the INSERT statement should be an UPDATE.
If (case 3) runs, the INSERT statement should run normally.
For a given timestamp (new_timestamp) which may or may not already be in my table, and hash (new_hash) I need a statement that will do something like this:
if new_timestamp in my_table:
if new_hash == old_hash:
IGNORE
else:
UPDATE
else:
INSERT
Any help is greatly appreciated.
EDIT:
The unique key for the row is the timestamp contrary to what my initial question said. The timestamp will not ever change. I will know some other value in the row changed by watching the hash, but I use the timestamp to ensure I am back in the right row. Case 2 could be a REPLACE for all I care, but if the hash changes for a given timestamp I do not want to create a new row for that timestamp. The timestamp will always be unique.
Assuming that the timestamp is defined as unique (which does not match the current question details), addition of the UPSERT clause will satisfy all three cases:
INSERT INTO my_table (hash, timestamp, value1, value2)
VALUES ('7494ab07987ba112bd5c4f9857ccfb3f', '2019-12-19', 'temp', 1)
ON CONFLICT (timestamp) DO UPDATE
SET hash = excluded.hash, value1 = excluded.value1, value2 = excluded.value2
WHERE hash != excluded.hash;
(FYI, the standard string delimiter is a single quote ', not double quotes ".)

Why did I get the ""exact fetch returns more than requested number of rows" when I use cursor already?

I am using the cursor to write the following codes, so that the cursor can pick up multiple lines. However, it still gives me the error message "exact fetch returns more than requested number of rows". Isn't that the cursor will retrieve data one line at a time. Why is this message still showing?
create or replace trigger e
before delete on enrollment
for each row
declare
get_tid scorest.tid%type;
get_ferm scorest.ferm%type;
get_sect scorest.sect%type;
get_name scorest.name%type;
get_score scorest.score%type;
cursor findenrolls(atid in scorest.tid%type,
aferm in scorest.ferm%type,
asect in scorest.sect%type)
is select * from scorest;
begin
for findenrolls_rec in findenrolls(:old.tid, :old.ferm, :old.sect) loop
select tid, ferm, sect, name, score
into get_tid, get_ferm, get_sect, get_name, get_score
from scorest
where scorest.tid=:old.tid
and scorest.ferm=:old.ferm
and scorest.sect=:old.sect;
insert into deleted_scores values (get_tid, get_ferm, get_sect, get_name, get_score);
delete from scorest
where tid=get_tid
and ferm=get_ferm
and sect=get_sect
and name=get_name;
end loop;
end;
Your error come from this statement part. select into must return exact one record.
select tid, ferm, sect, name, score
into get_tid, get_ferm, get_sect, get_name, get_score
from scorest
where scorest.tid=:old.tid
and scorest.ferm=:old.ferm
and scorest.sect=:old.sect;
You must rewrite your code make it work. Without knowing you use case it can be harder, but you can try this.
CREATE OR REPLACE
TRIGGER e before DELETE
ON enrollment
FOR EACH row
BEGIN
FOR get IN (
SELECT tid
, ferm
, sect
, name
, score
FROM scorest
WHERE scorest.tid=:old.tid
AND scorest.ferm =:old.ferm
AND scorest.sect =:old.sect
)
LOOP
INSERT
INTO deleted_scores VALUES
( get.tid
, get.ferm
, get.sect
, get.name
, get.score
);
DELETE
FROM scorest
WHERE tid=get.tid
AND ferm =get.ferm
AND sect =get.sect
AND name =get.name;
END LOOP;
END;
How you can see the explicit cursor is gone and replaced by an implicit one, this look more appropriate for this case.
error is coming due to duplicate data in scorest table based on tid , ferm ,sect
you can check this by
select tid , ferm ,sect from scorest having count(1)>1
group by tid , ferm ,sect;
if you don't need duplicates when you can put one more condition in your select statement
select tid, ferm, sect, name, score
into get_tid, get_ferm, get_sect, get_name, get_score
from scorest
where scorest.tid=:old.tid
and scorest.ferm=:old.ferm
and scorest.sect=:old.sect;
and rownum =1
else you can which to approach suggested by "Ftaveras ".

"Insert if not exists" statement in SQLite

I have an SQLite database. I am trying to insert values (users_id, lessoninfo_id) in table bookmarks, only if both do not exist before in a row.
INSERT INTO bookmarks(users_id,lessoninfo_id)
VALUES(
(SELECT _id FROM Users WHERE User='"+$('#user_lesson').html()+"'),
(SELECT _id FROM lessoninfo
WHERE Lesson="+lesson_no+" AND cast(starttime AS int)="+Math.floor(result_set.rows.item(markerCount-1).starttime)+")
WHERE NOT EXISTS (
SELECT users_id,lessoninfo_id from bookmarks
WHERE users_id=(SELECT _id FROM Users
WHERE User='"+$('#user_lesson').html()+"') AND lessoninfo_id=(
SELECT _id FROM lessoninfo
WHERE Lesson="+lesson_no+")))
This gives an error saying:
db error near where syntax.
If you never want to have duplicates, you should declare this as a table constraint:
CREATE TABLE bookmarks(
users_id INTEGER,
lessoninfo_id INTEGER,
UNIQUE(users_id, lessoninfo_id)
);
(A primary key over both columns would have the same effect.)
It is then possible to tell the database that you want to silently ignore records that would violate such a constraint:
INSERT OR IGNORE INTO bookmarks(users_id, lessoninfo_id) VALUES(123, 456)
If you have a table called memos that has two columns id and text you should be able to do like this:
INSERT INTO memos(id,text)
SELECT 5, 'text to insert'
WHERE NOT EXISTS(SELECT 1 FROM memos WHERE id = 5 AND text = 'text to insert');
If a record already contains a row where text is equal to 'text to insert' and id is equal to 5, then the insert operation will be ignored.
I don't know if this will work for your particular query, but perhaps it give you a hint on how to proceed.
I would advice that you instead design your table so that no duplicates are allowed as explained in #CLs answer below.
For a unique column, use this:
INSERT OR REPLACE INTO tableName (...) values(...);
For more information, see: sqlite.org/lang_insert
insert into bookmarks (users_id, lessoninfo_id)
select 1, 167
EXCEPT
select user_id, lessoninfo_id
from bookmarks
where user_id=1
and lessoninfo_id=167;
This is the fastest way.
For some other SQL engines, you can use a Dummy table containing 1 record.
e.g:
select 1, 167 from ONE_RECORD_DUMMY_TABLE

INSERT IF NOT EXISTS ELSE UPDATE?

I've found a few "would be" solutions for the classic "How do I insert a new record or update one if it already exists" but I cannot get any of them to work in SQLite.
I have a table defined as follows:
CREATE TABLE Book
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name VARCHAR(60) UNIQUE,
TypeID INTEGER,
Level INTEGER,
Seen INTEGER
What I want to do is add a record with a unique Name. If the Name already exists, I want to modify the fields.
Can somebody tell me how to do this please?
Have a look at http://sqlite.org/lang_conflict.html.
You want something like:
insert or replace into Book (ID, Name, TypeID, Level, Seen) values
((select ID from Book where Name = "SearchName"), "SearchName", ...);
Note that any field not in the insert list will be set to NULL if the row already exists in the table. This is why there's a subselect for the ID column: In the replacement case the statement would set it to NULL and then a fresh ID would be allocated.
This approach can also be used if you want to leave particular field values alone if the row in the replacement case but set the field to NULL in the insert case.
For example, assuming you want to leave Seen alone:
insert or replace into Book (ID, Name, TypeID, Level, Seen) values (
(select ID from Book where Name = "SearchName"),
"SearchName",
5,
6,
(select Seen from Book where Name = "SearchName"));
You should use the INSERT OR IGNORE command followed by an UPDATE command:
In the following example name is a primary key:
INSERT OR IGNORE INTO my_table (name, age) VALUES ('Karen', 34)
UPDATE my_table SET age = 34 WHERE name='Karen'
The first command will insert the record. If the record exists, it will ignore the error caused by the conflict with an existing primary key.
The second command will update the record (which now definitely exists)
You need to set a constraint on the table to trigger a "conflict" which you then resolve by doing a replace:
CREATE TABLE data (id INTEGER PRIMARY KEY, event_id INTEGER, track_id INTEGER, value REAL);
CREATE UNIQUE INDEX data_idx ON data(event_id, track_id);
Then you can issue:
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 2, 2, 3);
INSERT OR REPLACE INTO data VALUES (NULL, 1, 2, 5);
The "SELECT * FROM data" will give you:
2|2|2|3.0
3|1|2|5.0
Note that the data.id is "3" and not "1" because REPLACE does a DELETE and INSERT, not an UPDATE. This also means that you must ensure that you define all necessary columns or you will get unexpected NULL values.
INSERT OR REPLACE will replace the other fields to default value.
sqlite> CREATE TABLE Book (
ID INTEGER PRIMARY KEY AUTOINCREMENT,
Name TEXT,
TypeID INTEGER,
Level INTEGER,
Seen INTEGER
);
sqlite> INSERT INTO Book VALUES (1001, 'C++', 10, 10, 0);
sqlite> SELECT * FROM Book;
1001|C++|10|10|0
sqlite> INSERT OR REPLACE INTO Book(ID, Name) VALUES(1001, 'SQLite');
sqlite> SELECT * FROM Book;
1001|SQLite|||
If you want to preserve the other field
Method 1
sqlite> SELECT * FROM Book;
1001|C++|10|10|0
sqlite> INSERT OR IGNORE INTO Book(ID) VALUES(1001);
sqlite> UPDATE Book SET Name='SQLite' WHERE ID=1001;
sqlite> SELECT * FROM Book;
1001|SQLite|10|10|0
Method 2
Using UPSERT (syntax was added to SQLite with version 3.24.0 (2018-06-04))
INSERT INTO Book (ID, Name)
VALUES (1001, 'SQLite')
ON CONFLICT (ID) DO
UPDATE SET Name=excluded.Name;
The excluded. prefix equal to the value in VALUES ('SQLite').
Firstly update it. If affected row count = 0 then insert it. Its the easiest and suitable for all RDBMS.
Upsert is what you want. UPSERT syntax was added to SQLite with version 3.24.0 (2018-06-04).
CREATE TABLE phonebook2(
name TEXT PRIMARY KEY,
phonenumber TEXT,
validDate DATE
);
INSERT INTO phonebook2(name,phonenumber,validDate)
VALUES('Alice','704-555-1212','2018-05-08')
ON CONFLICT(name) DO UPDATE SET
phonenumber=excluded.phonenumber,
validDate=excluded.validDate
WHERE excluded.validDate>phonebook2.validDate;
Be warned that at this point the actual word "UPSERT" is not part of the upsert syntax.
The correct syntax is
INSERT INTO ... ON CONFLICT(...) DO UPDATE SET...
and if you are doing INSERT INTO SELECT ... your select needs at least WHERE true to solve parser ambiguity about the token ON with the join syntax.
Be warned that INSERT OR REPLACE... will delete the record before inserting a new one if it has to replace, which could be bad if you have foreign key cascades or other delete triggers.
If you have no primary key, You can insert if not exist, then do an update. The table must contain at least one entry before using this.
INSERT INTO Test
(id, name)
SELECT
101 as id,
'Bob' as name
FROM Test
WHERE NOT EXISTS(SELECT * FROM Test WHERE id = 101 and name = 'Bob') LIMIT 1;
Update Test SET id='101' WHERE name='Bob';
I believe you want UPSERT.
"INSERT OR REPLACE" without the additional trickery in that answer will reset any fields you don't specify to NULL or other default value. (This behavior of INSERT OR REPLACE is unlike UPDATE; it's exactly like INSERT, because it actually is INSERT; however if what you wanted is UPDATE-if-exists you probably want the UPDATE semantics and will be unpleasantly surprised by the actual result.)
The trickery from the suggested UPSERT implementation is basically to use INSERT OR REPLACE, but specify all fields, using embedded SELECT clauses to retrieve the current value for fields you don't want to change.
I think it's worth pointing out that there can be some unexpected behaviour here if you don't thoroughly understand how PRIMARY KEY and UNIQUE interact.
As an example, if you want to insert a record only if the NAME field isn't currently taken, and if it is, you want a constraint exception to fire to tell you, then INSERT OR REPLACE will not throw and exception and instead will resolve the UNIQUE constraint itself by replacing the conflicting record (the existing record with the same NAME). Gaspard's demonstrates this really well in his answer above.
If you want a constraint exception to fire, you have to use an INSERT statement, and rely on a separate UPDATE command to update the record once you know the name isn't taken.

Resources