Can column constraints be altered after creation in SQLite? - sqlite

Say I have created a table like this:
CREATE TABLE table_1(column_1 UNIQUE, column_2 NOT NULL);
Now after I have inserted lots of data, for some reason I need to remove UNIQUE constraint from the first column, that's column_1. Is it possible? If yes, how?

ALTER TABLE in sqlite is very limited.
You can however, to a limited extent, change some contraints using CREATE INDEX and CREATE TRIGGER.
You could, eg:
CREATE TABLE table_1(column_1, column_2);
-- No constrains in table_1
CREATE UNIQUE INDEX t1_c1_1 ON table_1 (column_1);
-- From now, column_1 must be UNIQUE
CREATE TRIGGER t1_c2_1i BEFORE INSERT ON table_1 WHEN column_2 IS NULL BEGIN
SELECT RAISE(ABORT, 'column_2 can not be NULL');
END;
CREATE TRIGGER t1_c2_1u BEFORE UPDATE ON table_1 WHEN column_2 IS NULL BEGIN
SELECT RAISE(ABORT, 'column_2 can not be NULL');
END;
-- From now, NULL column_2 update/insert will fail
DROP TRIGGER t1_c1_1;
-- Now column_1 doesn't need to be UNIQUE
DROP TRIGGER t1_c2_1i;
DROP TRIGGER t1_c2_1u;
-- Now column_2 may be NULL
Note:
you can't delete existing constrains;
creating indexes will grow your data (index data);
creating trigger may degrade table performance.
Another workaround is duplicating existing table removing constraints:
CREATE TEMP TABLE table_1 AS SELECT * FROM MAIN.table_1;
DROP TABLE table_1;
CREATE TABLE table_1 AS SELECT * FROM TEMP.table1;
-- table_1 is now a copy from initial table_1, but without constraints

SQLite's ALTER TABLE statement does not support this.
Your best bet is to export the database as text (.dump in the sqlite3 tool), remove the UNIQUE constrant, and recreate the database.

Related

SQLite Copy Table Content from one Table to another Table and Update

I have two SQLite files, each of them has one table and the same table design. One Column is set as Primary Key. I want to copy all data from ItemsB into ItemsA. All data should be updated. The ItemsB Table is the newer one.
I've tried:
ATTACH DATABASE ItemsB AS ItemsB;
INSERT INTO ItemsA.PMItem (ItemID,VarID,Name1) SELECT ItemID,VarID,Name1 FROM ItemsB.PMItem;
Obviously this can't work due the Primary Key (which is the column VarID).
Then I tried it with ON CONFLICT:
ON CONFLICT (VarID) DO UPDATE SET Name1=excluded.Name1
But this won't work either.
Example Table:
CREATE TABLE PMItem (
ItemID INTEGER,
VarID INTEGER PRIMARY KEY,
Name1 TEXT
);
You need a WHERE clause with an always true condition, to overcome the ambiguity that is raised when ON CONFLICT is used after a SELECT statement:
INSERT INTO PMItem (ItemID,VarID,Name1)
SELECT ItemID,VarID,Name1
FROM ItemsB.PMItem
WHERE 1
ON CONFLICT(VarID) DO UPDATE
SET Name1 = EXCLUDED.Name1;

sqlite column constraint not in set of values

In sqlite, how do I restrict the values of a column to being not in another table/view column?
For example
sqlite> create table tab1(col1 check (col1 not in (1,2)));
does what I want except that it seems only to exclude hard-coded values. However, the following does not work -
sqlite> create table tab2(vals_to_exclude);
sqlite> insert into tab2 values(1);
sqlite> insert into tab2 values(2);
sqlite> create table tab3(col1 check (col1 not in (select vals_to_exclude from tab2)));
Error: subqueries prohibited in CHECK constraints
Is it possible to constrain a column to exclude a dynamically determined set of values?
If the built-in mechanisms are not sufficient, implement the check manually with a trigger:
CREATE TRIGGER tab3_col1_not_in_tab2
BEFORE INSERT ON tab3 -- you also need a trigger for UPDATEs
FOR EACH ROW
WHEN EXISTS (SELECT 1
FROM tab2
WHERE vals_to_exclude = NEW.col1)
BEGIN
SELECT RAISE(FAIL, "col1 conflicts with tab2");
END;
or, alternatively:
CREATE TRIGGER tab3_col1_not_in_tab2
BEFORE INSERT ON tab3 -- you also need a trigger for UPDATEs
FOR EACH ROW
BEGIN
SELECT RAISE(FAIL, "col1 conflicts with tab2")
FROM tab2
WHERE vals_to_exclude = NEW.col1;
END;

