Ordering by a max or a min from another table - java

I have a table that consists of a unique id, and a few other attributes. It holds "schedules". Then I have another table that holds a list of all the times each schedule has or will "fire". This isn't the exact schema, but it's close:
create table schedule (
id varchar(40) primary key,
attr1 int,
attr2 varchar(20)
);
create table schedule_times (
id varchar(40) foreign key schedule(id),
fire_date date
);
I want to query the schedule table, getting the attributes and the next and previous fire_dates, in Java, sometimes ordering on one of the attributes, but sometimes ordering on either previous fire_date or the next fire_date. Ordering by the attributes is easy, I just stick an "order by" into the string while I'm building my prepared statement. I'm not even sure how to go about selecting the last fire_date and the next one in a single query - I know that I can find the next fire_date for a given id by doing a
SELECT min(fire_date)
FROM schedule_times
WHERE id = ? AND
fire_date > sysdate;
and the similar thing for previous fire_date using max() and fire_date < sysdate. I'm just drawing a blank on how to incorporate that into a single select from the schedule so I can get both next and previous fire_date in one shot, and also how to order by either of those attributes.

You can do that using two sub-queries in Left Joins.
This has the advantage of returning NULL for your fire_dates if there is no next/previous schedule.
Select id, attr1, attr2, next_fire_date, previous_fire_date
From schedule s
Left Join ( Select id, Min(fire_date) As next_fire_date
From schedule_times st
Where st.fire_date > Sysdate
Group By id ) n
On ( n.id = s.id )
Left Join ( Select id, Max(fire_date) As previous_fire_date
From schedule_times st
Where st.fire_date < Sysdate
Group By id ) p
On ( p.id = s.id )
You can then add your ORDER BY on next_fire_date or previous_fire_date.
If performance matters, create a compound index on schedule_times( id, fire_date ), this will allow the sub-queries to read only from this index.

Try something like this:
select schedule.*,
(
select max(si.fire_date) from schedule_times si where si.id = schedule.id and si.fire_date < sysdate
) as prevfire,
(
select min(si.fire_date) from schedule_times si where si.id = schedule.id and si.fire_date > sysdate
) as nextfire
from schedule
where id = ?
order by attr1

Modify the query to
SELECT Distinct S.ID, ATTR1, ATTR2,
LEAD(FIRE_DATE, 1, SYSDATE) OVER (PARTITION BY S.ID ORDER BY S.ID) NEXT_FIRE_DATE,
LAG(FIRE_DATE, 1, SYADATE) OVER (PARTITION BY S.ID ORDER BY S.ID) PREV_FIRE_DATE
FROM SCHEDULE S,SCHEDULE_TIMES ST WHERE ST.ID=S.ID;
THIS WAS SIMPLE QUERY. YOU CAN TRY THIS.
LEAD has the ability to compute an expression on the next rows (rows which are going to come after the current row) and return the value to the current row. The general syntax of LEAD is shown below:
LEAD (sql_expr, offset, default) OVER (analytic_clause)
sql_expr is the expression to compute from the leading row.
offset is the index of the leading row relative to the current
row. offset is a positive integer
with default 1.
default is the value to return if the offset points to a row
outside the partition range.
The syntax of LAG is similar except that the offset for LAG goes into the previous rows.

Related

How can I change the order of my recyclerView items by changing values in the database?

