I am using an SQLite database that holds different messaging conversations in a thread id. To get the main conversation list I use the following code:
database.query(true, MessagesHelper.CONVERSATION_TABLE, inboxCols, null, null, MessagesHelper.THREAD_ID, null, "${MessagesHelper.DATE} DESC", null)
The issue is that I provide a function to load in older messages that are not showing, by which I use:
database.insert(MessagesHelper.CONVERSATION_TABLE, null, values)
The issue is that after inserting messages, which are older than the most recent one that the distinct list used to call, it now shows the older added messages in the inbox list, which makes the list all out of order and causes a lot of confusion.
Here is the create statement:
private const val CONVO_CREATE = "CREATE TABLE $CONVERSATION_TABLE($NAME VARCHAR(255), $THREAD_ID VARCHAR(255), $MESSAGE_ID VARCHAR(40), $ADDRESS VARCHAR(14), $BODY VARCHAR(500), $SUBJECT VARCHAR(100), $MMS VARCHAR(255), $MESSAGE_TYPE VARCHAR(7), $MMS_TYPE VARCHAR(20), $CONVERSATION_TYPE VARCHAR(20), $GROUP_ADDRESS VARCHAR(255), $GROUP_NAME VARCHAR(255), $READ VARCHAR(10), $WHO VARCHAR(3), $COUNT INTEGER, $DATE INTEGER);"
Inbox columns are:
val inboxCols = arrayOf(MessagesHelper.COUNT, MessagesHelper.GROUP_ADDRESS, MessagesHelper.NAME, MessagesHelper.ADDRESS, MessagesHelper.READ, MessagesHelper.GROUP_NAME, MessagesHelper.BODY, MessagesHelper.DATE, MessagesHelper.THREAD_ID, MessagesHelper.CONVERSATION_TYPE, MessagesHelper.MESSAGE_ID, MessagesHelper.WHO)
Is there any way to insert into the database but have the distinct query still sort by each thread_id by date as well?
Thanks!
I believe the issue is that DISTINCT considers the entire row being extracted, thus it is likely, based upon the column names, that both the COUNT column and the DATE column would or could likely be different when inserting a new message (e.g. perhaps count would initially be 0?) and thus cause them to be inserted as they make a new row DISTINCT (not a duplicate).
e.g. Consider this table :-
Then if DISTINCT is used just on the idbet column, the result is 2 rows 1 for where idbet is 3000 and another for where idbet is 1981 as per :-
However, if columns idbet and gamble are extracted then all 4 columns are extracted as there are now no duplicates, as per :-
If the row with _id 350 had WIN in the gamble column then 3 rows would be extracted as rows with _id's 349 and 350 would be a duplicate according to the idbet and gamble columns, as per :-
Perhaps rather than DISTINCT, or just DISTINCT you should use a WHERE condition or conditions (4th and 5th parameters of query). Perhaps "count < 1" as the 4th parameter (5th null) this does assume that count will initially be 0. An alternative would be to reduce the columns extracted but that may not be practical.
Related
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 want to delete all my values in my sqlite Table.
When I click my button it deletes everything besides the id it keeps counting
so for example:
ID, FIRSTNAME, LASTNAME
1, Jack Sparrow
2, Johhny Dep
if I press delete and add new values, it shows this
ID, FIRSTNAME, LASTNAME
2, Obama Barack
3, Mike Tyson
this is my method
private void DeleteEverything()
{
SQLiteDatabase db = mDbHelper.getWritableDatabase();
db.execSQL("delete from "+NamesContract.NameEntry.TABLE_NAME);
}
You should consider to drop and recreate the table.
You can find an example here
If you specify ?? INTEGER PRIMARY KEY or ?? INTEGER PRIMARY KEY AUTOINCREMENT then SQlite treats this in a specific way. That is ?? (by the looks of it ID in your case) is an alias for SQLite's rowid.
rowid if not specified when inserting a row will automatically be generated and is in general guaranteed to be unique and is normally incremented by one. However, it is not guaranteed to be incremented nor incremented by one.
If you specify AUTOINCREMENT (i.e. the latter of the two above) then the guarantee, is that a new rowid will be greater but not necessarily by 1.
There is a limit of 9223372036854775807 on the value of rowid. If AUTOINCREMENT is not used and this limit has been reached then an attempt will made to utilise free numbers (e.g. the rowid of rows that have been deleted). If AUTOINCREMENT is specified and the limit has been reached then an insert will fail with an SQLITE_FULL error.
As such, in your case the freed ID's from deleting rows will not be reused.
In short you should never rely upon the rowid (or an alias of it) column being a specific value, rather you should rely upon it just being a unique value purely for the purpose of uniquely identifying a row (and perhaps the fastest way of accessing a row).
You can, albeit it inadvisable, set rowid either by say INSERT INTO mytable (rowid, myothercolumn) VALUES(1, 'other data') or if ID has been used as an alias then INSERT INTO mytable (ID, myothercolumn)VALUES(1, 'other data').
If you really need the first row to be 1 and then 2 and so on then you could DROP the table and then recreate it rather then deleting all rows. e.g. DROP TABLE mytable, followed by CREATE TABLE mytable (ID INTEGER PRIMARY KEY, FIRSTNAME TEXT, LASTNAME TEXT). However, I'd suggest you will just end up with issues which will subsequently be far more complicated to determine and correct.
If you think about it, really what does Johnny Depp being 2 signify rather than Barack Obama being 2? If it is, for example, a popularity rating the surely you'd have some means of determining the popularity and input that directly rather than input it somewhere else to then be used to determine the insertion order and then do the inserts.
Much of the above is basically a summary of SQLite Autoincrement
As an example of unpredictability, a table was created with:-
CREATE TABLE mytable (ID INTEGER PRIMARY KEY, FIRSTNAME TEXT, LASTNAME TEXT)
A row was inserted using INSERT INTO mytable (ID, FIRSTNAME, LASTNAME) VALUES(9223372036854776000,'FRED','BLOGGS'). Note the use of 9223372036854776000 forces above the limit processing.
This was then followed by a number of INSERT INTO mytable(FIRSTNAME, LASTNAME) VALUES('????','????') inserts. Note! without ID, so using SQLITE's unique identifer determination (above the limit processinmg). The resultant table :-
MARY QUITECONTRARY was the 2nd row inserted,TOM SMITH the 3rd. The two RICHARD BURTON rows are an example of where the unique id could be essential for determining a specific row, both were also inserted without specifying the insertion order.
Note! if the above were tried but with AUTOINCREMENT specified, then the second insert, and any subsequent inserts, would fail with an SQLITE_FULL error.
I have a ContentProvider that uses a custom CursorFacory in debug to print out the SQL queries (for debugging).
A certain query was returning 0 rows, while I knew there were rows that should have been included. So I copied the query from my logs, replaced the bind values and ran it in sqlite3 shell on the device and got the correct result.
The Query Code
cr.query (contentUri,
Projection.columns,
FeedColumns.FEED_TYPE + "=? AND " +
FeedColumns.SUB_TYPE + "=? AND " +
ProfileUpdateFeedItem.UPDATED_FIELD + "=? AND " +
FeedColumns.IS_NOTIFIED + "=?",
new String[] {FeedType.USER, // 2
WallPostData.WallPostType.PROFILE_UPDATE, // 1
ProfileUpdateData.ProfileField.STATUS, // 0
SQLBoolean.FALSE // 0
},
FeedColumns.CREATED + " ASC");
From the logs:
07-04 12:48:51.339 4067-4314/com.redacted.android D/DATABASE﹕ QUERY: SQLiteQuery: SELECT DISTINCT id, sender, data_1, data_2, photo, feed_type, sub_type, created, expiry, updated, comment_count, comment_unread, reaction_count, reaction_unread, sender_name, sender_photo, _id FROM wall WHERE feed_type=? AND sub_type=? AND data_1=? AND is_notified=? ORDER BY created ASC LIMIT 100
On device:
Enter SQL statements terminated with a ";"
sqlite> SELECT DISTINCT id, sender, data_1, data_2, photo, feed_type, sub_type, created, expiry, updated, comment_count, comment_unread, reaction_count, reaction_unread, sender_name, sender_photo, _id FROM wall WHERE feed_type=2 AND sub_type=1 AND data_1=0 AND is_notified=0 ORDER BY created ASC LIMIT 100;
53b702b827d7482062f52b03|a7e759d78abe4bfa97045ce49a24ab57|0|Educ||2|1|1404502712279|1404761912325|1404502712279|||||Luke Skywalker|pr/e5c2c0398b267f93683c80dc5009722e|49
The ContentProvider, however, doesn't agree and cursor.getCount() returns 0.
Any ideas why this is happening?
feed_type, sub_type, and is_notified are INTEGER columns.
data_1 is a BLOB that is storing an integer for any row that would qualify for this query, but stores strings for other types of data that could go in this table.
When you run in the shell i'm surprised you get any rows. The blob data type may not convert the keyed value properly for you. Typically the database API requires a special function to set the blob value as well as retrieve it.
So the problem here was the BLOB column. It was being evaluated properly in queries (The data in the table is used in a ListView and is displayed differently depending on the contents of the data_1 and data_2 columns).
Everything in the feed category gets parsed into a member of a class hierarchy rooted at an AnstractFeedObject.
Most fields that use both data_1 and data_2 store text in both, but some fields (those who correspond to a subset of the mentioned class hierarchy) use data_1 as a type enumeration that the UI uses to interpret the value stored in data_2. For example, a 0 type means that data_2 is a picture id (construct the url and download), while type 1 means it's actual text content.
What I ended up doing was that I replaced data_1 with an integer column called type_enumeration and renamed data_2 to data_1. Now that I know BLOB can cause those kinds of issues, I'll be changin data_2 also to a TEXT column.
If at some point in the future I need to store binary data in the DB, I'll add a bin_data to the column.
Now usually in a proper normalized schema you'd use linked tables to represent such hierarchy, but in a mobile environment, you want to minimize joins so a few extra columns are cheaper in terms of performance (at least that's been my experience).
I am having a database table (Table1) which has 3 columns (ID,NAME,SIZE). The column, ID will have values like 1,2,3,... and coressponding values of NAME will be Abc.pdf,aaa.txt,abcc.doc,... ,.
My question is , I need to write a query which will group table according to the coloumn NAME i.e, For example, below is my sample table
Abc.pdf 20
aaa.txt 1
abcc.doc 10
ac.pdf 25
My query should give me
Abc.pdf 20
ac.pdf 25
aaa.txt 1
abcc.doc 10
Edit:
I would suggest storing the file types as a separate column, since that seems to be what you want the rows to be grouped by. Afterwards, you can simply concatenate the file type back onto the end of the file name.
Cursor cursor = database.query("Table1", null, null, null, null, null, FILE_TYPE + " DESC");
The method documentation can be found here
My app reads an XML file on the internet, takes note of the time and creates/writes an SQLite database. The next time data is required, if the time is >24hrs the database is updated (xml downloaded again).
The problem is that whenever I relaunch the app in AVD it has to re-download and so I notice that all the data in the database is written again (duplicated). So instead of 10 items, I have 20 (10+10 duplicates). If I relaunch again I get another 10 items duplicated.
I thought about how I could prevent the duplication of the database (or delete the old entries), so I decided to increment the database version every time the content is downloaded. I thought this would trigger the onUpgrade() method so the data would be cleared but nothing changes.
Now I am clueless. How should I go about this?
On your database create you'll want to use the UNIQUE constraint. You may not want the ON CONFLICT REPLACE that i use, but you should get the idea.
For Ex:
private static final String DATABASE_CREATE_NEWS= "create table news (_id integer primary key autoincrement, "title text not null, description text not null, date text not null, LastModified text not null, UNIQUE(title, date) ON CONFLICT REPLACE);";
Here is another solid thread that talks about it as well.
SQLite table constraint - unique on multiple columns
Here is some more info on the android sqlite: http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html
You should create an index on the columns that represent a unique identifier.
see this article on SQLite's website.
CREATE INDEX ix_tblexample ON TableName ( Column1, Column2, Column3 [, Column4, etc..])
Or (as per your comment) you can select the table into a cursor and check for each one.
String sql = "select * from " + tableName + "where column1 = " + param1 + "and column2 = " + param2;
Cursor cur = _db.rawQuery( sql, new String[0] );
if(cur.getCount() == 0)
{
//upload
}