SQLite update on select (or vice versa) - sqlite

Is there a one-statement select-and-update (or update-and-select) method in SQLite?
A trigger can invoke select, but that doesn't allow update to be used in an expression:
CREATE TABLE id ( a integer );
CREATE TRIGGER idTrig AFTER UPDATE ON id BEGIN SELECT old.a FROM id; END;
INSERT INTO id VALUES ( 100 );
INSERT INTO test VALUES ( (UPDATE id SET a=a+1) ); -- syntax error
(Is a triggered select only accessible via the C API?)
I generate object IDs for several databases from a single ID database (with a single row for the next available ID). I'd like to select-and-update on the ID db in one statement, so that concurrent db connections which attach the ID db won't have trouble with this (where two connections could insert before either updates):
INSERT INTO tab VALUES ( (SELECT uuid||oid AS oid FROM id.tab), ... );
UPDATE id.tab SET oid = oid+1;

I'll start with the prerequisite nag: why not use GUIDs? They don't require central authority and are thus more efficient and easier to work with.
If you really need a central ID store, you can make the ID an autoincrement and fetch it with SELECT last_insert_rowid(). If you have to generate your own IDs, then make the ID column a primary key, so you can generate the ID, INSERT it, and retry if the INSERT fails.

This discussion presents two possible solutions:
http://www.mail-archive.com/sqlite-users#sqlite.org/msg10705.html

Related

How to UPDATE a table by using __rowid__ of another table in sqlite3

I am doing CRUD operations using __rowid__ which comes default with the sqlite table. I don't have separate columns for ID's in any of my tables.
My create, read and delete operations are done.
I am searching the database by customer's name.
TABLES
UPDATE query for the customers table
cursor.execute("""
SELECT * FROM customers
WHERE name = ?""", (name_variable.get(),))
cursor.execute("""
UPDATE customers SET
'contact' = ?,
'mail' = ?,
'address' = ?
WHERE name = ?
""",
(
contact_variable.get(),
mail_variable.get(),
address_variable.get(),
name_variable.get()
)
)
My issue is with updating the services & charges table.
What I want is if user changes John's information then how do I UPDATE only John's data to these two tables using __rowid__. I don't understand how to execute that query.
(I am using sqlite3 version 3.31.1 on Ubuntu 20.04).
According to the schema you have shown there is no relationship between Customers, Services and Charges so updating a Customer has no bearing on the other tables. As such you probably want a relationship and the implication of you saying
then how do I UPDATE only John's data to these two tables using rowid
The answer to that is the rowid column, as there are not relationships, does not do anything other than uniquely identify a row in the respective tables.
So first you need to define the relationships which will require either
a column in each of the two tables (services and charges) to cater for a parent (customer) with children (services will be children of a customer and charges will be children of a customer) aka two one (customer) to many (services and charges) relationship, or
a mapping reference table if you need a many-many relationship.
Typically the most efficient way of mapping/reference/relating/linking/associating children to parents is to utilises the always present (but normally hidden) rowid by aliasing it to column name (e.g. id INTEGER PRIMARY KEY).
As such you probably want you table definitions to be something like:-
CREATE TABLE IF NOT EXISTS customers (
customer_id INTEGER PRIMARY KEY,
name TEXT,
contact TEXT,
mail TEXT,
address TEXT
);
the customer_id is an alias of the rowid column
then :-
CREATE TABLE IF NOT EXISTS services (
serviceid INTEGER PRIMARY KEY,
service TEXT, subservice TEXT,
customer_id INTEGER REFERENCES customers(customer_id) ON DELETE CASCADE ON UPDATE CASCADE
);
the service_id column is an alias of the rowid column
the customer_id column references the parent, i.e. the customer to whom the services belongs to.
the REFERENCES keyword along with the table and the associated column defines a constraint that says the customer_id column MUST be a value that exists in the customer_id column of the customers table (i.e. a Foreign Key constraint (rule)).
then :-
- The ON DELETE CASCADE says that if a parent is deleted then all the children of the parent are to be deleted down from the parent.
- The ON UPDATE is similar but cascades any change to the customer_id column in the customers table.
CREATE TABLE IF NOT EXISTS charges (
initialcharges REAL,
taxes REAL,
discount REAL,
advance REAL,
total REAL,
customer_id INTEGER REFERENCES customers(customer_id) ON DELETE CASCADE ON UPDATE CASCADE
);
Similar to services
Now say you then insert some (2) customers (noting that for the demo specific customer_id vales are specified rather than allowing them to be auto generated) using :-
INSERT OR IGNORE INTO customers VALUES
(10,'John','something','something','something')
,(20,'Jane','something','something','something')
;
and then use :-
SELECT *,rowid FROM customers;
Then :-
note that rowid is displayed as customer_id(1) as it now has customer_id as it's alias and that it exactly matches the value of the customer_id column.
Now we add some rows the the services table using:-
INSERT INTO services (service,subservice,customer_id) VALUES
('Exterior','something',10)
,('Interior','something',10)
,('Interior','something',20)
;
Note how the customer_id is a value from the customer_id column of the customers table and hence how you relate each services row to ONE customer.
using: -
SELECT *,rowid FROM services;
results in :-
again the rowid matches it's alias BUT both columns are both just a unique identifier of the row in the services table it has no meaning to the relationship between a service row and it's parent customer (hence why the rowid is of no use for what you want).
the important row, relationship wise, is the customer_id row which specifies the parent.
Similarly for the charges table :-
INSERT INTO charges (initialcharges,taxes,discount,advance,total,customer_id) VALUES
(10.50,0.50,1.5,0,11.50,10),
(105.00,05.00,1.5,0,115,20)
;
SELECT *,rowid FROM charges;
Now say you used-
SELECT customers.*,customers.rowid AS custid,' - ' AS ' ', services.*,services.rowid AS sid,' - ' AS ' ',charges.*,charges.rowid AS cid
FROM customers
JOIN services ON services.customer_id = customers.customer_id
JOIN charges ON charges.customer_id = customers.customer_id
;
then you get :-
If the name of John were changed to Fred using :-
UPDATE customers SET name = 'Fred' WHERE name = 'John';
Then as the John (now Fred) is accessed from the specific row it's change will be seen without any special processing in future queries e.g.
SELECT customers.*,customers.rowid AS custid,' - ' AS ' ', services.*,services.rowid AS sid,' - ' AS ' ',charges.*,charges.rowid AS cid
FROM customers
JOIN services ON services.customer_id = customers.customer_id
JOIN charges ON charges.customer_id = customers.customer_id
;
now results in :-
However, say the id for Jane were changed to 10000 using:-
UPDATE customers SET customer_id = 10000 WHERE customer_id = 20;
Then using the same query results in:-
i.e the ne value (10000) has automatically been applied to the children (not that you would likely change the customer_id often).
NOTE if you updated a child's column (if it did not violate the FK constraint) then that change IS NOT propagated to the parent. The parent would be switched.
Deletion works in a similar way, Delete the parent and the children will be deleted. Delete an child and just that child is child.
So with something like above, all you need to do is update whatever you need to update.
NOTE the above may or may not reflect actually what you want, it is rather a demonstration of the principle.

