Access ancestors - sql

I have an Access db table, that holds couples of values: (SourceId, DestinationId). Both values are taken from the same list of Id's.
I want to create a list (query result?) of all item's ancestors. Ie, if the user enters Id=15, I'd like to return all Id's that are destinations for source-15, but also their destinations etc.
For example, if my table hold
15, 3 |
15, 4 |
4, 7 |
4, 8 |
3, 5 |
5, 2 |
1, 9
Id like to return 3, 4, 7, 8, 5, 2 (but not return 9).
I guess the solution should include some VBA code with loops or recursion, but I got confused by recordsets versus collections.
Any idea?
Thanks,
Aviram

Unfortunately Access SQL lacks the CONNECT BY syntax that Oracle uses to do hierarchical queries. However, if you are prepared to create a temporary table you can emulate it in Access with a loop.
In this example your original table is "LinkTab" and the temporary table will be "TmpTree" an you are starting from SourceID 15.
First execute:
SELECT SourceID, DestID, 1 as Lvl INTO TmpTree FROM LinkTab WHERE SourceID = 15
Then in a loop, repeatedly execute:
INSERT INTO TmpTree ( SourceID, DestID, Lvl )
SELECT newrows.SourceID, newrows.DestID, TmpTree.Lvl + 1
FROM TmpTree INNER JOIN LinkTab newrows ON TmpTree.DestID = newrows.SourceID
WHERE TmpTree.Lvl = (SELECT MAX(Lvl) FROM TmpTree)
until the statement returns zero affected rows (or if you can't detect this, count the rows in TmpTree each time and stop when they don't increase)
Now your results can be retrieved with :
SELECT DestID FROM TmpTree
And finally, to tidy up:
DROP TABLE TmpTree
I've tested these statements in the Access Query designer and they get the desired result.

Related

Return a list of IDs in which geometry connect to each other with SQL Amazon Redshift

I have a list of geometry IDs which contains the Linestrings. I want to create a query that returns the object IDs as a LISTAGG from those that connect to each other.
Here an example of a geometry I have with their IDs:
I want to input all IDs into SQL and return all geometry that are connected. So I'm expecting something like this:
| IDs |
|:----------------------------------------------------------|
|0001, 0002, 0003, 0004, 0005, 0006, 0007 |
|0008, 0009, 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017, 0018, 0019|
I have a table with a set of IDs and their respective Linestrings as this:
| ID | Vector |
|:---|:----------------------------------|
|0001|Linestring(1 2, 2 3, 3 4, 4 5, 5 6)|
|0002|Linestring(6 7, 8 9) |
|0003|Linestring(9 10, 11 12, 13 14) |
|0004|Linestring(14 15, 16 17) |
|0005|Linestring(17 18, 18 19, 19 20) |
And so on.
I'm pretty new with Amazon Redshift and I'm just struggling to find a way to do this.
I tried using ST_Buffer but then I'm stuck. Since the geometry connects to each other, maybe something that can bring all connections. I already got the A-B point of each geometry, but now I need to find a way to get the whole set of links.
This is what I currently have:
CREATE TEMP TABLE geoList
(ID BIGINT,
AB_Coords VARCHAR);
INSERT INTO geoList
SELECT ID,
CONCAT(SPLIT_PART(vector,',',1),')') AS startEndPoint
FROM geometry
WHERE ID IN (0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017, 0018, 0019);
INSERT INTO geoList
SELECT ID,
CONCAT('LINESTRING (',TRIM(SPLIT_PART(vector,',',LEN(vector)-LEN(REPLACE(vector,',',''))+1))) AS startEndPoint
FROM geometry
WHERE ID IN (0001, 0002, 0003, 0004, 0005, 0006, 0007, 0008, 0009, 0010, 0011, 0012, 0013, 0014, 0015, 0016, 0017, 0018, 0019);
SELECT *, g1.ID = g2.ID AS sameGeo FROM geoList g1
LEFT JOIN geoList g2
ON g1.AB_Coords = g2.AB_Coords
I'm stuck here...
Thanks!
The algorithm you need is clustering, specifically DBSCAN. With systems that inherently support it, you just call DBSCAN, and then aggregate by cluster ID it returns to get the list of groups.
E.g. with BigQuery, similar query can be done in PostgreSQL:
with data as (
select 1 id, 'Linestring(1 2, 2 3, 3 4, 4 5, 5 6)' line union all
select 2, 'Linestring(1 3, 4 1)' union all
select 6, 'Linestring(25 10, 30 15)' union all
select 7, 'Linestring(25 15, 30 10)'
)
select array_agg(id)
from (
select id, st_clusterdbscan(st_geogfromtext(line), 0, 1) over() as cluster_id
from data
) group by cluster_id
Result:
[1,2]
[6,7]
It looks like Redshift does not support DBSCAN though. The workaround would be to use external code - there are implementations for most languages. Another option is to do it natively in SQL using Recursive CTE, e.g. see discussion about implementing DBSCAN this way in this paper: https://db.in.tum.de/~schuele/data/ssdbm2022.pdf

