MYSQL select candidate rows randomly but only once - android

I have a table in my database which has 3 columns: id, name, category_id which has 1 million rows. the user selects a category_id in the app and then the database has to return 12 rows which have the selected category_id. then in the next api call, the database has to return the next 12 rows which have the selected category_id and so on.
My question is how I can return the rows randomly and avoid returning repeated rows in next api calls?

You are looking for a repeatable random sort. For this you can use RAND() with a seed. The idea is that each search should be assigned a constant seed (that you can persist in the user's session, for example).
Assuming that a user has seed 12345, you can fetch its first page like:
select * from mytable where category_id = ? order by rand(12345) limit 12
Then the second page is fetched as follows:
select * from mytable where category_id = ? order by rand(12345) limit 12, 12
Third page:
select * from mytable where category_id = ? order by rand(12345) limit 24, 12
And so on.

Assuming id is a key of the table and has positive values only, you can do:
select id, name, category_id
from my_table
where category_id = ? and id > ?
order by id
limit 12
The first time you call it, you can use:
category_id = 123
id = -1
The second time you get the max id from before (let's say 25) and use that one.
category_id = 123
id = 25

Related

How to calculate running total without using windows function

Android SQLite version is 3.19 and doesn't support windows function like over and row_number(). I don't have any auto incremented column. I have created view using data from different table. My view details are below.
1 means in stock
2 means out stock
3 means reset count and start from given quantity.
uniquekey is unique id for each row
I want to get running total as below:
uniquekey date ProductName uniqueKeyProduct InOutType quantity runningTotal
edfrgdctydkkc 2020-06-07 Apple dheykdhr 1 10 10
edfrgdctkduxc 2020-06-08 Orange xdefrttk 1 20 20
fdfrgdctydysc 2020-06-08 Apple dheykdhr 2 5 5
3dfrgrtkvctyf 2020-06-08 Apple dheykdhr 1 2 7
ctgrteerylkdc 2020-06-09 Orange xdefrttk 2 8 12
edffjritydmnc 2020-06-10 Orange xdefrttk 3 5 5
kkdjdjrgdctyk 2020-06-10 Apple dheykdhr 3 2 2
egdhgdctyjjdc 2020-06-11 Orange xdefrttk 1 20 25
edfryrytymnbc 2020-06-15 Apple dheykdhr 1 10 12
fgeegdctydk3c 2020-06-18 Apple dheykdhr 2 2 10
hyidfhhhfd89c 2020-06-20 Orange xdefrttk 2 8 17
I don't have any auto incremented column
Assuming that you haven't defined the table using WITHOUT ROWID then you still have an equivalent column, namely the rowid column.
AUTOINCREMENT is an alias of the rowid column, it is inefficient and rarely required
You may wish to read https://www.sqlite.org/rowidtable.html
I want to get running total as below:
As the date column does not determine the insert order (e.g. 2020-06-08 2 Apple rows were processed) the rowid column has been utilised (not 100% failsafe in this regard).
You may wish to consider making the date a column that holds the date and time of the insert.
One way to accomplish the above would be to utilise a TRIGGER. Triggers are driven by an INSERT UPDATE or DELETE event. So it makes sense to use a trigger whenever a row is inserted to then set the runningtotal.
The following TRIGGER maintains the running total for the data that you have provided.
CREATE TRIGGER IF NOT EXISTS mytable_after_insert
AFTER INSERT ON mytable
BEGIN
UPDATE mytable SET runningtotal =
CASE
/* in stock */
WHEN new.inouttype = 1 THEN
COALESCE(
(
SELECT mt1.runningtotal
FROM mytable AS mt1
WHERE mt1.rowid < new.rowid AND mt1.uniquekeyproduct = new.uniquekeyproduct
ORDER BY rowid DESC
LIMIT 1
),0
)
+ new.quantity
/* out stock */
WHEN new.inouttype = 2 THEN
COALESCE(
(
SELECT mt2.runningtotal
FROM mytable AS mt2
WHERE mt2.rowid < new.rowid AND mt2.uniquekeyproduct = new.uniquekeyproduct
ORDER BY rowid DESC
LIMIT 1
),0
)
- new.quantity
/* reset */
WHEN new.inouttype = 3 THEN new.quantity
END
WHERE rowid = new.rowid
;
END
;
note that the table name mytable will likely have to be changed. Perhaps the trigger's name should be changed as well.
The following is the SQL used to test the above. It
Drops the Trigger
Drops the Table
Creates the table (this may need to be amended appropriately)
Creates the Trigger
Inserts all the rows (note that the runningtotal column defaults to 0)
Queries the resultant table showing the runningtotal and also the rowid
:-
DROP TRIGGER IF EXISTS mytable_after_insert;
DROP TABLE IF EXISTS mytable;
CREATE TABLE IF NOT EXISTS mytable
(
uniquekey TEXT PRIMARY KEY,
date TEXT,
productname TEXT,
uniquekeyproduct TEXT,
inouttype INTEGER,
quantity INTEGER,
runningtotal INTEGER DEFAULT 0
)
;
CREATE TRIGGER IF NOT EXISTS mytable_after_insert
AFTER INSERT ON mytable
BEGIN
UPDATE mytable SET runningtotal =
CASE
/* in stock */
WHEN new.inouttype = 1 THEN
COALESCE(
(
SELECT mt1.runningtotal
FROM mytable AS mt1
WHERE mt1.rowid < new.rowid AND mt1.uniquekeyproduct = new.uniquekeyproduct
ORDER BY rowid DESC
LIMIT 1
),0
)
+ new.quantity
/* out stock */
WHEN new.inouttype = 2 THEN
COALESCE(
(
SELECT mt2.runningtotal
FROM mytable AS mt2
WHERE mt2.rowid < new.rowid AND mt2.uniquekeyproduct = new.uniquekeyproduct
ORDER BY rowid DESC
LIMIT 1
),0
)
- new.quantity
/* reset */
WHEN new.inouttype = 3 THEN new.quantity
END
WHERE rowid = new.rowid
;
END
;
INSERT INTO mytable (
uniquekey,date,productname,uniquekeyproduct,inouttype,quantity /* running total not supplied so defaults to 0 */
)
VALUES
('edfrgdctydkkc','2020-06-07','Apple','dheykdhr',1,10),
('edfrgdctkduxc','2020-06-08','Orange','xdefrttk',1,20),
('fdfrgdctydysc','2020-06-08','Apple','dheykdhr',2,5),
('3dfrgrtkvctyf','2020-06-08','Apple','dheykdhr',1,2),
('ctgrteerylkdc','2020-06-09','Orange','xdefrttk',2,8),
('edffjritydmnc','2020-06-10','Orange','xdefrttk',3,5),
('kkdjdjrgdctyk','2020-06-10','Apple','dheykdhr',3,2),
('egdhgdctyjjdc','2020-06-11','Orange','xdefrttk',1,20),
('edfryrytymnbc','2020-06-15','Apple','dheykdhr',1,10),
('fgeegdctydk3c','2020-06-18','Apple','dheykdhr',2,2),
('hyidfhhhfd89c','2020-06-20','Orange','xdefrttk',2,8)
;
SELECT rowid,* FROM mytable ORDER BY rowid ASC
The result being :-
Note that if normalisation rules were followed then there should be
another table for the products and thus that the product name would
not be duplicated, all that would be needed was the uniquekeyproduct.
e.g. if a product name were changed then all rows in the mytable table would have to be updated whereas if there were a product table a name changed would be just need the 1 row to be updated and it would be reflected when querying mytable (the ProductName column would no longer be required).
COALESCE is used to convert the null returned when the first row for a product is inserted as there are no rows that match the selection criteria for in stock and out stock actions. see https://www.sqlitetutorial.net/sqlite-functions/sqlite-coalesce/
CASE ..... END is for conditional processing of the inouttype column. It has 3 WHEN clauses one for each of the types (1,2 or 3). see https://www.sqlite.org/lang_expr.html#case
In the subqueries used within the first two WHEN clauses tables are given a temporary name mt1 and mt2 respectively, using AS to ensure that there is no ambiguity as to where the data is to come from.
Note that the as 3.19 is quite old the above was tested on more recent versions so there may be some issues. You may wish to have a look through https://www.sqlite.org/changes.html if you come across issues.

Getting the last three records from SQLite database

I have a database from which I would like to take the last 3 records. For example if I had the lines 1,2,3,4,5,6, ... 10,11,12,13,14, I would like 12,13,14 no matter the order (12,13,14 for me is equal to 14,13,12).
I tried to follow another question at this link Android SQLite Query - Getting latest 10 records
but what I get is just showing the first 3 rows of the database.
This is my query
String query2 ="select * from (select * from USERS order by ID ASC limit 3)";
In any case you can sort by rowid descending and get 3 rows:
select * from USERS order by rowid desc limit 3
If you want to sort by a specific column:
select * from USERS order by columnname desc limit 3

How to store only 10 last records to room?

I have some Entity to store my data into ROOM. How to store only 10 last row to my db using room. For now I'n using #Query("SELECT * FROM Entity LIMIT 10") but it's dont looks right
We you want only first 10 or less records in your database then you have to set the id as auto increment and try to delete all those records where the ids doesn't match the first 10 results (after every insertion)
DELETE FROM tableName where id NOT IN (SELECT id from tableName ORDER BY id DESC LIMIT 10)
Here is a link to explore more:-
Limit the amount of rows in a room database
Does it work?
This is the correct way to do it:
SELECT expressions
FROM tables
[WHERE conditions]
[ORDER BY expression [ ASC | DESC ]]
LIMIT number_rows [ OFFSET offset_value ];
And here's a real example:
SELECT contact_id, last_name, first_name
FROM contacts
WHERE website = 'TechOnTheNet.com'
ORDER BY contact_id ASC
LIMIT 5;
I got these from https://www.techonthenet.com/sql/select_limit.php.
There could by slight differences in syntax as I do not know which type of SQL you are using.

Why does SQLite min() function does not work in this query?

Lets say there is a table of price submissions that consist of rowId, shopID, goodID, price, submission time. I'm trying to retrieve the oldest price submission in a given shop. My query looks like this:
Cursor c = database.rawQuery("SELECT * FROM price_submissions WHERE validshop = '-Kcq2pOeJA2_URuJLFKC' AND submissiondateandtime = (SELECT min(submissiondateandtime) FROM price_submissions)", null);
This query returns 0 results. Though if I replace part (SELECT min(submissiondateandtime) FROM price_submissions) with actual min (oldest) value, it does return a row.
Why doesn't the initial query work?
SELECT min(submissiondateandtime) FROM price_submissions returns the oldest price submission not in the given shop, as you write in the textual description, but in the whole table. You might have meant SELECT * FROM price_submissions AS outer WHERE validshop = '-Kcq2pOeJA2_URuJLFKC' AND submissiondateandtime = (SELECT min(submissiondateandtime) FROM price_submissions AS inner WHERE inner.validshop = outer.validshop)
First run SELECT min(submissiondateandtime) FROM price_submissions and store min value in one varaiable X then use it in parent query SELECT * FROM price_submissions WHERE validshop = '-Kcq2pOeJA2_URuJLFKC' AND submissiondateandtime=X

How can I count the number of rows before a certain row in sqlite?

I have a database that stores the rank of an Item.
The rank is an absolute value that will be correct if all the items are taken into account.
If I only need a subset say four of this items it will give me something like:
Rank RowId in the whole Table
---------------
4 114
8 71
70 16
83 7
I now need an int specifying the rank only in the subset where the max rank is the number of items in the subset in my example 1,2,3,4.
Is there a way to achieve this in my sqlite query? I only need one of the ranks in my Activity. I thought of ordering the results of the query by rank and then somehow get the position of item I want to rank at that moment. But how would I achieve this with sqlite?
I tried to create a temporary table and insert the subset into it like this:
CREATE TABLE rank(ID);
INSERT INTO position SELECT ID from items WHERE ITEM_ID = 3 ORDER BY POSITION;
SELECT RowID from rank WHERE ID = 9;
DROP TABLE rank;
This is working in SQLite Manager and will return the correct number. But if I do this in Android in fails saying that there is no table rank while compiling query
07-07 13:35:46.150: ERROR/AndroidRuntime(2047): Caused by: android.database.sqlite.SQLiteException: no such table: rank: , while compiling: SELECT RowID from rank WHERE ID = 9
EDIT: have to agree with #Matt the only way I've been able to do this is to use the temp table approach.
For what it's worth here's what it looks like...
create temp table if not exists <temptable>(Id integer primary key, rank);
insert into temptable(rank) select <column> from <table>;
select * from temptable;
EDIT: Actually that returns the ID associated with the row which isn't sequential so you won't always get 1,2,3,4... I'll have to think of something else. Sorry.
Not sure if I've understood your question. You basically want this?
Id Value
---------------
1 4
2 8
3 70
4 83
So you want to add a pseudo-column as the id no matter what your subset contains?
If that's correct then this should do it...
SELECT RowId, <other columns>.... FROM <table> WHERE <where>
Apologies if I've misunderstood.
You could output your query (ordered by rank) into a temporary table with an auto increment ID.
If you need to read only one row from a subquery you can always execute a limit on it, by providing the offset of how many records to be skipped first, and how much to be returned
so if you want to get 25th row you tell to skip 24, and return 1
select * from (SELECT * FROM table order by rank) limit 24,1

Categories

Resources