In my app, the user should be able to change the order of the item. I am having the data from the table by SELECT statement order by Order field, and storing the result in the List. Then I am showing the List data in the Recycler View.
I would like to move "Item 5 " from recycler view position 4, and Order field value 12 to recycler view position 1.
My first question :
How do I move the Item 5 to recycler position 1 using Java/Kotlin?
My second question:
How do I move the Item 5 to recycler position 1 and change the Order field in DB accordingly? Means when I move the Item 5 to recycler position 1 the Item 5 Order field should change to 5,
Item 2 Order field should change to 7,
Item 3 Order field should change to 9 ,
Item 4 Order field should change to 12
The Recycler View items:
Answer one, rebuild/refresh the recyclerview with new data after changing the database.
Answer two
Roughly speaking you have to
ascertain the range of the affected rows (those whose order is between the from an to)
shift the values of the affected rows that are NOT the row being moved using the value from the next row in the direction of the move
change the value in the moved row
This is quite complex especially if all of the stages are to be combined. However, this is achievable with SQLite using Common Table Expressions (temporary tables that exist just for the duration of the query).
The following is a demonstration that solves the issue posed i.e. changing from:-
to
This is the demo (commented and formatted) that demonstrates the core query that would be implemented in Room (the comments indicate the core query):-
/* Just in case the environment has not been cleanup, clean it up*/
DROP TABLE If EXISTS example;
/* Create the table to be demonstrated */
CREATE TABLE IF NOT EXISTS example (
orderinrecyclerview INTEGER,
orderfieldvaluesfromtable INTEGER,
textfieldvaluesfromtable TEXT
);
/* Add the data for the demo */
INSERT INTO example VALUES
(0,2,'Item 1'),
(1,5,'Item 2'),
(2,7,'Item 3'),
(3,9,'Item 4'),
(4,12,'Item 5'),
(5,16,'Item 6')
;
/* Show the original data as RESULT 1 */
SELECT * FROM example ORDER BY orderfieldvaluesfromtable;
/*<<<<<<<<<< THIS IS THE CORE QUERY BEING DEMONSTRATED >>>>>>>>>>*/
WITH
/* CTE (Common Table Expression which is temp table that exists only for the duration of the query)
store the from and to values for the move to be made,
so values can be obtained whenever needed
*/
cte_args(fromvalue,tovalue) AS (
SELECT
12 /* will not be hard coded but passed/bound via arg*/,
5 /* will not be hard coded but passed/bound via arg */
) /* from to values*/,
/* Second CTE to get all the rows that will be affected, along with the new replacement value */
/* This is done in two stages
stage one gets all affected rows EXCEPT the actual row being moved.
stage two gets the actual row being changed
*/
cte_rowstochange(rowid,originalorder,neworder) AS (
SELECT
rowid, /* the rowid (always exists for tables unless WITHOUT ROWID table (ROOM does not support WITHOUT ROWID tables)) */
orderfieldvaluesfromtable,
(
SELECT orderfieldvaluesfromtable
FROM example
WHERE orderfieldvaluesfromtable > e.orderfieldvaluesfromtable /* e is the primary query example table as opposed to subquery example table */
ORDER BY orderfieldvaluesfromtable ASC /* want the next higher */
LIMIT 1 /* only want the 1 value */
) /* The value of the order in the next row (next higher order) */
FROM example AS e /* need to alias the table for WHERE clause above which accesses the same table */
WHERE orderfieldvaluesfromtable >= (SELECT tovalue FROM cte_args)
AND orderfieldvaluesfromtable < (SELECT fromvalue FROM cte_args)
UNION ALL SELECT
rowid,
orderfieldvaluesfromtable,
(
SELECT tovalue FROM cte_args
)
FROM example
WHERE orderfieldvaluesfromtable = (SELECT fromvalue FROM cte_args)
ORDER BY orderfieldvaluesfromtable ASC
)
UPDATE example
SET orderfieldvaluesfromtable = (
SELECT neworder
FROM cte_rowstochange
WHERE example.rowid = cte_rowstochange.rowid
)
WHERE rowid IN (
SELECT rowid FROM cte_rowstochange
)
;
/*<<<<<<<<<< END OF THE CORE QUERY >>>>>>>>>>*/
SELECT * FROM example ORDER BY orderfieldvaluesfromtable;
/* Cleanup testing environment */
DROP TABLE If EXISTS example;
To use the above in Room, then you just need the core query (adjusted to table args for the from an to values) e.g. :-
Query("WITH cte_args(:fromvalue,:tovalue) AS (SELECT 12,5),cte_rowstochange(rowid,originalorder,neworder) AS (SELECT rowid,orderfieldvaluesfromtable,(SELECT orderfieldvaluesfromtable FROM example WHERE orderfieldvaluesfromtable > e.orderfieldvaluesfromtable ORDER BY orderfieldvaluesfromtable ASC LIMIT 1) FROM example AS e WHERE orderfieldvaluesfromtable >= (SELECT tovalue FROM cte_args) AND orderfieldvaluesfromtable < (SELECT fromvalue FROM cte_args) UNION ALL SELECT rowid,orderfieldvaluesfromtable,(SELECT tovalue FROM cte_args) FROM example WHERE orderfieldvaluesfromtable = (SELECT fromvalue FROM cte_args) ORDER BY orderfieldvaluesfromtable ASC) UPDATE example SET orderfieldvaluesfromtable = (SELECT neworder FROM cte_rowstochange WHERE example.rowid = cte_rowstochange.rowid) WHERE rowid IN (SELECT rowid FROM cte_rowstochange);")
fun moveOrderDown(fromvalue: Int,tovalue: Int)
NOTE obviously the component names (tables and columns therein) would have to be changed to suit the actual tables and columns i.e.
the tablename example would have to be changed to reflect whatever that table is
column orderfieldvaluesfromtable would have to be changed.
Other component names can remain as is i.e.
rowid, e, tovalue, fromvalue, neworder, cte_args and cte_rowstochange
all except rowid could be changed consistently if required. rowid MUST be rowid it is the name of a hidden column that exists in all tables that are not WITHOUT ROWID tables or virtual tables (none of which Room supports directly (full text search aka FTS being an exception)).
Important
The above just solves moving an order to a lower order. The logic for the move to a higher order would be similar but reversed. You would likely want a moveOrderUp function. You could perhaps have a third function that determines which move function to invoke e.g.
fun moveOrder(fromvalue: Int, tovalue: Int) {
if (fromvalue == tovalue) return
if (fromvalue > tovalue) {
moveOrderDown(fromvalue,tovalue)
} else {
moveOrderUp(fromvalue,tovalue)
}
}
Further more the solution is in-principle and has not been exhaustively tested and may therefore have undetected issues.

