Recursive CTE query without top level result in SQLite - android

I'm currently working on a recursive query on an Android SQLite database. I have a table containing assets, which can form a hierarchy by referring to parents. For example:
AssetId | ParentAssetId 1--2--5
----------------------- | |
1 | NULL | |--6--8
2 | 1 | | |
3 | 1 | | |--9
4 | 1 | |
5 | 2 | |--7
6 | 2 |
7 | 2 |--3
8 | 6 |
9 | 6 |--4--10
10 | 4
I need to find all of the descendents of a given start point, but not including the start point. For example:
1 = 2,3,4,5,6,7,8,9,10
2 = 5,6,7,8,9
6 = 8,9
I managed to get this working using the example from the SQLite page:
SQLite WITH page
WITH RECURSIVE
Child(AssetId) AS (
VALUES (1)
UNION
SELECT Assets.AssetId FROM Assets, Child
WHERE Assets.ParentAssetID = Child.AssetId)
SELECT AssetId FROM Child WHERE AssetId != 1
This works, but I'm not happy regarding the final WHERE clause to filter out the original item. Is there some other way to start the cascade without including the original item?

You could start with the children of the original item:
WITH RECURSIVE
Child(AssetId) AS (
SELECT AssetId FROM Assets WHERE ParentAssetID = 1
UNION ALL
SELECT ...
)
SELECT AssetId FROM Child
This isn't really any simpler.

Related

Subqueries in sqlite. Count the number of selected items in a group

When forming a tree of objects, it is necessary to know whether all elements in the group are selected or not.
A group can have subgroups, unlimited nesting. You want to know if all items are selected or multiple items are selected or nothing is selected.
Is it possible to make one query in SQLite, and find out how many items are selected in a group (including subgroups) and the total number of objects in a group(including subgroups)?
I need to find out if all nested elements of a group are selected or not all or nothing is selected
As a result, you need to get such a state for the group:
sealed class GroupSelectState {
object None: GroupSelectState()//nothing selected
object Any: GroupSelectState()//at least one item is selected in a folder or in subfolders
object All: GroupSelectState()//all objects in the folder and subfolders are selected
}
Here is the structure of the tables:
Group table
| id | name | parentId |
| 1 | old | null |
| 2 | gaz | 1 |
| 3 | kamaz| 1 |
| 4 | 54901| 3 |
Object table
| id | name | groupId |
| 1 | Kamaz 777 | 3 |
| 2 | Kamaz 767 | 3 |
| 3 | Kamaz 677 | 4 |
| 4 | Kamaz 766 | 4 |
| 5 | Gaz 556 | 2 |
Table of selected objects (if the object is not in this table, then it is not selected)
| id | objectId | isChecked |
| 1 | 1 | 1 |
| 2 | 2 | 1 |
| 3 | 3 | 0 |
| 4 | 4 | 0 |
| 5 | 5 | 0 | // if the object has never been selected, then the table will not have a record for this object
For representing hierarchical data in sql database, the Nested Set Approach was invented. Read more on this on wikipedia: https://en.m.wikipedia.org/wiki/Nested_set_model
First get all child and sub child groups for the selected group
;WITH tempTable AS (
SELECT
folderId,
name,
parentId
FROM TreeItemEntity
WHERE folderId = 20058 (groupId)
UNION ALL
SELECT
t.folderId,
t.name,
t.parentId
FROM
TreeItemEntity t
INNER JOIN tempTable c
ON t.parentId = c.folderId
)
SELECT *
FROM tempTable
Result
Далее получаем объекты для этих групп
SELECT * objects where groupId in resultTableIds
returned all objects for select group and all child groups. Then we can check the selected objects or not

Alternatives to LEAD and LAG in SQLite

