android room database Dao two queries?

Viewed 70

How do I return the results of two queries using one @Query statement?

I have a database of items with a single table. Each item has a due date (saved as a long in the Room database) or no due date (saved as -1 in the database). I would like to have a query that returns all items with due dates in ascending order and then return all of the remaining items, sorted by a timestamp that is saved in the database. The timestamp represents the calendar date and time when the item was originally saved to the Room database.

Here is an example of the output I expect, using a U.S. calendar for the due dates:

  1. 8/17/2022 (August 17, 2022 due date)
  2. 8/19/2022 (due date)
  3. 12/15/2022 (due date)
  4. 5601 timestamp (no due date)
  5. 4200 timestamp (no due date)
  6. 1150 timestamp (no due date)

The below query in the Dao returns the expected results of the first part of the query, the ascending due dates. So how do I append the below query with the second part where I also return the items that have no due dates and show their timestamps in descending order? I tried multiple ways to use UNION, UNION ALL, etc. with no luck.

@Query("SELECT * FROM cards WHERE cardDuedatentime !=-1 ORDER BY cardDuedatentime ASC")

2 Answers

If I understand you questions correctly then I believe that you could use:-

@Query("WITH cte1 AS (SELECT * FROM cards WHERE cardDueDatentime != -1 ORDER BY cardDueDatentime ASC),cte2 AS (SELECT * FROM cards WHERE cardDueDatentime = -1 ORDER BY timestamp ASC) SELECT * FROM cte1 UNION ALL SELECT * FROM cte2;")

The following was used to test/demonstrate:-

DROP TABLE IF EXISTS cards;
CREATE TABLE IF NOT EXISTS cards (cardDueDatentime INTEGER,timestamp INTEGER, othercolumns TEXT);
INSERT INTO cards VALUES 
    (strftime('%s','2022-08-17'),strftime('%s','now'),'A')
        ,(-1,5601,'A')
    ,(strftime('%s','2022-08-11'),strftime('%s','now'),'A')
        ,(-1,4201,'A')
    ,(strftime('%s','2022-12-15'),strftime('%s','now'),'A')
    ,(-1,1150,'A')
;

WITH 
    cte1 AS (SELECT * FROM cards WHERE cardDueDatentime != -1 ORDER BY cardDueDatentime ASC),
    cte2 AS (SELECT * FROM cards WHERE cardDueDatentime = -1 ORDER BY timestamp ASC)
SELECT * FROM cte1 UNION ALL SELECT * FROM cte2;
DROP TABLE IF EXISTS cards; 

The result from the executing the above (using the Navicat for SQLite tool):-

enter image description here

Two CTE's (Common Table Expressions, aka temporary tables) were used, each to extract one of the sets of data importantly sorting them independently. They are then combined via the UNION and not sorted (as the sort affects the complete set of data).

  • Note how the data has purposefully been inserted so that they are not appropriately sorted.

Here's an SQLFiddle of the above

An even simpler way would be to use:-

@Query("SELECT * FROM cards ORDER BY cardDueDatentime=-1 ASC,cardDueDatentime ASC, timestamp ASC;")

Which using the data above results in the same. This works because cardDueDatentime=-1 will equate to either true (1) or false (0). Therefore -1 will equate to 1 and a valid datetime will equate to 0, so the valid datetimes precede the invalid (-1) datetimes. Then the subsequent sort fields sort each set accordingly.

If you wanted any invalid date (les than 0) then you could use something like:-

@Query("SELECT * FROM cards ORDER BY cardDueDatentime<0 ASC,max(CAST(cardDueDatentime AS INTEGER),0) ASC, timestamp ASC;")

So if you had additional rows inserted such as :-

....
,(-2,111,'B')
,(-3,11,'C')
,(-1,1,'X')

The the result would be:-

enter image description here

Whilst with the first simpler SELECT, with the additional data, the result (WRONG) would be :-

enter image description here

  • i.e. for the C row as -3 is not equal to -1 then it will be as if it were a valid date,

  • so < 0 treats it as an invalid date so it is include in the set of invalid dates;

  • However, with < 0 allowing the -3 to in the invalid date set, the second sort, on the cardDueDatentime would place -3 before the -2 and before the -1 so the max function will for values less than -1 make them -1 and hence -3 becomes -1 (as with all the other invalid dates) so the third sort field is then the applicable sort field within the set of invalid dates.

    • this could be useful if you for some reason wanted to have different sets/types of invalid dates but not affect the query.

First sort by the boolean expression cardDuedatentime = -1 to get all the rows with no due date at the bottom of the resultset.
Then use conditional sorting with a CASE expression to sort the rows with no due date descending and the rows with a valid due date ascending:

SELECT *
FROM cards
ORDER BY cardDuedatentime = -1,
         CASE WHEN cardDuedatentime = -1 THEN -timestamp ELSE cardDuedatentime END;

If you want only 1 column in the results:

SELECT CASE WHEN cardDuedatentime = -1 THEN timestamp ELSE cardDuedatentime END time
FROM cards
ORDER BY cardDuedatentime = -1,
         CASE WHEN cardDuedatentime = -1 THEN -timestamp ELSE cardDuedatentime END;

See the demo.

Related