How can I read the first n rows of table a based upon the number of rows in table b?

I have a table "L20" that contains 1 to 20 values "HDIF" in it, sorted in ascending order. I need to extract the first 1 to 10 of those values into table "T10" depending upon the number of values in table "L20". I'm using Windows 10, Libreoffice 6.4.4, with Firebird 3 database. I've tried the CASE statement and the DECODE statement on the COUNT of rows in "L20", but neither seems to work.
If I put in a numeral for the SELECT on table "L20" then it works correctly. Anyone have an idea of how to solve? The purpose of this query is to calculate a golf handicap which uses [up to] the best (lowest) 10 scores of [up to] the last (most recent) 20 games played. Here is the coding:
/* Qry_Index_Calc - calculates handicap index from top 10 differentials of last 20 games */
/* Source is "VW_Plyr_Diff" which has handicap differentials already calculated. */
SELECT (AVG ("T10"."HDIF") * .96) "Index", (Count ("T10"."HDIF")) FROM
/* Get only the games needed if less than 20 games have been played. */
(
SELECT FIRST
DECODE ((SELECT COUNT (*) FROM "L20"),
1, 1
, 2, 1
, 3, 1
, 4, 1
, 5, 1
, 6, 1
, 7, 2
, 8, 2
, 9, 3
, 10, 3
, 11, 4
, 12, 4
, 13, 5
, 14, 5
, 15, 6
, 16, 6
, 17, 7
, 18, 8
, 19, 9
, 10)
"L20"."HDIF"
FROM
/* Get up to 20 of the most recent (last) games played. */
( SELECT FIRST 20 "PlayerID" "PID", "GID" "GID",
RANK ( ) OVER ( PARTITION BY "PlayerID" ORDER BY "Diff" ) "Rnk",
"Diff" "HDIF", "Date" "Gdate"
FROM "Vw_Plyr_Diff"
WHERE "PlayerID" = 1)
"L20"
) "T10"
You need to put parentheses around the expression in FIRST. As specified in the Firebird 3.0 Language Reference for FIRST, SKIP:
SELECT
[FIRST <m>] [SKIP <n>]
FROM ...
...
<m>, <n> ::=
<integer-literal>
| <query-parameter>
| (<integer-expression>)
So, use
select first (decode(...)) ....
When using subqueries directly in first, you need to use double parentheses (once for the expression, and once for the fact that sub-queries in expressions are enclosed in parentheses.
The SQL standard OFFSET/FETCH clauses introduced in Firebird 3 do not support expressions.
Beware, your current code doesn't specify an ORDER BY, this means it is undefined exactly which rows are returned, it will depend on location of data inside the database, the access plan, etc. I would recommend that you add an appropriate ORDER BY clause to ensure the returned rows are as expected.
It looks like you're trying to SELECT from the derived table L20 defined in the FROM clause, and not from an actual table L20. If you want to be able to do that, then L20 most be specified as a common table expression.

Oracle: Select multiple values from a column while satisfying condition for some values

I have a column COL in a table which has integer values like: 1, 2, 3, 10, 11 ... and son on. Uniqueness in the table is created by an ID. Each ID can be associated with multiple COL values. For example
ID | COL
——————————
1 | 2
————+—————
1 | 3
————+—————
1 | 10
————+—————
is valid.
What I want to do is select only the COL values from the table that are greater than 3, AND (the problematic part) also select the value that is the MAX of 1, 2, and 3, if they exist at all. So in the table above, I would want to select values [3, 10] because 10 is greater than 3 and 3 = MAX(3, 2).
I know I can do this with two SQL statements, but it's sort of messy. Is there a way of doing it with one statement only?
SELECT col FROM table
WHERE
col > 3
UNION
SELECT MAX(col) FROM table
WHERE
col <= 3
This query does not assume you want the results per id, because you don't explicitely mention it.
I don't think you need pl/sql for this, SQL is enough.

SQL COUNT of COUNT

I have some data I am querying. The table is composed of two columns - a unique ID, and a value. I would like to count the number of times each unique value appears (which can easily be done with a COUNT and GROUP BY), but I then want to be able to count that. So, I would like to see how many items appear twice, three times, etc.
So for the following data (ID, val)...
1, 2
2, 2
3, 1
4, 2
5, 1
6, 7
7, 1
The intermediate step would be (val, count)...
1, 3
2, 3
7, 1
And I would like to have (count_from_above, new_count)...
3, 2 -- since three appears twice in the previous table
1, 1 -- since one appears once in the previous table
Is there any query which can do that? If it helps, I'm working with Postgres. Thanks!
Try something like this:
select
times,
count(1)
from ( select
id,
count(distinct value) as times
from table
group by id ) a
group by times

Recursive SQL for a menu system

I have a table for a menu system with the following structure, and some data.
ID, Text, ParentID, DestinationID
1, Applications, (null), (null)
2, Games, (null), (null)
3, Office, 1, (null)
4, Text Editing, 1, (null)
5, Media, (null), (null)
6, Word, 3, 1
7, Excel, 3, 2
8, Crysis, 2, 3
What I need is a query to which I can pass the menu ID, and it will return a list of items that have that ID as a child. BUT, I need it to only return children that have a valid path to a destination. So in the example above, the user will be presented initially with (Applications, Games), when he selects Applicaions, he is presented with (Office). Text Editing and Media should be omitted, because there are no valid destinations beneath them.
The trickiest thing about this, is that there is no predetermined depth to any given menu.
EDIT:
Today, the problem came up for MS SQL 2008, but in the past 2 weeks I've needed similar solutions for SQLite and SQL CE. The ideal solution should not be tied to any specific SQL engine.
SQL server only, but it sounds like a job for Common Table Expressions.
In Oracle:
SELECT m.*, level
FROM my_table m
START WITH
Id = :startID
CONNECT BY
ParentID = PRIOR Id
AND DestinationID IS NOT NULL
There is no way to do it in ANSI SQL with a single query. You may create an additional column AccessPath for you table:
ID, Text, ParentID, DestinationID AccessPath
1, Applications, (null), (null), "1"
2, Games, (null), (null), "2"
3, Office, 1, (null), "1.3"
4, Text Editing, 1, (null), "1.4"
5, Media, (null), (null), "5"
6, Word, 3, 1, "1.3.6"
7, Excel, 3, 2, "1.3.7"
8, Crysis, 2, 3, "1.2.8"
, and query:
SELECT mp.Id, mp.Text
FROM my_table mp, my_table mc
WHERE mp.parentID = #startingParent
AND mc.Id <> mp.Id
AND SUBSTR(mc.AccessPath, LENGTH(mp.AccessPath)) = mp.AccessPath
GROUP BY
mp.Id, mp.Text
It's a bad idea to start with NULL, as the index on ParentID cannot be used in this case. For a start, use a fake parentID of 0 instead of NULL.
If the hierarchy/tree that you are stroing in your database does not change very often, I would recommend to use the modified preorder tree traversal (MPTT) algorithm. That would require a different table schema, but would allow you to request a whole subtree with a simple SQL statement (no recursion, etc.).
The article on Storing Hierarchical Data in a Database describes this method in detail.
In your example you would get the following tree, where I call the red numbers the left value and the green right value of a node.
Now, if you want to select the Office subtree, you can do this with:
SELECT * FROM tree WHERE left BETWEEN 10 AND 15 AND destination IS NOT NULL
If your DB does not support the BETWEEN statement, you can of course write left > 10 AND left < 15 instead.
Your table would look like this:
name | left | right | destination
------------------------------------------
root | 1 | 17 | NULL
Applications | 7 | 16 | ...
...
If this is a problem that interests you (or plagues you), you may want to check out: Joe Celko's Trees and Hierarchies in SQL for Smarties.
As others have pointed out, there's no way in standard ANSI SQL to do what you want. For something like this, I once implemented on SQL 2000 a system for tracking components of products an ex employer made - each "product" could be atomic component like, say, screw A500. This component could be used in "composite" components: some A500 screws plus 6 B120 wood boards conformed a C90 "stylish tool box". That box, plus more screws and a motor "M500" could conform a carpetry tool.
I designed a table "Product" like this:
ID, PartName, Description
1, A500, "Screw A500"
2, B120, "Wood panel B120"
3, C90, "Stylish tool box C90"
4, M500, "Wood cutter M500"
And a "ProductComponent" table as follows:
Hierarchy, ComponentID, Amount
0301, 1, 24
0302, 2, 6
0401, 1, 3
0402, 3, 1
0403, 4, 1
040201, 1, 24
040202, 2, 6
The trick is: field hierarchy is a VARCHAR with first 2 chars representing each product's ID, each next pair of chars identify a node in the tree. So we see that product 3 depends on 2 other products. Product 4 depends on 2 others, also, one of which depends on its part on two others.
There's lots of redundancy in this model, but allows to easily calculate how many screws you need for a particular product, determine fastly which parts need wood panels or get the list of all components a product ultimately depends on (including indirect dependencies), etc. And scanning the tree below a certain level is a simple LIKE query!
By using 2 chars in a hexadecimal representation I limited a product to depend directly on maximum 256 other prods (which on turn can depend on something else). You could change that to use base 36 (the 26 letters plus 10 numbers) or base-64 if you need more than that.
Besides, this table model works very well on Access and mySQL, too. What you can not have is circular dependencies in any way.
SQL is not very good at walking arbitrary depth hierarchies.
If there's less than 1000 of these records, I would grab them all to the application and construct the graph there.
If there's more than 1000 of these records, I'd group them into raw subtrees of approx 1000 (by adding a SubtreeID foreign key) and fetch each subtree... then construct the graph of the subtree in the application.
The first thing I'd do is strip out the destination column - it's meaningless in terms of hierarchy (it actually appears to be a kind of second primary key to signal a live child row the way you've represented it)
this would give
ID, Item, parentID
1, Applications, (null)
2, Games, (null)
3, Office, 1
4, Text Editing, 1
5, Media, (null)
6, Word, 3
7, Excel, 3
8, Crysis, 2
e.g...
word > office > applications and...
excel > office > applications
...should presumably be on the same menu item (parent id 3)
I'm not sure how you're selecting the menu but I'll work on the principle that there's an initial menu button set with (null) as it's parameter and each subsequent click holds the next parameter in sequence dynamically (which seems to match your comments)
e.g.
click top level menu :- value is (null)
click applications :- value is 1
click Office :- value is 3
etc.
Assuming the destinationID is doing nothing apart from showing an active child link (allowing you to remove it), the code would then be as follows:
with items (nodeID, PID, list) as
(select id, ParentID, item
from menu
where id = 9
union all
select id, ParentID, item
from menu
inner join items on nodeID = menu.ParentID
)
select *
from items
where (pid = 9)
and nodeID in (select parentid from menu)
This works in MSSQL 2005+
If you need the destination id for some other reason then you can amend the code as follows (if you need to return the lowest level where a node id hasn't been set as a parent id, for instance):
with items (nodeID, PID, list, dest) as
(select id, ParentID, item, destinationID
from menu
where id = 9
union all
select id, ParentID, item, destinationID
from menu
inner join items on nodeID = menu.ParentID
)
select *
from items
where (pid = 9)
and (nodeID in (select parentid from menu)
or dest is not null)
https://geeks.ms/jirigoyen/2009/05/22/recursividad-con-sql-server/
ALTER PROCEDURE [dbo].[Usuarios_seguridad_seleccionar]
AS
BEGIN
DECLARE #minClave int
SELECT #minClave = MIN(Clave) FROM dbo.Usuarios_seguridad;
WITH UsuariosAccesos AS(
SELECT top 1 us1.Padre,us1.Clave,us1.Variable,us1.Modulo,us1.Contenido,us1.Acceso,us1.Imagen
FROM dbo.Usuarios_seguridad us1
WHERE us1.Clave = #minClave
UNION ALL
SELECT top 100 percent us2.Padre,us2.Clave,us2.Variable,us2.Modulo,us2.Contenido,us2.Acceso,us2.Imagen
FROM dbo.Usuarios_seguridad us2
INNER JOIN UsuariosAccesos AS us3 ON us3.Clave = us2.Padre
WHERE us2.Clave <> #minClave
)
SELECT TOP 100 PERCENT ia.Padre,ia.Clave,ia.Variable,ia.Modulo,ia.Contenido,ia.Acceso,ia.Imagen
FROM UsuariosAccesos ia
ORDER BY padre, clave
END
GO