Hi,

I'm a big fan of SQLite, but I've been having some terrible speed problems
when doing joins between subqueries.  Strangely, if I join twice (first to
the main table and then to its subquery), I can get the same answer in
tenths of seconds and not minutes - or one thousand times faster.

For example, this takes minutes: CPU Time: user 263.759513 sys 2.237379
SELECT s.Id, s.BookingId, s.At, s.DateIn, s.DateOut, s.RoomUID,
s.RatePlanUID FROM StayV s
 WHERE NOT EXISTS (SELECT NULL FROM InvoiceItemV i WHERE i.PriceUID IS NULL
AND i.StayUID=s.UID);

While this takes tenths of seconds for the same result: CPU Time: user
0.231575 sys 0.021046
SELECT s.Id, s.BookingId, s.At, s.DateIn, s.DateOut, s.RoomUID,
s.RatePlanUID FROM StayV s
 WHERE NOT EXISTS (SELECT NULL FROM InvoiceItem i WHERE i.PriceUID IS NULL
AND i.StayUID=s.UID AND EXISTS (SELECT NULL FROM InvoiceItemV v WHERE
i.UID=v.UID));

The latter looks like more work yet is 1000 times faster.  Thoughts?

Background:
Generally, I don't delete or update records for this project.  I create new
ones with a matching ID field.  This allows cool things like getting a
customers address as it was when the invoices were made without storing it
with each invoice.  Or navigating the database as it was yesterday, before
little Jimmy started playing.

This is but one example - here I'm trying to find a complete list of
unbilled stays at a motel.  (Or a stay that has been updated, but there's
not matching invoice item.)

For simplicity I use a couple of views, so I can see the Stays as it was at
a given time.

CREATE VIEW StayV AS SELECT * FROM Stay WHERE UID IN (SELECT MAX(UID) FROM
Stay WHERE At <= 20160401000000 GROUP BY Id) AND Remove = 0;

CREATE VIEW InvoiceItemV AS SELECT * FROM InvoiceItem WHERE UID IN (SELECT
MAX(UID) FROM InvoiceItem WHERE At <= 20160401000000 GROUP BY Id) AND
Remove = 0;

Here are the tables:

CREATE TABLE InvoiceItem (
UID INTEGER PRIMARY KEY AUTOINCREMENT,
Id INTEGER,
At INT CHECK(TYPEOF(At) IN ("integer","null")),
UserUID INT NOT NULL CHECK(TYPEOF(UserUID) = "integer"),
Remove INT NOT NULL DEFAULT 0 CHECK(Remove = 0 OR Remove = 1),
ClientId INTEGER, -- MUST be NULL if InvoiceId or StayId filled out.
InvoiceId INTEGER,
StayUID INTEGER,
PriceUID INTEGER, -- MUST be NULL if StayId filled out.
Quantity FLOAT NOT NULL DEFAULT 1,
DescriptionOverride TEXT,
PriceOverride INT -- NULL if unused
);

CREATE TRIGGER InvoiceItemTrigger AFTER INSERT ON InvoiceItem BEGIN
UPDATE InvoiceItem SET Id=NEW.UID WHERE UID=NEW.UID AND NEW.Id IS NULL;
UPDATE InvoiceItem SET At=CAST(STRFTIME('%Y%m%d%H%M%S','now') as "integer")
WHERE UID=NEW.UID AND NEW.At IS NULL;
END;

CREATE TABLE Stay (
UID INTEGER PRIMARY KEY AUTOINCREMENT,
Id INTEGER,
At INT CHECK(TYPEOF(At) IN ("integer","null")),
UserUID INT NOT NULL CHECK(TYPEOF(UserUID) = "integer"),
Remove INT NOT NULL DEFAULT 0 CHECK(Remove = 0 OR Remove = 1),
BookingId INT NOT NULL,
RoomUID INT NOT NULL REFERENCES Room(UID),
RatePlanUID TEXT NOT NULL DEFAULT '', -- TODO
DateIn INT NOT NULL,
DateOut INT NOT NULL,
Eta INT NOT NULL DEFAULT 0,
Adults INT NOT NULL DEFAULT 0,
Children INT NOT NULL DEFAULT 0,
Infants INT NOT NULL DEFAULT 0,
ClientId INT NOT NULL,
Price INT,
Comment TEXT NOT NULL DEFAULT '',
Status INT NOT NULL DEFAULT 0
);
CREATE TRIGGER StayTrigger AFTER INSERT ON Stay BEGIN
UPDATE Stay SET Id=NEW.UID WHERE UID=NEW.UID AND NEW.Id IS NULL;
UPDATE Stay SET At=CAST(STRFTIME('%Y%m%d%H%M%S','now') as "integer") WHERE
UID=NEW.UID AND NEW.At IS NULL;
END;

CREATE INDEX InvoiceItem_StayUID ON InvoiceItem(StayUID);
Thanks.

Reply via email to