I need to find an alternative to LAG and LEAD for finding the previous and next entry in my table in SQLite since those are not support in the version used (updating is not an option).
But I also cant use the value I order by, since it can be a date and therefore can be identical on multiple entries.
Since the table has to be sorted by date, using the ID isn't an option either.
It'd be great if someone knew an alternative way of dealing with this issue, since after more than an hour of searching and trying I am out of ideas.
Edit:
The important columns to my use case are:
_id booking_date
1 2017:11-21
3 2017:11-21
4 2017:11-21
5 2017:11-21
2 2017:11-22
6 2017:11-22
7 2017:11-22
...
_id is the primary key.
The bookings need to be sorted by date.
It is possible for multiple bookings to have the same date.
Bookings with the same date are sorted by their ids (See id 2, 6 and 7 in the give n example)
I need a way to query the entry before and after an entry by its id.
For example for _id=6 the I need a query that selects the row with _id=2 and a query that selects the row with _id=7.
Alternatively a query single query that selects both will work just as good.
I do not need you to provide an entire query, but rather an approach to this issue.
Try something like that, this retrieves previous and next id of a given record using your sort order (by date+id) - assumming that id is the primary key, you can retrieve other columns od prev-next records using these ids:
SELECT *,
(SELECT id FROM t t1
WHERE t1.booking_date < t.booking_date
OR t1.booking_date = t.booking_date AND t1.id < t.id
ORDER BY booking_date DESC, ID DESC LIMIT 1 ) prev_id,
(SELECT id FROM t t1
WHERE t1.booking_date > t.booking_date
OR t1.booking_date = t.booking_date AND t1.id > t.id
ORDER BY booking_date , ID LIMIT 1 ) next_id
FROM t
order by booking_date, id
Demo: http://www.sqlfiddle.com/#!5/17631/2
| id | booking_date | prev_id | next_id |
|----|--------------|---------|---------|
| 1 | 2017-11-21 | (null) | 3 |
| 3 | 2017-11-21 | 1 | 4 |
| 4 | 2017-11-21 | 3 | 5 |
| 5 | 2017-11-21 | 4 | 2 |
| 2 | 2017-11-22 | 5 | 6 |
| 6 | 2017-11-22 | 2 | 7 |
| 7 | 2017-11-22 | 6 | (null) |
If the table looked like this, the final select would be fairly trivial.
_id booking_date seq
1 2017:11-21 1
3 2017:11-21 2
4 2017:11-21 3
5 2017:11-21 4
2 2017:11-22 1
6 2017:11-22 2
7 2017:11-22 3
seq being the number of rows in the same booking_date with smaller id. You could create a virtual view with this structure to drive the main select.
This is a possible approach. Since you were not soliciting "an entire query", I leave it up to you how to implement this idea.

Inner Join two tables and return greatest value from second table for each entry in first table

How would I construct a query in SQLite that selects every row from a TABLE1, and also selects one row from a TABLE2 where:
There is a match between an id value from TABLE1, and;
If there is more than one id match in TABLE2, only the highest value in another DATE column is returned.
Here is the table arrangement I have right now. The value in the r_identifier column corresponds to the t_id value in TABLE1:
TABLE1 ("Tanks")
+------+--------------+-------+
| t_id | t_name | t_vol |
+------+--------------+-------+
| 1 | A Tank | 23 |
| 2 | Another Tank | 48 |
+------+--------------+-------+
TABLE2("Readings")
+------+--------------+--------+---------+
| r_id | r_identifier | r_date | r_value |
+------+--------------+--------+---------+
| 0 | 1 | 5000 | 5 |
| 1 | 1 | 6000 | 7 |
| 2 | 2 | 7000 | 4 |
| 3 | 1 | 8000 | 3 |
+------+--------------+--------+---------+
And here is the table I would like to return from my query. Because there are multiple entries with the r_identifier of 1, only the one with the highest value in r_date is returned:
+------+--------------+-------+------+--------------+--------+---------+
| t_id | t_name | t_vol | r_id | r_identifier | r_date | r_value |
+------+--------------+-------+------+--------------+--------+---------+
| 1 | A Tank | 23 | 3 | 1 | 8000 | 5 |
| 2 | Another Tank | 48 | 2 | 2 | 7000 | 4 |
+------+--------------+-------+------+--------------+--------+---------+
The closest I've been able to manage so far is with the following statement, inspired by this answer:
SELECT t.*, r.* FROM t INNER JOIN r ON t._id=r_identifier ORDER BY r_date DESC LIMIT 1
This returns the correct values, but for only one "tank" - the first one in the table.
Edit
I forgot to mention in my original question that I wanted to retrieve every value in TABLE1 even if there were no entries in TABLE2 that had a matching value in the r_identifier column. Using Gordon Linoff's answer as a guide though I was able to come up with the following which is working well:
SELECT t.*, r.*
FROM t LEFT OUTER JOIN
r ON t._id=r_identifier
WHERE
(r_date IS NULL) OR
(r_date=(SELECT MAX(r2.r_date)
FROM r r2
WHERE r.r_identifier=r2.r_identifier));
One method is to use a WHERE clause with a correlated subquery to get the maximum date:
SELECT t.*, r.*
FROM t INNER JOIN
r
ON t._id = r.r_identifier
WHERE r.r_date = (SELECT MAX(r2.r_date)
FROM r r2
WHERE r.r_identifier = r2.r_identifier
);

Selecting from a table based on relationships which are saved in another table

