Storing/retrieving highly dynamic data from an Android app - android

I'm trying to develop an Android app to help me test ceramic glazes, and have run into a problem with storing data. The main feature I want is, within one glaze, the ability to modify its recipe, yet still keep the older versions of that recipe. Since a lot of my data is repetitive (finish, opacity, firing atmosphere, etc.), a database would be the best way to store that data. However, each glaze can have any number of versions, and the recipe within each version can have any number of ingredients (normally 1-10, but as much as 20). I cannot think of a way to store and retrieve this "two dimensional" dynamic data. I could make an individual database for each glaze, but I feel that defeats the purpose of a database. Any help implementing a way to store this highly dynamic data would be appreciated. Thank you.

I think you are looking to have a number of related tables.
I'd suggest a glaze base table, containing the base glazes, a table for recipe/versioned glazes. An ingredients/properties table. An then an extended link/associative table table this being used to link ingredients properties to recipes and also contain amounts.
The Glaze Table would be very simple just the name of the glaze and a unique identifier. e.g
CREATE TABLE IF NOT EXISTS glazes (_id INTEGER PRIMARY KEY, glazename TEXT)
The GlazeVersion Table is again relatively simple, a unique identifier, the version and a reference to it's parent/base glaze. e.g.
CREATE TABLE glazeversions (_id INTEGER PRIMARY KEY, version TEXT, glazebase_ref INTEGER)
The Indgredients table is yet again simple, a unique id and name e.g.
CREATE TABLE ingredients (_id INTEGER PRIMARY KEY, ingredient TEXT)
The Recipes table would have a reference to the glazeversion (and therefore implicitly the glaze), a reference to the ingredient and a field for the amount of the ingredient e.g.
CREATE TABLE IF NOT EXISTS recipes (glazeversion_ref INTEGER, ingredient_ref INTEGER, amount INTEGER, PRIMARY KEY(glazeversion_ref, ingredient_ref))
So lets assume we have 3 base glazes RED, GREEN and BLUE. There would be 3 entries in the glazes table (with for illustrative purposes id's 1 2 and 3).
Also lets assume that each has three versions named Version001, Version002 and Version003 (9 rows in the glazeversion table id's 1-9 for illustrative purposes).
The resultant glazeversions table could be :-
A query could be written to show the links/associations/references e.g.
SELECT * FROM glazeversions JOIN glazes WHERE glazebase_ref = glazes._id ORDER BY glazename
the result would be :-
For the Ingredients table lets assume that there are 5 ingredients, Ochre, Calcium, Atmosphere, Temperature (temp for short) and Duration (id's 1-5 respectively). e.g.
Now it's onto the Recipes table, let's initially assume 1 recipe for glaze Blue version Version001 and it has 3 ingredients Atmosphere, Temp and Ochre
The recipes table would have 3 rows all three referencing the glazeversion Version001 for glaze Blue (_id 2) but each referencing the respective ingredient (id's 1, 3 and 4).
We'll also assume two other glazes:-
Red Version001 with 4 ingredients Duration, Temp, Atmosphere and Ochre so 4 rows all referencing glazeversion Version001 for Red (_id 1), respective ingredients id's 1, 3, 4 and 5.
Green Version003 with 2 ingredients Ochre and Calcium. So both rows reference glazeversion Version003 for Blue (_id 9), repective ingredients are id's 1 and 2.
So the table looks like:-
The following query is an example of tying everything together:-
SELECT glazename, version, ingredient, amount FROM recipes JOIN ingredients ON ingredient_ref = ingredients._id JOIN glazeversions ON glazeversion_ref = glazeversions._id JOIN glazes ON glazebase_ref = glazes._id
This would result in :-
If you wanted just glaze Green Version003 then you could add a WHERE clause along the lines of:-
SELECT glazename, version, ingredient, amount FROM recipes JOIN ingredients ON ingredient_ref = ingredients._id JOIN glazeversions ON glazeversion_ref = glazeversions._id JOIN glazes ON glazebase_ref = glazes._id WHERE glazeversions._id = 9
which would result in :-

You should be able to add multiple glazes that have the same identifier (say name for example), and for each one add the relevant information as well as a date. Then when you choose to view a glaze, you pull down all the info for a glaze with that identifier, and sort by the date.
Really the only extra thing you would be adding is another date, id, and name. Otherwise the entry holds all the same data you would otherwise need.

Related

How to insert only 3 columns data in room database table if we have more columns?

I have a project that I wrote in kotlin. I want to insert data in different columns of the same table on different pages. I have specified these columns in the dataclass, but it gives a null data error.
In order to make this insert process more healthy, should I divide the table into two separate tables or send static 'null' data and update these fields?
In a database, such as SQLite (which Room is a wrapper around), the unit of insertion is a row.
A row will consist of the same number of columns. You cannot insert a column on it's own, other than if you ALTER the table to add or remove a column, when the change is reflected throughout the entire table.
if adding a column then a DEFAULT VALUE must be provided, this could be the default/implicit value of null or another specific value.
Room with Kotlin will apply a constraint (rule) of NOT NULL unless nulls are specifically allowed using for example ?
var theColumn: Int has the implicit NOT NULL
var theColumn: Int? does not have the implicit NOT NULL and nulls can be stored
var theColumn: Int=-1 will apply a default value of -1 in the absence of the field not being supplied a value when instantiating the object.
var theColumn: Int?=null will apply null in the absence of the field not being supplied a value when instantiating the object.
obviously fields may be altered before inserting the object, if var rather than val is used.
The data stored in the column can be interpreted to represent whatever you wish, often NULL will be used to represent a special situation such as no value.
If using an #Insert annotated function, then ALL columns are applied the values as obtained from the object or objects passed to the function. In Kotlin whether or not NULLs can be used is dependent upon the field definition or in some cases the #NonNull annotation.
#Insert indicates what is termed as a convenience method, it actually builds the underlying SQL along with binding the values using the SQLite API.
However, if you want flexibility, then an #Query annotation with suitable INSERT SQL statement can be used.
e.g. you could perhaps have a table that has 4 columns COL1, COL2, COL3 and COL4 and only apply some of the columns (the DEFAULT VALUE will be applied to the other column if specified, if not the NULL but if there is a NOT NULL constraint then a conflict would be raised).
So to insert when only two of the columns (COL2 and COL4) then you could use:-
#Query("INSERT INTO theTable (COL2,COL4) VALUES(:valueForCol2,:valueForCol4)")
fun insertCol2AndCol4Only(valueForCol2: Int, valueForCol4: Int?)
Note that valueForCol4 could be NULL. However, whether or not a NULL will result in a conflict depends upon how the field is defined in the #Entity annotated class.
Conflicts (breaking a rule) can be handled by SQLite, depending upon the type of the conflict. UNIQUE, PRIMARY KEY (which is really a UNIQUE conflict), CHECK (Room doesn't cater for CHECK constraints) and NOT NULL constraints can be handled in various ways at the SQLite level.
A common use of conflict handling is to IGNORE the conflict, in which case the action (INSERT or UPDATE) is ignored. In the case of INSERT the row is not inserted but SQLite ignores the conflict and doesn't issue an error.
So if for example COL4's field was var COL4: Int and not var COL4: Int? then the insert would fail and an SQlite Exception would occurr.
However if instead
#Query("INSERT OR IGNORE INTO theTable (COL2,COL4) VALUES(:valueForCol2,:valueForCol4)")
were used and the COL4 field were defined as var COL4: Int (implied NOT NULL constraint) then the conflict if NULL was passed as valueForCol4 then the row would not be inserted but no failure would occur as the NOT NULL conflict would be ignored.
With the #Insert annotation you can defined this conflict handling via the onConflictStrategy parameter e.g. #Insert(onConflictStrategy=OnConflict.IGNORE)
You may wish to consider reading the following:-
The On Conflict Clause
INSERT
In order to make this insert process more healthy, should I divide the table into two separate tables or send static 'null' data and update these fields?
Note the above is only a summary, INTEGER PRIMARY KEY aka #PrimaryKey var id: Long?=null or variations such as #PrimaryKey(autoGenerate=true) etc has specifically not been discussed.
The design of the database could be handled either way, from the very limited description of the scenario, a most likely suitable scenario cannot really be advised, although either could probably be an approach.
Additional
Based upon the comment:-
For example, I'm going to add the features of a car to the database, but it could be a different type at a time. So on the first page, the type of car will be chosen, like off road, sedan, 4x4, hatchback.
The perhaps consider having a feature table and a mapping table for a many-many relationship between car and it's features as per my response:-
I would suggest that features be a table and with a many-many relationship with the car. That is a car could have a 0-n features and a feature could be used by 0-n cars. The many-many relationship would require a third table known by many terms such as an associative table/reference table/ mapping table. Such a table has 2 core columns a column to map to the car and a column to map to the feature, the primary key being a composite of both these columns.
Here's a basic example of how this could work from an SQLite basis:-
DROP INDEX IF EXISTS carFeatureMap_idxon_feature;
DROP TABLE IF EXISTS carFeatureMap;
DROP TABLE IF EXISTS car;
DROP TABLE IF EXISTS feature;
CREATE TABLE IF NOT EXISTS car (
carId INTEGER PRIMARY KEY,
carname TEXT /* and other columns */
);
CREATE TABLE IF NOT EXISTS feature (
featureId INTEGER PRIMARY KEY,
featureDescription TEXT
);
CREATE TABLE IF NOT EXISTS carFeatureMap (
carIdMap INTEGER REFERENCES car(carId) ON DELETE CASCADE ON UPDATE CASCADE,
featureIdMap INTEGER REFERENCES feature(featureId) ON DELETE CASCADE ON UPDATE CASCADE,
PRIMARY KEY(carIdMap, featureIdMap)
);
/* Should improve efficiency of mapping from a feature */
CREATE INDEX IF NOT EXISTS carFeatureMap_idxon_feature ON carFeatureMap(featureIdMap);
/* Add some features */
INSERT OR IGNORE INTO feature VALUES(100,'4x4'),(101,'Sedan'),(106,'Convertable'),(null /*<<<< featureId generated by SQLite*/ ,'Hatchback');
/*Report1 Output the features */
SELECT * FROM feature;
/* Add some cars */
INSERT OR IGNORE INTO car VALUES(10,'Car1'),(20,'Car2'),(30,'Car3');
/*Report2 Output the cars */
SELECT * FROM car;
/* add the mappings/relationships/associations between cars and features */
INSERT OR IGNORE INTO carFeatureMap VALUES (10,101) /* Car 1 has 4x4*/,(10,106) /* Car 1 has Sedan */,(20,100);
/*Report3 Get the Cars with features cartesian product */
SELECT
car.carName,
featureDescription
FROM car
JOIN carFeatureMap ON car.carId=carFeatureMap.carIdMap
JOIN feature ON featureIdMap=featureId
;
/*Report4 Get the Cars with features with all the features concatendated, i.e. single output per car with features */
SELECT
car.carName,
group_concat(featureDescription,': ') AS allFeatures
FROM car
JOIN carFeatureMap ON car.carId=carFeatureMap.carIdMap
JOIN feature ON featureIdMap=featureId GROUP BY (carId)
;
/*Report5 Similar to the previous BUT if no features then output none so ALL cars are output */
SELECT
carName,
coalesce(
(
SELECT
group_concat(featureDescription)
FROM feature
JOIN carFeatureMap ON carFeatureMap.featureIdMap=featureId AND carFeatureMap.carIdMap=carId
),
'none'
) AS features
FROM car
;
/* Clean Up After Demo*/
DROP INDEX IF EXISTS carFeatureMap_idxon_feature;
DROP TABLE IF EXISTS carFeatureMap;
DROP TABLE IF EXISTS car;
DROP TABLE IF EXISTS feature;
Results from the demo code above
Report1 - The features
Report2 - The cars
**Report3 ** Cars and features
Report 4 Cars and features 2
Report 5 Cars and features 3

How to model a relational database that stores order details?

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

Designing date orientated database sqlite

I'm new in sqlite. I've built database but based on query I was trying to solve for it (which was over-complicated), I was suggested to look into normalising database, which I did, but can't seem to find examples on database that would be orientated around dates like a diary, with lots of daily entries. I'm working on app that would help log in everyday what I've eaten, what exercise did I do, what activities I've done, what was my well-being, how many hours I've slept. I will be able to go back to any day in the past and see what I was up to, so it will have to look up all entries for that particular date.
So I understand I need separate tables for food type, exercise type, activities types, event types and I need main table "diary" which will log each time date and id referencing another table. So I'm wondering if in that diary table I can have date column, id column and lets say type column (which will differentiate which table does id column references) or should I rather have date column and column for each of the other tables ids, even though I will be logging only one type at the time?
Also, would indexing the date column be a good idea?
Or maybe there is a better way to design that database? Any suggestions will be appreciated.
So I understand I need separate tables for food type, exercise type,
activities types, event types
If normalising then perhaps consider a single table for all types with a column to indicate the type.
So I'm wondering if in that diary table I can have date column, id
column and lets say type column (which will differentiate which table
does id column references) or should I rather have date column and
column for each of the other tables ids, even though I will be logging
only one type at the time?
If you are logging and assuming human input (as opposed to automated) then it is highly likely that a timestamp would be sufficient to uniquely identify a row.
As such there would be little need for an id column(in theory).
Saying that SQLite, unless you specify WITHOUT ROWID (which you might consider, this may be of use in deciding:-Clustered Indexes and the WITHOUT ROWID Optimization ), automatically creates a unique row identifier column i.e ROWID.
If you code a column as columnname INTEGER PRIMARY KEY or columnname INTEGER PRIMARY KEY AUTOINCREMENT then columnname becomes an alias for ROWID, in which case the unique value will be provided, if you do not provide a value when inserting.
However, if you were to specify timestamp INTEGER PRIMARY KEY and provide the current date/time as a value for the column when inserting, the current date/time would be stored and would also be indexed (it would have to be unique (current date/time would very likely be)).
So I'd suggest that a log entry need only be something like CREATE TABLE log (timestamp INTEGER PRIMARY KEY, eventref INTEGER);, where eventref is a reference to the event type.
As _id is required at times e.g. for a CursorAdapter then you could specify the columns to be extracted as *, timestemp as _id (3 columns timestamp, eventref and _id (timestamp and _id would be identical))or timstemp as _id, * (3 columns but _id, timestamp and eventref) or timestamp as _id, eventref (2 columns _id and eventref).
So using this model as the basis would minimise columns and be indexed automatically.
An example
You may have the events table as :-
Along with log table as :-
A query such as SELECT * FROM log JOIN events WHERE eventref = _id would give:-
Note! made up timestamps for illustration purposes

SQL: Table with match-results needs team information added, based on team-ids. How do I do this for home and away-teams in one query?

I am programming on Android and only starting with SQL:
What I have are 2 SQL tables, table A contains a list of match-results of sports games, table B contains all information about the teams.
Table A has two team-ids, one for the home-team, one for the away-team.
I want to create a sql query, that gets a match-result for every match, that is linked up to the corresponding teams, e.g. the two team-ids in table A should get replaced by at least the team-name (preferably more columns) of table B.
So in short: For every match in table A -> get all match-info from table A -> add information for the home and the away team from table B, corresponding to home-id and away-id from table A -> deliver result
I achieved this party through an SQL JOIN, but I only managed to join the information for either the home- or the away-team, not both, since the columns get in conflict, since both the home and away team information come from the same table and thus the column names are the same (home team has "team_name", and away team also has "team_name" -> conflict)
How can I achieve this?
For some information on why I would like to do this in one query: I am working on Android, displaying a list of match-results, which are loaded asynchronously via a Loader, which feeds a Cursor to a CursorAdapter. As I understand the cursor, it's its nature to only deliver one result per row of the list, thus all the data querying has to be done in one sql query.
Thank you for your help!
EDIT: My current sql join is this
ScheduleTable.TABLE_SCHEDULE + " JOIN " + TeamsTable.TABLE_TEAMS + " ON " +
ScheduleTable.TABLE_SCHEDULE+"."+ScheduleTable.COLUMN_HOME_TEAM_ID + " = " + TeamsTable.TABLE_TEAMS+"."+TeamsTable.COLUMN_ID
The easiest way to look up a value from another table might be a subquery:
SELECT Date,
Result,
(SELECT Name FROM Teams WHERE ID = Schedule.HomeTeamID
) AS HomeTeamName,
(SELECT Name FROM Teams WHERE ID = Schedule.AwayTeamID
) AS AwayTeamName
FROM Schedule
However, this becomes unwieldy if you need to look up more than one column for a team.
To be able to address a single table that you are using multiple times in the same FROM clause, you must use table aliases:
SELECT Schedule.Date,
Schedule.Result,
HomeTeam.Name,
AwayTeam.Name
FROM Schedule
JOIN Teams AS HomeTeam ON Schedule.HomeTeamID = HomeTeam.ID
JOIN Teams AS AwayTeam ON Schedule.AwayTeamID = AwayTeam.ID

Android SQLite getting records from one table based on another

Please excuse me if its a repeated question. I tried searching here and at google but I couldn't exactly find what I wanted.
I have got two tables A & B.
Table A Fields : id, name, description, rating.
Table B Fields : id, aId (linked to table A), customerId, recommended.
Table A contains my data items for which I'm storing average cumulative ratings provided by users.
Table B stores another attribute for data of Table A. It stores the recommended bit (1 for recommended & 0 for non-recommended).
I want to list all the data from Table A but I want to sort them using recommended bit from Table B. So, if there are 10 records in Table A and 2 records in Table B, while listing all those 10 records, the two from Table B should come first and then the others from Table A. It doesn't matter whether the recommended bit value is a 0 or a 1. While listing the other 8 records from Table A, I want to list the records based on their rating in descending order.
Can someone please guide me in writing this sqlite query for Android app? Thanks in advance!
The left join adds the recommended field to the result set (with a value of NULL if there is no matching B record).
The expression recommended IS NULL or EXISTS(...) returns either 0 or 1:
SELECT DISTINCT A.*
FROM A LEFT JOIN B ON A.id = B.aId
ORDER BY B.recommended IS NULL,
A.rating DESC
Alternatively:
SELECT *
FROM A
ORDER BY NOT EXISTS (SELECT 1
FROM B
WHERE B.aId = A.id),
rating DESC

Categories

Resources