Reset sqlite squence autoincrement

I want to reset autoincrement in my table. I tried to do this, but when I created and inserted a new row an id is not 1, but was just autoincremented .
#Query("delete from sqlite_sequence where name='approval';")
void delete();
#Query("DELETE FROM approval")
void nukeTable();
How can I reset autoincrement to start from 1 again?
You can reset by update sequence after deleted rows in your-table
UPDATE SQLITE_SEQUENCE SET SEQ=0 WHERE NAME='table_name';
Reference
I don't know what Room may be bringing to the mix, but at the pure Sqlite3 level, both deleting the record from sqlite_sequence and setting its value to zero does the trick, provided all records are deleted from the table in question.
autoinc.sql3
create table ai ( id integer primary key autoincrement, value ) ;
select 'Normal insertion' ;
insert into ai(value) values ( 1 ), ( 42 ) ;
select * from ai ;
select 'Delete sequence only' ;
delete from sqlite_sequence where name = 'ai' ;
insert into ai(value) values ( 1 ), ( 42 ) ;
select * from ai ;
select 'Delete sequence and data' ;
delete from sqlite_sequence where name = 'ai' ;
delete from ai ;
insert into ai(value) values ( 1 ), ( 42 ) ;
select * from ai ;
Running this with sqlite3 < autoinc.sql3 gives the following output:
Normal insertion
1|1
2|42
Delete sequence only
1|1
2|42
3|1
4|42
Delete sequence and data
1|1
2|42
Exactly the same results are obtained if instead of deleting the record from the sequence table we reset it to zero using update sqlite_sequence set seq=0 where name = 'ai' ;.
However, as the results above show, if there are any records in the table, numbering is persisted (presumably with the highest-used-plus-one that a non autoincrement primary key uses).
Could it be something is inserting records between deleting/resetting the sequence and deleting the records from approval? Assuming Room supports it, try both operations within a single transaction.