My Situation
I am using SQLite on Android to store some data. This data is all in the same table, but each row can have one or more relationships to other rows in that same table. These relationships are saved in another table. Think of it like this:
In Table 1 may be a row with id 0 which has 2 children with the ids 1 and 2. Both of those children will again be saved in table 1, but in table 2 there will be a mapping for each of those children from the id 0 to their own id. The tables may look something like this:
+---------------------------+
| Table 1 |
+------+------+------+------+
| ID | .... Data .... |
+------+------+------+------+
| 0 | ... | ... | ... | <--- This would be the parent of rows 1 & 2
| 1 | ... | ... | ... | as indicated in the other table
| 2 | ... | ... | ... |
| 3 | ... | ... | ... |
+----------------------------+
| Table 2 |
+-------------+--------------+
| Parent ID | Child ID |
| 0 | 1 | <-- This means that row 0 has
| 0 | 2 | <-- 2 children with the ids 1 and 2
| 2 | 5 |
| 3 | 2 | <-- Each row can have multiple parents and/or children
What I want to do essentially is select from table 1 with some arbitrary where clause and if this where clause for example matches row 0, I also need to select the children of row 0 along with it and the children of those children and so on. Since I generally suck at explaining things let me illustrate this again:
If I were to run a select like this:
SELECT * FROM TABLE1 WHERE ...
I would get a result like this:
+------+------+------+------+
| ID | .... Data .... |
+------+------+------+------+
| 0 | ... | ... | ... |
| 3 | ... | ... | ... |
But what I would like to get is this:
+------+---------+------+------+------+
| ID | isChild | .... Data .... |
+------+---------+------+------+------+
| 0 | 0 | ... | ... | ... | <--- This row along with row 3 is what actually matches the where clause
| 1 | 1 | ... | ... | ... |
| 2 | 1 | ... | ... | ... |
| 5 | 2 | ... | ... | ... |
| 3 | 0 | ... | ... | ... | <--- This row along with row 0 is what actually matches the where clause
| 2 | 1 | ... | ... | ... |
| 5 | 2 | ... | ... | ... |
Only row 1 and 3 actually match the where clause. The order of the children is not important but they should follow right after the parent and the "isChild" column would be used to indicate whether the row is a child and to what it is a child.
Notice the third row from the top in the output above, the one with the id 2. It has 2 in "isChild" because it is a child of the row above which also is a child. You can think of the whole output above as a tree like this:
- 0
- 1 <-- 1 is a child of 0
- 2 <-- 2 is a child of 0
- 5 <-- 5 is a child of 2
- 3
- 2 <-- 2 is a child of 3
- 5 <-- 5 is a child of 2
The "isChild" column essentially tells you on which level of the tree you are.
The Problem
Up until now I had implemented this with multiple selects. I would first select the rows from table1, take the ids from each row and then select the mappings for each row from table2. With those mappings I would select the children from table1 and after that I would again look for mappings of the children in table2 and so on. It doesn't take a genius to see that this can cause huge performance problems very quickly and it indeed was pretty slow.
I have since then been trying to improve this by reducing the number of selects required but now I have hit a wall. I have implemented any sort of improvement I can think of and it works for the most part but if you are dealing with big datasets everything slows down exponentially and I don't see any other way I could improve this in code. I started thinking and came to the conclusion that if I could somehow select everything at once in the manner I described above it would solve a whole slew of problems for me.
My attempts to solve the problem so far
Since I cannot improve this further in code I have turned my attention to SQL. I have already made many unrelated improvements which resulted in great performance gains by implementing triggers to do the most common tasks like creating and deleting the mappings in table2. And I have been hoping I can also solve this problem in a similar manner.
I have tried all sorts of JOINs or UNIONs but nothing seems to work as I expect it. I have a feeling I might be going about this all the wrong way. I haven't event attempted to include a "isChild" column up until now.
This is a link to the SQLFiddle I use to test my selects
When I started working on this I foolishly thought that a simple JOIN would solve the problem but I am doubting that at this point and I am also not sure if what I want to do is even possible (in an efficient manner).
This problem has made me realise how little I know about SQL and if some SQL wizard could come along and tell me how simple the solution actually is I would very much appreciate it! (Although I suspect that the solution to my problem isn't actually that simple)
Please keep in mind that this question is talking specifically about SQLite on Android. But I tried to make this question as general as possible since it is also applicable for many other SQL implementations or operating systems.
If you have a really great answer to this question with a simple solution that blows my mind and a great explanation to go along with then I won't hesitate to reward you with a bounty.
To read the children recursively, you would have to use a recursive common table expression.
However, this was introduced in SQLite 3.8.3, so your Android device is very unlikely to support it.
You have to keep using multiple queries, or to use your own version of SQLite with the NDK.

Traversing directed cyclic graph in SQLite on Android

Say I have two SQL tables: NODES and EDGES
Nodes contains information pertaining to each node, and each row in EDGES contains a directed edge between two nodes.
i.e.
Table EDGES
|edge_id | node_from_id | node_to_id |
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 1 |
| 4 | 3 | 5 |
| 5 | 6 | 10 |
| 6 | 6 | 11 |
| 7 | 7 | 8 |
Querying the table for all the children of a node is trivial, but what if I want to have a list of all the children of all the children of a node? Or better yet a list of all the edges that create a tree of the node's descendants? Note that the graph may be cyclical and is not fully connected.
For example, I want to select all the descendant edges of node 1:
QUERY Results:
|edge_id | node_from_id | node_to_id |
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 2 | 1 |
| 4 | 3 | 5 |
In SQL and SQLite 3.8.3+ I could use something like a Common Table Expression (CTE) to do this and there are many answers on SO to that effect. But on Android I'm currently limited to SQLite 3.4.0 which doesn't support CTE's.
I could programmatically query each level of the tree, but I would prefer to do this all in SQL if possible. Is there a neat way to accomplish this given my limitations?
CTEs exists because there is no other way to do recursive queries.
You have to query the children step by step.

Categories

Resources