SQLite regular table and table for fts

I have table news (id, news_id, news_title) and I creat FTS table:
CREATE VIRTUAL TABLE news_search USING fts4 (news_title, tokenize=porter);
I use trigger to keep table NEWS and news_search in sync:
CREATE TRIGGER IF NOT EXISTS insert_news_trigger
AFTER INSERT ON news
BEGIN
INSERT OR IGNORE INTO news_search (news_title) VALUES (NEW.news_title);
END;
Question: how to use search? When I do MATCH in news_search table it returns me only records from this table, but I need *news_id* from news table. May be I should add *news_id* column to news_search table?
What is the proper way to use fts in sqlite?
Read the documentation; FTS tables also have a rowid column (also called docid) that you can set explicitly to the same value as the corresponding key of the original table.
Assuming that news.id is the rowid (i.e., INTEGER PRIMARY KEY), you should change your trigger to also copy that ID value into the news_search table.
You can the use that to look up the original record:
SELECT *
FROM news
WHERE id IN (SELECT docid
FROM news_search
WHERE news_title MATCH '😸')

SQLITE - INSERT or UPDATE without changing ROWID value

I need to update a table row IF EXISTS, otherwise INSERT a new row.
I tried:
INSERT OR REPLACE INTO table VALUES ...
but if the row row exist this statement changes the row's ROWID, and that's what I'm trying to avoid (I need the rowid :D)
I also tried to find a way to get some sort of return value from the update, in the case where an update has taken place, but I still don't understand how... If I could get the return value from the update statement, I could choose wether to proceed with an insert or not.
Do you have any suggestion or solution to this problem? Or do I need to make a copy of the ROWID and use that instead of the "pure" table ROWID?
Thanks in advance, best regards
ps: I was looking HERE and I was wondering if sqlite has the OUTPUT special word too, but google didn't help me..
---- EDIT after reading comments:
table schema example
CREATE TABLE test (
table_id TEXT NOT NULL,
some_field TEXT NOT NULL,
PRIMARY KEY(table_id)
)
INSERT or REPLACE INTO test (table_id, some_field) VALUES ("foo","bar")
I tested Chris suggestion but the rowid still gets changed. I think the best alternative is to do a SELECT to see if a row with that key already exist. If so, UPDATE, otherwise, INSERT... good old fashion but guaranteed to work.
Combine it with select, like this
INSERT or REPLACE INTO test (ROWID, table_id, some_field)
VALUES ((SELECT ROWID from test WHERE table_id = 'foo' UNION SELECT max(ROWID) + 1 from test limit 1), 'foo','bar')
You need to specify that your table_id is unique in addition to being the primary key:
sqlite> CREATE TABLE test (
table_id TEXT NOT NULL,
some_field TEXT NOT NULL,
PRIMARY KEY(table_id),
UNIQUE(table_id)
);
sqlite> insert or replace into test values("xyz", "other");
sqlite> select * FROM test;
xyz|other
sqlite> insert or replace into test values("abc", "something");
sqlite> insert or replace into test values("xyz", "whatever");
sqlite> select * FROM test;
abc|something
xyz|whatever
From version 3.24.0 (2018-06-04), SQLite now supports an UPSERT clause that will do exactly what the OP needed: https://www.sqlite.org/lang_UPSERT.html
The insert would now look like this:
INSERT INTO test (table_id, some_field) VALUES ("foo","baz")
ON CONFLICT(table_id) DO UPDATE SET some_field=excluded.some_field;

Select for Update sql is a read and write mode?

I have simultaneous request to a particular row in a table and PL/SQL statement is used to update the table by reading the data from master row in the same table and update the current range row and master row it read.
Algorithm is like this:-
Declare
variable declaration
BEGIN
Select (Values) into (values1) from table where <condition1> for update;
select count(*) into tempval from table where <condition2>;
if (tempval == 0) then
insert into table values(values);
else
select (values) into (values2) from table where <condition2> for update;
update table set (values1) where <condition2>;
end if
update table set (values1+incrval) where <condition1>
END;
Unfortunately the master row is updated properly with the correct sequence but the current range picks up the old value of the master range. It does the dirty read. Even though the transaction isolation level for the table is serialized.
Please could some tell me what is happening here?
This is working as designed. Oracle default, and only, read isolation lets the session see all of their own updates. If you perform:
INSERT INTO TABLE1 (col1) values (1);
COMMIT;
UPDATE TABLE1 SET col1 = 2 where col1 = 1;
SELECT col1 FROM TABLE1;
you will see 2 returned from the last query. Please read the Merge Explanation for how to use a MERGE statement to perform the insert or update based upon a single criteria.

Resources