Generate incremental unique IDs for users on registration

I have a website wherein I need to users to register themselves by adding their details and submitting. On submitting each user should be allocated incremental unique ids. To test this I had 15 users enter necessary details on the site and then click on register simultaneously, however two set of users got the same ids allocated to them. This is not a problem when all register at different times, then the allocation of ids is unique.
How do I ensure each user gets unique ids even when they register simultaneously.
The "easiest" way to give each user a unique ID would be to either use an IDENTITY column on your user table (if you want the ID to be allocated when the record is successfully written to it), or a SEQUENCE object if you want to get a unique ID before creating the user account in the database.
create table MyUserTable (
UserID int identity(1,1) NOT NULL constraint PK_MyUserTable PRIMARY KEY CLUSTERED
,UserName nvarchar(50)
,Email nvarchar(100);
insert into MyUserTable output inserted.UserId values ('MyUserName', 'me#example.com');
Will write the record to the table and return the ID that was created.
If you want to fetch an ID before writing to the table:
CREATE SEQUENCE UserIds as int start with 1 increment by 1;
create table MyUserTable (
UserID int NOT NULL constraint PK_MyUserTable PRIMARY KEY CLUSTERED
,UserName nvarchar(50)
,Email nvarchar(100);
select next value for UserIds
-- Do whatever you need to do in the application
insert into MyUserTable (UserId,UserName,Email) values (UserIdYouGotEarlier, 'MyUserName','me#example.com');
Understand that with both approaches, you are not guaranteed to have sequential IDs. In the case of a transaction rollback, these will both still auto-increment and those values will be "lost." But you shouldn't be depending upon them being sequential in the first place.
You'll probably want a unique constraint on those username fields too.

Trigger in sqlite different database

I have 2 different database 'A' and 'B'.I need to create a trigger that when I would insert any entry in table 'T1' of database 'A' then entries of table 'T2' of database 'B' would gets deleted.
Kindly suggest me a way!!
This is not possible.
In SQLite, DML inside triggers can only modify tables of the same database (see here). You cannot modify tables of an attached database.
Similarly, you cannot declare triggers for an attached database (to do it the other way) unless you declare them TEMPORARY.
Hence, (only) the following is possible:
For A.sqlite:
create table T1(id integer primary key);
For B.sqlite:
create table T2(id integer primary key);
attach 'A.sqlite' as A;
create temporary trigger T1_del after delete on A.T1
begin
delete from T2 where id = OLD.id;
end;
But that would only propagate deletes from T1 to T2 within the connection that declared the temporary trigger. If you opened A.sqlite separately, the trigger would not be there.

How do you write a good stored procedure for update?

I want to write a stored procedure (SQL server 2008r2), say I have a table:
person
Columns:
Id int (pk)
Date_of_birth date not null
Phone int allow null
Address int allow null
Name nvarchat(50) not null
Sample data:
Id=1,Date_of_birth=01/01/1987,phone=88888888,address=null,name='Steve'
Update statement in Stored procedure, assume
The parameters are already declare:
Update person set
Date_of_birth=#dob,phone=#phone,address=#address,name=#name where id=#id
The table has a trigger to log any changes.
Now I have an asp.net update page for updating the above person table
The question is, if user just want to update address='apple street' , the above update statement will update all the fields but not check if the original value = new value, then ignore this field and then check the next field. So my log table will log all the event even the columns are not going to be updated.
At this point, my solutions
Select all the value by id and store them into local variables.
Using if-else check and generate the update statement. At last,
dynamically run the generated SQL (sp_executesql)
Select all the value by id and store them into local variables.
Using if-else check and update each field seperately:
If #dob <> #ori_dob
Begin
Update person set date_of_birth=#dob where id=#id
End
May be this is a stupid question but please advice me if you have better idea, thanks!
This is an answer to a comment by the OP and does not address the original question. It would, however, be a rather ugly comment.
You can use a statement like this to find the changes to Address within an UPDATE trigger:
select i.Id, d.Address as OldAddress, i.Address as NewAddress
from inserted as i inner join
deleted as d on d.Id = i.Id
where d.Address <> i.Address
One such statement would be needed for each column that you want to log.
You could accumulate the results of the SELECTs into a single table variable, then summarize the results for each Id. Or you can use INSERT/SELECT to save the results directly to your log table.

Is it possible to (emulate?) AUTOINCREMENT on a compound-PK in Sqlite?

According to the SQLite docs, the only way to get an auto-increment column is on the primary key.
I need a compound primary key, but I also need auto-incrementing. Is there a way to achieve both of these in SQLite?
Relevant portion of my table as I would write it in PostgreSQL:
CREATE TABLE tstage (
id SERIAL NOT NULL,
node INT REFERENCES nodes(id) NOT NULL,
PRIMARY KEY (id,node),
-- ... other columns
);
The reason for this requirement is that all nodes eventually dump their data to a single centralized node where, with a single-column PK, there would be collisions.
The documentation is correct.
However, it is possible to reimplement the autoincrement logic in a trigger:
CREATE TABLE tstage (
id INT, -- allow NULL to be handled by the trigger
node INT REFERENCES nodes(id) NOT NULL,
PRIMARY KEY (id, node)
);
CREATE TABLE tstage_sequence (
seq INTEGER NOT NULL
);
INSERT INTO tstage_sequence VALUES(0);
CREATE TRIGGER tstage_id_autoinc
AFTER INSERT ON tstage
FOR EACH ROW
WHEN NEW.id IS NULL
BEGIN
UPDATE tstage_sequence
SET seq = seq + 1;
UPDATE tstage
SET id = (SELECT seq
FROM tstage_sequence)
WHERE rowid = NEW.rowid;
END;
(Or use a common my_sequence table with the table name if there are multiple tables.)
A trigger works, but is complex. More simply, you could avoid serial ids. One approach, you could use a GUID. Unfortunately I couldn't find a way to have SQLite generate the GUID for you by default, so you'd have to generate it in your application. There also isn't a GUID type, but you could store it as a string or a binary blob.
Or, perhaps there is something in your other columns that would serve as a suitable key. If you know that inserts won't happen more frequently than the resolution of your timestamp format of choice (SQLite offers several, see section 1.2), then maybe (node, timestamp_column) is a good primary key.
Or, you could use SQLite's AUTOINCREMENT, but set the starting number on each node via the sqlite_sequence table such that the generated serials won't collide. Since rowid is SQLite is a 64-bit number, you could do this by generating a unique 32-bit number for each node (IP addresses are a convenient, probably unique 32 bit number) and shifting it left 32 bits, or equivalently, multiplying it by 4294967296. Thus, the 64-bit rowid becomes effectively two concatenated 32-bit numbers, NODE_ID, RECORD_ID, guaranteed to not collide unless one node generates over four billion records.
How about...
ASSUMPTIONS
Only need uniqueness in PK, not sequential-ness
Source table has a PK
Create the central table with one extra column, the node number...
CREATE TABLE tstage (
node INTEGER NOT NULL,
id INTEGER NOT NULL, <<< or whatever the source table PK is
PRIMARY KEY (node, id)
:
);
When you rollup the data into the centralized node, insert the number of the source node into 'node' and set 'id' to the source table's PRIMARY KEY column value...
INSERT INTO tstage (nodenumber, sourcetable_id, ...);
There's no need to maintain another autoincrementing column on the central table because nodenumber+sourcetable_id will always be unique.

Resources