How implement friends Relationship android

I'm trying to implement some code for get friends list.
First:
- I have my String id. E.G: 784717
- I have an string with 100 numbers. E.G: 7781,5913,551949194,4919491,...,444131 (One string separated by ,)
- I have 3000 records in my Database with different numbers. (With numbers I mean some kind of ID)
- Of my 100 numbers only 8 are registered in my database.
Question:
How can I know what numbers are registered in the database and insert in other table the relationship?
My table Relationship have this columns:
*number1 - (Here should be my ID)
*number2 - (1 of the 100 numbers that exists)
So in my table Relationship should be 8 new rows.
I tried with :
EXEC('SELECT * FROM Accounts WHERE ID IN(' +#in_mystring+')')
but i don't know how insert in the other table or if is efficiently
Assuming this is SQL Server, and with the help of a parser function
For example
Select * from [dbo].[udf-Str-Parse]('7781,5913,551949194,4919491,...,444131',',')
Returns
Key_PS Key_Value
1 7781
2 5913
3 551949194
4 4919491
5 ...
6 444131
From this sub-query, you can join the results to your Accounts Table
Perhaps something like this
Select A.*
From Accounts A
Join (Select * from [dbo].[udf-Str-Parse]('7781,5913,551949194,4919491,...,444131',',')) B
on A.Key_Value =A.ID
The UDF -- If 2016, There is a native parser.
CREATE FUNCTION [dbo].[udf-Str-Parse] (#String varchar(max),#delimeter varchar(10))
--Usage: Select * from [dbo].[udf-Str-Parse]('Dog,Cat,House,Car',',')
-- Select * from [dbo].[udf-Str-Parse]('John Cappelletti was here',' ')
-- Select * from [dbo].[udf-Str-Parse]('id26,id46|id658,id967','|')
Returns #ReturnTable Table (Key_PS int IDENTITY(1,1) NOT NULL , Key_Value varchar(max))
As
Begin
Declare #intPos int,#SubStr varchar(max)
Set #IntPos = CharIndex(#delimeter, #String)
Set #String = Replace(#String,#delimeter+#delimeter,#delimeter)
While #IntPos > 0
Begin
Set #SubStr = Substring(#String, 0, #IntPos)
Insert into #ReturnTable (Key_Value) values (#SubStr)
Set #String = Replace(#String, #SubStr + #delimeter, '')
Set #IntPos = CharIndex(#delimeter, #String)
End
Insert into #ReturnTable (Key_Value) values (#String)
Return
End

keep latest n entries in table

I have an sqlite database (on android) where I want to store the latest N entries of some data.
The primary key of the table is a date field. Basically whenever I insert some row after the threshold is reached, I want to delete the oldest entry from the table.
Is there any especially clever/nice way to do this? Note that I always check the invariant (nr rows <= THRESHOLD) after each insert so we don't have to deal with anything but with deleting the oldest entry.
What I'm planning to do is basically:
insert data
if count(*) of table <= THRESHOLD: goto 4
DELETE FROM table WHERE date == (SELECT date from table order by date ASC LIMIT 1);
DONE
Note I'm using ORMlite, but since there's no user data involved I can just use raw SQL, so there shouldn't be a problem.
DELETE FROM table WHERE date = (SELECT MAX(date) from table LIMIT 1);
You can use a trigger to delete the oldest row when a new row is added, and the number of rows is over your threshold.
The count of rows can be kept in a separate accounting table to avoid a COUNT on every insert.
Here's a complete example:
create table bookkeepings (bk_name text primary key, bk_value integer not null);
insert or replace into bookkeepings values ('Max Results', 50);
insert or replace into bookkeepings values ('Qty Results', 0);
create table results
(r_timestamp text primary key default (datetime(current_timestamp)),
result text);
create trigger results_limit_trigger before insert on results"
for each row"
when (select bk_value from bookkeepings where bk_name = 'Qty Results')
>= (select bk_value from bookkeepings where bk_name = 'Max Results')
begin
delete from results
where r_timestamp = (select r_timestamp from results order by r_timestamp limit 1);
end;
create trigger results_count_insert_trigger after insert on results
for each row
begin
update bookkeepings set bk_value = bk_value + 1 where bk_name = 'Qty Results';
end;
create trigger results_count_delete_trigger after delete on results
for each row
begin
update bookkeepings set bk_value = bk_value - 1 where bk_name = 'Qty Results';
end;
How about this?
-- keep the last N records by expiration date
declare #expDate datetime
set #expDate = (select top 100 max(dt) from table order by dt asc);
delete from table where dt > #expDate

Improve Performance of pl/sql,want to perform Q2 on result of Q1

CREATE or REPLACE PROCEDURE TEST(
activationStartDate IN DATE,
activationEndDate IN DATE,
deActivationStartDate IN DATE,
deActivationEndDate IN DATE
)
AS
FirstNameListTable LIST_TABLE;
{--COMMENT :LIST_TABLE is nested table :create or replace TYPE "LIST_TABLE" as table of varchar2(20);-----Nested Table Declaration
/
}
totalSameFirstName NUMBER;
j NUMBER := 1;
BEGIN
SELECT first_name BULK COLLECT INTO FirstNameListTable FROM Employee where start_date between activationStartDate AND activationEndDate
MINUS
SELECT first_name FROM Employee where start_date between deActivationStartDate AND deActivationEndDate
FOR i IN FirstNameListTable.FIRST .. FirstNameListTable.LAST LOOP
SELECT count(*) INTO totalSameFirstName FROM Employee where start_date between activationStartDate AND activationEndDate AND first_name=FirstNameListTable(i)
IF totalSameFirstName > 2 THEN
---business logic
END IF;
END LOOP;
Actually there are two queries as mensioned above
Letsay this query as Q1:
SELECT first_name BULK COLLECT INTO FirstNameListTable FROM Employee where start_date between activationStartDate AND activationEndDate
MINUS
SELECT first_name FROM Employee where start_date between deActivationStartDate AND deActivationEndDate
and this query as Q2 :
SELECT count(*) INTO totalSameFirstName FROM Employee where start_date between activationStartDate AND activationEndDate AND first_name=FirstNameListTable(i)
In both the qyery i am scanning complete table,which i think there is no need. I am iterating result of Q1 and then again scanning the table to count similar first_name. If a particular firstName occur more than two times i wrote business logic.
Can i combine both the queries,Means i want to store result of Q1 in some PL/SQL dataStructure and want to perform Q2 on result of Q1.
I want to modify Q1 as
SELECT * BULK COLLECT INTO FirstNameListTable FROM Employee where start_date between activationStartDate AND activationEndDate
MINUS
SELECT * FROM Employee where start_date between deActivationStartDate AND deActivationEndDate
But how to store 'select *' result in pl/sql dataStructure and How to pass these records to second query..Can u tell me how my code will look like?
try to be more clear, i'm in lack of ideas in this PL/SQL, even it sounds like a classic :I have spend hours trying to play around with this but have got nowhere
#Ollie
I changed the code as suggested by u but getting some error and not able to solve them
CREATE or REPLACE PROCEDURE TEST(
activationStartDate IN DATE,
activationEndDate IN DATE,
deActivationStartDate IN DATE,
deActivationEndDate IN DATE,
Out_Entity OUT TEST1.RefCsr
)
AS
FirstNameListTable CRITERIA_LIST_TABLE;
out NUMBER;
j NUMBER := 1;
CURSOR main_cur
IS
WITH include_rec
AS (SELECT first_name,COUNT(1) OVER (PARTITION BY first_name) name_count FROM employee where start_date between activationStartDate AND activationEndDate
MINUS
SELECT first_name FROM employee where start_date between deActivationStartDate AND deActivationEndDate)
SELECT first_name FROM include_rec WHERE name_count > 2;
BEGIN
OPEN main_cur;
FETCH main_cur BULK COLLECT INTO FirstNameListTable;
CLOSE main_cur;
OPEN Out_Entity FOR SELECT * FROM TABLE(
CAST (
FirstNameListTable AS LIST_TABLE
)
) Nos;
END;
/
Error 1: PL/SQL: SQL Statement ignored {Indicating : "WITH include_rec" Line}
Error 2 : PL/SQL: ORA-01789: query block has incorrect number of result columns {Indicating line : "AS (SELECT first_name,COUNT(1) OVER (PARTITION BY first_name)"}
thanks in advance
waiting for ur reply
I'd like to caveat this by saying that I haven't had a lot of time to work on this so there may be a few errors but it should give you the gist of what i'm trying to tell you:
CREATE or REPLACE
PROCEDURE TEST(
activationStartDate IN DATE,
activationEndDate IN DATE,
deActivationStartDate IN DATE,
deActivationEndDate IN DATE )
AS
CURSOR main_cur
IS
WITH include_rec
AS (SELECT first_name,
start_date,
COUNT(1) OVER (PARTITION BY first_name) name_count
FROM Employee
WHERE start_date BETWEEN activationStartDate
AND activationEndDate)
SELECT DISTINCT
first_name
FROM include_rec
WHERE start_date NOT BETWEEN deActivationStartDate
AND deActivationEndDate
AND name_count > 2;
--
FirstNameListTable dbms_sql.varchar2_table;
BEGIN
OPEN main_cur;
FETCH main_cur BULK COLLECT INTO FirstNameListTable;
CLOSE main_cur;
FOR i IN FirstNameListTable.FIRST .. FirstNameListTable.LAST
LOOP
---business logic
END LOOP;
etc...
I'd also say that if you are expecting a large resultset then put the BULK COLLECT into a loop too to reduce your memory requirements.
In an ideal world, you'd pass the variables activationStartDate, activationEndDate, deActivationStartDate and deActivationEndDate into the cursor as cursor parameters to keep the cursor modular but that's up to you. ;-)
Hope this helps...
Ollie.
EDIT:
In response to your question about using records to select * from the table, you can declare an associative array based on the cursor columns, for the example above if you wanted to select more than one column from EMPLOYEE then after the WITH clause you would select the named columns from EMPLOYEE you want and instead of:
FirstNameListTable dbms_sql.varchar2_table;
declare the associative array type and variable as:
TYPE main_cur_tabtype IS TABLE OF main_cur%ROWTYPE
INDEX BY PLS_INTEGER;
main_cur_tab main_cur_tabtype;
This gives you a flexible array that will automatically hold the columns selected in your cursor (main_cur).
You would collect the records into this array with the BULK COLLECT:
OPEN main_cur;
FETCH main_cur BULK COLLECT INTO main_cur_tab;
CLOSE main_cur;
and loop through them with:
FOR i IN main_cur_tab.FIRST .. main_cur_tab.LAST
LOOP
etc.
as for returning your resultset to Java, well I'm no java expert and you'll need to ask that in another forum or tag this question with the Java tag and hope a Java person picks it up and answers it for you.
Can you do this in SQL?
E.g. maybe using a WITH like this:
WITH q1 AS (SELECT ....)
SELECT /* q2 */ ... FROM q1
WHERE ..
GROUP BY etc
If you can do it in SQL you can always embed it in PL/SQL afterwards if you need to.

Categories