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

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.

Related

SQLite: Does PRIMARY KEY default to ASC?

Ie, are the following two SQL statements equivalent in SQLite?
CREATE TABLE posts (
id INTEGER PRIMARY KEY
);
CREATE TABLE posts (
id INTEGER PRIMARY KEY ASC
);
Yes they are.
There is no need to specify ASC and beware that if you were to specify DESC, then NO they are then not equivalent (see 4 below) as id INTEGER PRIMARY KEY DESC is an exclusion to the column being an alias of the rowid column as per :-
The exception mentioned above is that if the declaration of a column
with declared type "INTEGER" includes an "PRIMARY KEY DESC" clause, it
does not become an alias for the rowid and is not classified as an
integer primary key. This quirk is not by design. It is due to a bug
in early versions of SQLite. But fixing the bug could result in
backwards incompatibilities. Hence, the original behavior has been
retained (and documented) because odd behavior in a corner case is far
better than a compatibility break.
ROWIDs and the INTEGER PRIMARY KEY
You can use id INTEGER, PRIMARY KEY(id, DESC), but still the order defaults to ASC when retrieving the column as it is an alias of the rowid (see 5 below )
Perhaps consider the following :-
DROP TABLE IF EXISTS posts1;
CREATE TABLE posts1 (
id INTEGER PRIMARY KEY
);
DROP TABLE IF EXISTS posts2;
CREATE TABLE posts2 (
id INTEGER PRIMARY KEY ASC
);
DROP TABLE IF EXISTS posts3;
CREATE TABLE posts3 (
id INTEGER PRIMARY KEY DESC
);
DROP TABLE IF EXISTS posts4;
CREATE TABLE posts4 (
id INTEGER, PRIMARY KEY (id DESC)
);
INSERT INTO posts1 VALUES(null),(null),(null);
INSERT INTO posts2 VALUES(null),(null),(null);
INSERT INTO posts3 VALUES(null),(null),(null);
INSERT INTO posts4 VALUES(null),(null),(null);
SELECT * FROM sqlite_master WHERE name LIKE '%posts%';
SELECT * FROM posts1;
SELECT * FROM posts2;
SELECT * FROM posts3;
SELECT * FROM posts4;
Results
1
The query SELECT * FROM sqlite_master WHERE name LIKE '%posts%'; results in :-
As you can see posts3 is significantly different as the index sqlite_autoindex_posts3_1 has been created
The others do not have a specific index created as the id column is an alias of the rowid column
The data for rowid tables is stored as a B-Tree structure containing
one entry for each table row, using the rowid value as the key. This
means that retrieving or sorting records by rowid is fast. Searching
for a record with a specific rowid, or for all records with rowids
within a specified range is around twice as fast as a similar search
made by specifying any other PRIMARY KEY or indexed value.
ROWIDs and the INTEGER PRIMARY KEY
2
The query SELECT * FROM posts1; results in :-
3
The query SELECT * FROM posts2;, confirms the initial YES answer as per :-
4
The query SELECT * FROM posts3;, may be a little confusing, but shows that id INTEGER PRIMARY KEY DESC does not result in an alias of the rowid and in the case of no value or null being inserted into the column, the value is null rather than an auto generated value. There is no UNIQUE constraint conflict (as nulls are considered as being different values).
5
The query SELECT * FROM posts4; produces the same result as for 1 and 2 even though id INTEGER, PRIMARY KEY (id DESC) was used. Confirming that even if DESC is applied via the column definition that the sort order is still defaults to ASC (unless the ORDER BY clause is used).
Note that this peculiarity is specific to the rowid column or an alias thereof.
See both https://www.sqlite.org/lang_createtable.html#rowid and https://www.sqlite.org/lang_createindex.html for a more complete answer. Shawn's link is specific to INTEGER PRIMARY KEY which matches the example code, but the more general question is not answered explicitly in either location, but can be deduced by reading both.
Under SQL Data Constraints, the first link says
In most cases, UNIQUE and PRIMARY KEY constraints are implemented by creating a unique index in the database. (The exceptions are INTEGER PRIMARY KEY and PRIMARY KEYs on WITHOUT ROWID tables.)
The CREATE INDEX page explains that originally the sort order was ignored and all indices were generated in ascending order. Only as of version 3.3.0 is the DESC order "understood". But even that description is somewhat vague, however altogether it is apparent that ASC is the default.

