I am making a restaurant POS app for android and I am trying to decide the best way to model the database for it using Room ORM that ensures maintainability. My database needs, among a lot of other things, to keep a record of all items sold within a transaction/order, as well as a log of the orders themselves and a list of the food products sold within the restaurant.
Considering the following tables (for brevity purposes I only include columns I think relevant to the question and may not illustrate all the information I will need to catalog), I can create a table that includes a log of all the orders ever placed and call it all_orders:
all_orders
-----------
id (PK)
oder_details_id (FK) - referencing the PK from order_details table
date
notes
total
payment_type
I can also create a table that contains all the food products/dishes that the restaurant serves, and we’ll call it all_items:
all_items
---------
id (PK)
name
category
price
No problems there so far, but my current confusion lies here—how do I manage to keep a log of the actual food items sold within an order?
One approach I thought about was to create a table per order number, but creating tables dynamically is already a problem and having 60,000 tables at the end of the year will be a maintainability nightmare.
So my other possible solution is to create a table called order_details that will probably end up with hundreds of thousands of entries per year with the following columns:
order_details
-------------
id (PK)
item_id (FK) - referencing the PK from the all_items table
order_id (FK) - referencing the PK from the all_orders table
quantity_ordered
And when a user wants to pull up an order from say, last week, the program can use a join query that will produce the following to be displayed in the app’s UI:
order
---------
id (PK)
date (from the all_orders table)
name (from all_items)
category (from all_items)
price (from all_items)
total (from all_orders)
payment_type (from all_orders)
I am afraid that the order_details table is just too broad since it will contain hundreds of thousands of entries, and querying it for entries will be sluggish. I'm sure indexing it will help, but is this the correct approach to this problem? If not, is there a better, “best practice” solution? If possible something that focuses on grouping any order and its items together without just dumping all items from all orders into one table. Any help will be most appreciated.
Edit: This question is not a duplicate of this, and while helpful, the supplied link has not provided any additional context on what I am really asking about nor is it entirely relevant to the answer I am after. I have bolded my last original paragraph since my question is really about a how I can model the above data as it isn't clear to me based on my research how to store actual order details attached to an order (many tutorials/similar questions I've come across fail short of thoroughly explaining the aforementioned).
The all_orders table would be superfluous as that is just repeating other data and would be contrary to normalisation.
You probably want a category table rather than repeat data (i.e. normalise categories).
Likewise, you also probably want a payment_type table (again to normalise).
Creating individual tables for orders would probably just create a nightmare.
Price and total aren't they the same? Saying that totals can be derived when extracting the data so there is no need to store such information.
As such the following structure schema may be close to what you want :-
DROP TABLE IF EXISTS item;
DROP TABLE IF EXISTS category;
CREATE TABLE IF NOT EXISTS category (_id INTEGER PRIMARY KEY, category_name TEXT);
CREATE TABLE IF NOT EXISTS item (
_id INTEGER PRIMARY KEY,
item_name TEXT UNIQUE,
category_ref INTEGER REFERENCES category(_id) ON DELETE CASCADE ON UPDATE CASCADE,
item_price REAL
);
DROP TABLE IF EXISTS payment_type;
CREATE TABLE IF NOT EXISTS payment_type (
_id INTEGER PRIMARY KEY,
payment_type TEXT UNIQUE,
surcharge REAL
);
-- NOTE cannot call a table order as it is a keyword (not rea true but have to enclose the name e.g.g [order]).
DROP TABLE IF EXISTS customer_order;
CREATE TABLE IF NOT EXISTS customer_order (
_id INTEGER PRIMARY KEY,
customer_name TEXT,
date TEXT DEFAULT CURRENT_TIMESTAMP,
payment_type_ref INTEGER REFERENCES payment_type(_id) ON DELETE CASCADE ON UPDATE CASCADE
);
DROP TABLE IF EXISTS order_detail;
CREATE TABLE IF NOT EXISTS order_detail (
customer_order_ref INTEGER REFERENCES customer_order(_id) ON DELETE CASCADE ON UPDATE CASCADE,
item_ref REFERENCES item(_id) ON DELETE CASCADE ON UPDATE CASCADE,
quantity
);
Example
The following is native SQL that demonstrates the schema above :-
Part 1 adding (inserting) the data :-
INSERT INTO category (category_name) VALUES
('Fish'),('Beef'),('Chicken'),('Lamb'),('Sea Food')
;
INSERT INTO item (item_name, item_price, category_ref) VALUES
('Fish and Chips',11.30,1),
('Steak and Kidney Pudding',15.45,2),
('Lamb Chops, Mashed Potato and Gravy',17.40,3)
;
INSERT INTO payment_type (payment_type, surcharge) VALUES
('Master Card',0.05),('Visa',0.05),('Cash',0),('American Express',0.15)
;
INSERT INTO customer_order (customer_name, payment_type_ref) VALUES
('Fred',3),
('Mary',1),
('Tom',2),
('Jane',4)
;
INSERT INTO order_detail (customer_order_ref, item_ref, quantity) VALUES
(1,1,2),(1,2,1), -- Fred (id 1) orders 2 Fish and Chips (id 1) and 1 Steak and Kidney (id 2)
(2,3,10), -- Mary orders 10 Lamb chops
(3,2,1),(3,1,1),(3,3,1), -- Tom orders 1 of each
(4,1,1) -- Just Fish and chips for Jane
;
Part 2 - Extracting Useful(perhaps) Data
Here's and example of what you can do with SQL which includes derived data (as suggested above) :-
SELECT
customer_name,
date,
group_concat(item_name) ||'('||quantity||')' AS items,
sum(item_price) AS total_price,
payment_type,
round(sum(item_price) * surcharge,2) AS surcharge,
round((sum(item_price) * surcharge) + sum(item_price),2) AS total_price
FROM customer_order
JOIN order_detail ON customer_order._id = order_detail.customer_order_ref
JOIN item ON order_detail.item_ref = item._id
JOIN payment_type ON customer_order.payment_type_ref = payment_type._id
GROUP BY customer_order._id -- Treats all data for an order as a single row allowing the use of aggregate functions on the groups e.g. sum, group_concat
;
Result
I wanted to create two related tables. My query that is creating tables looks like this:
static final String SQL_CREATE_TABLE_LISTS =
"CREATE TABLE Lists(Id INTEGER PRIMARY KEY AUTOINCREMENT, Name TEXT);";
static final String SQL_CREATE_TABLE_ITEMS =
"CREATE TABLE Items(IdList INTEGER, ItemName TEXT, FOREIGN KEY(IdList) REFERENCES Lists(Id));";
I want now insert and select some data from table Items, but I do not know how the query should looks like. Lets say I have one record in table Lists: id=1 and name=element1. And now I want to add 3 records to table Items, so IdList will be 1, and name will be item1, item2 and item3. How the inserting query will be like? And then, if I want to take for ex. all Names from table Items that its IdList is 1, how the select query will be like? Thanks.
A FOREIGN KEY is a Constraint (Rule that MUST be followed), it does not define a relationshop/link for extracting data. In other words it is saying if the rule is not met then a row cannot be inserted. It is not saying every time you access either of the tables that they are automatically linked.
When inserting you you would insert the Lists first, you would then insert the Items using(checking) the available Lists. You cannot insert into/across multiple tables directly.
You need to use JOIN when querying the data as FOREIGN KEY is just a rule (constraint) that is checked when inserting a row.
So you would do something along the line of:-
SELECT Lists.Id, Lists.Name, Items.ItemName FROM Lists JOIN Items ON Lists.Id = Items.IdList
I have a table in a SIP app that stores the call history for all your accounts. I am not a friend of multi-column primary keys, so I put an auto-increment column as my PK.
The first columns of the table are
CREATE TABLE IF NOT EXISTS CALLHISTORY
(
CALLHISTORYID INTEGER PRIMARY KEY AUTOINCREMENT,
ACCOUNTID INTEGER NOT NULL,
CALLID TEXT NOT NULL,
... + many more columns
I get a callId from the CallManager (SIP Server), which is unique for an account (so accountId + callId together build a unique pair).
I set up indices like this:
CREATE INDEX IF NOT EXISTS IX_CALLHISTORY_ACCOUNTID ON CALLHISTORY (ACCOUNTID);
CREATE UNIQUE INDEX UIX_CALLHISTORY_ACCOUNTID_CALLID ON CALLHISTORY (ACCOUNTID,CALLID);
I have several queries on this table in the app, some querying only the accountId, some querying the pair (depends on Activity).
Do I really need both indices or will queries that have only the accountId in the where clause use the unique index too?
Thanks for your help!
From the doc (1.6 Multi-Column Indices):
A multi-column index follows the same pattern as a single-column
index; the indexed columns are added in front of the rowid. The only
difference is that now multiple columns are added. The left-most
column is the primary key used for ordering the rows in the index. The
second column is used to break ties in the left-most column. If there
were a third column, it would be used to break ties for the first two
columns. And so forth for all columns in the index. Because rowid is
guaranteed to be unique, every row of the index will be unique even if
all of the content columns for two rows are the same.
As a result, the index of the ACCOUNTID and CALLID fields already handles the ordering of the ACCOUNTID so creating index on ACCOUNTID is unnecessary in this case.
According to some blogs like http://reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/ and even in some of the aswers here.
One of the first steps before including the datababe into the project is to rename the primary id field of your tables to "_id" so Android will know where to bind the id field of your tables.
What should be done with a table that have a combined primary key
Assume that i'm creating the relation between the product id and the store id to assign it's price.
CREATE TABLE `Products-Stores` (
`product` INTEGER NOT NULL,
`store` INTEGER NOT NULL,
`price` INTEGER NOT NULL,
PRIMARY KEY(product,store)
);
There is no need to rename any column in your database. SQL allows column aliases like this:
SELECT integer_primary_key AS _id
...
The only time this is necessary is when you are using a ListAdapter to display the contents of a cursor queried from your DB. You must have an integer primary key column, named "_id" in the cursor, to do that
Better yet, every SQLite database table has an implicit column named "rowid". You don't even have to have your own integer primary key column. Just use rowid, like so:
SELECT rowid AS _id
...
EDITED TO INCLUDE #CL's EXPLANATION OF WORKING JOINS
Obviously, this trick won't work, for many kinds of joins. As long as the rowids are unique over all the rows in the join, though, it works fine.
I have a databse with three tables.
The first table is something like:
_id user
The second table:
_id route user_id
and user_id is exactly the _id from the first table.
So when I insert in the first tabel a new record,I should keep the _id in order to insert it
in the second table.But how could I keep something that is autoincremented and given by the database?:-S
The SQLiteDatabase has an insert() method, which returns the _id you are looking for.
There may be a better way, but you could grab (query for) the _id field right after you do your commit to the first table. Then you will have the user_id for use in the second table.