Efficient insertion of row and foreign table row if it does not exist

Similar to this question and this solution for PostgreSQL (in particular "INSERT missing FK rows at the same time"):
Suppose I am making an address book with a "Groups" table and a "Contact" table. When I create a new Contact, I may want to place them into a Group at the same time. So I could do:
INSERT INTO Contact VALUES (
"Bob",
(SELECT group_id FROM Groups WHERE name = "Friends")
)
But what if the "Friends" Group doesn't exist yet? Can we insert this new Group efficiently?
The obvious thing is to do a SELECT to test if the Group exists already; if not do an INSERT. Then do an INSERT into Contacts with the sub-SELECT above.
Or I can constrain Group.name to be UNIQUE, do an INSERT OR IGNORE, then INSERT into Contacts with the sub-SELECT.
I can also keep my own cache of which Groups exist, but that seems like I'm duplicating functionality of the database in the first place.
My guess is that there is no way to do this in one query, since INSERT does not return anything and cannot be used in a subquery. Is that intuition correct? What is the best practice here?
My guess is that there is no way to do this in one query, since INSERT
does not return anything and cannot be used in a subquery. Is that
intuition correct?
You could use a Trigger and a little modification of the tables and then you could do it with a single query.
For example consider the folowing
Purely for convenience of producing the demo:-
DROP TRIGGER IF EXISTS add_group_if_not_exists;
DROP TABLE IF EXISTS contact;
DROP TABLE IF EXISTS groups;
One-time setup SQL :-
CREATE TABLE IF NOT EXISTS groups (id INTEGER PRIMARY KEY, group_name TEXT UNIQUE);
INSERT INTO groups VALUES(-1,'NOTASSIGNED');
CREATE TABLE IF NOT EXISTS contact (id INTEGER PRIMARY KEY, contact TEXT, group_to_use TEXT, group_reference TEXT DEFAULT -1 REFERENCES groups(id));
CREATE TRIGGER IF NOT EXISTS add_group_if_not_exists
AFTER INSERT ON contact
BEGIN
INSERT OR IGNORE INTO groups (group_name) VALUES(new.group_to_use);
UPDATE contact SET group_reference = (SELECT id FROM groups WHERE group_name = new.group_to_use), group_to_use = NULL WHERE id = new.id;
END;
SQL that would be used on an ongoing basis :-
INSERT INTO contact (contact,group_to_use) VALUES
('Fred','Friends'),
('Mary','Family'),
('Ivan','Enemies'),
('Sue','Work colleagues'),
('Arthur','Fellow Rulers'),
('Amy','Work colleagues'),
('Henry','Fellow Rulers'),
('Canute','Fellow Ruler')
;
The number of values and the actual values would vary.
SQL Just for demonstration of the result
SELECT * FROM groups;
SELECT contact,group_name FROM contact JOIN groups ON group_reference = groups.id;
Results
This results in :-
1) The groups (noting that the group "NOTASSIGNED", is intrinsic to the working of the above and hence added initially) :-
have to be careful regard mistakes like (Fellow Ruler instead of Fellow Rulers)
-1 used because it would not be a normal value automatically generated.
2) The contacts with the respective group :-
Efficient insertion
That could likely be debated from here to eternity so I leave it for the fence sitters/destroyers to decide :). However, some considerations:-
It works and appears to do what is wanted.
It's a little wasteful due to the additional wasted column.
It tries to minimise the waste by changing the column to an empty string (NULL may be even more efficient, but for some can be confusing)
There will obviously be an overhead BUT in comparison to the alternatives probably negligible (perhaps important if you were extracting every Facebook user) but if it's user input driven likely irrelevant.
What is the best practice here?
Fences again. :)
Note Hopefully obvious, but the DROP statements are purely for convenience and that all other SQL up until the INSERT is run once
to setup the tables and triggers in preparation for the single INSERT
that adds a group if necessary.

SQLite Trigger on one table for increment and decrement column other table

So I have two tables, I want to have a column on one table increment or decrement as items on another table are added or deleted, the link is the FOREIGN key.
I have two triggers but I'm not sure if they would work.
So I'd like some confirmation as to whether or not I'm barking up the right tree,if I'm going wrong or not an any fixes improvements?
SQL=
CREATE TABLE IF NOT EXISTS Agents(
Id INTEGER PRIMARY KEY,
Name TEXT,
Office_Count INT,
);
CREATE TABLE IF NOT EXISTS Branches(
Id INTEGER PRIMARY KEY,
Street_Address TEXT,
City TEXT,
Postcode TEXT,
Agents_Id INTEGER,
FOREIGN KEY(Agents_Id) REFERENCES Branches(Id)
);
CREATE TRIGGER Branches_Count_Increment AFTER INSERT ON Branches
BEGIN
UPDATE Agents SET
Office_Count=(MAX(Office_Count)+ 1 FROM Branches Where Agents_Id=Agents.Id) WHERE Id=NEW.Id;
END;
CREATE TRIGGER Branches_Count_Decrement AFTER DELETE ON Branches
BEGIN
UPDATE Agents SET
Office_Count=(MAX(Office_Count)- 1 FROM Branches Where Agents_Id=Agents.Id) WHERE Id=NEW.Id;
END;
You are barking in the general direction of an okay tree.
But the outer WHEREs use the wrong ID: the agents table must use an agent ID, which NEW.id is not.
And the MAX is not needed, and a subquery would need a SELECT.
UPDATE Agents
SET Office_Count = Office_Count + 1
WHERE Id = NEW.Agents_Id;

Speed up SQL select in SQLite

I'm making a large database that, for the sake of this question, let's say, contains 3 tables:
A. Table "Employees" with fields:
id = INTEGER PRIMARY INDEX AUTOINCREMENT
Others don't matter
B. Table "Job_Sites" with fields:
id = INTEGER PRIMARY INDEX AUTOINCREMENT
Others don't matter
C. Table "Workdays" with fields:
id = INTEGER PRIMARY INDEX AUTOINCREMENT
emp_id = is a foreign key to Employees(id)
job_id = is a foreign key to Job_Sites(id)
datew = INTEGER that stands for the actual workday, represented by a Unix date in seconds since midnight of Jan 1, 1970
The most common operation in this database is to display workdays for a specific employee. I perform the following select statement:
SELECT * FROM Workdays WHERE emp_id='Actual Employee ID' AND job_id='Actual Job Site ID' AND datew>=D1 AND datew<D2
I need to point out that D1 and D2 are calculated for the beginning of the month in search and for the next month, respectively.
I actually have two questions:
Should I set any fields as indexes besides primary indexes? (Sorry, I seem to misunderstand the whole indexing concept)
Is there any way to re-write the Select statement to maybe speed it up. For instance, most of the checks in it would be to see that the actual employee ID and job site ID match. Maybe there's a way to split it up?
PS. Forgot to say, I use SQLite in a Windows C++ application.
If you use the above query often, then you may get better performance by creating a multicolumn index containing the columns in the query:
CREATE INDEX WorkdaysLookupIndex ON Workdays (emp_id, job_id, datew);
Sometimes you just have to create the index and try your queries to see what is faster.

SQLite update on select (or vice versa)

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

Resources