orientdb traverse until condition on node - orientdb

I'm trying to traverse some nodes and I want to stop the traversal when the furthest reached node matches a certain condition.
My data is some sequentially connected nodes - there is a sample data creation script here (note this is much simplified from my real data, just to illustrate the problem):
create database plocal:people
create class Person extends V
create property Person.name string
create property Person.age float
create property Person.ident integer
insert into Person(name,age,ident) VALUES ("Bob", 30.5, 1)
insert into Person(name,age,ident) VALUES ("Bob", 30.5, 2)
insert into Person(name,age,ident) VALUES ("Carol", 20.3, 3)
insert into Person(name,age,ident) VALUES ("Carol", 19, 4)
insert into Person(name,age,ident) VALUES ("Laura", 75, 5)
insert into Person(name,age,ident) VALUES ("Laura", 60.5, 6)
insert into Person(name,age,ident) VALUES ("Laura", 46, 7)
insert into Person(name,age,ident) VALUES ("Mike", 16.3, 8)
insert into Person(name,age,ident) VALUES ("David", 86, 9)
insert into Person(name,age,ident) VALUES ("Alice", 5, 10)
insert into Person(name,age,ident) VALUES ("Nigel", 69, 11)
insert into Person(name,age,ident) VALUES ("Carol", 60, 12)
insert into Person(name,age,ident) VALUES ("Mike", 16.3, 13)
insert into Person(name,age,ident) VALUES ("Alice", 5, 14)
insert into Person(name,age,ident) VALUES ("Mike", 16.3, 15)
create class NEXT extends E
create edge NEXT from (select from Person where ident = 1) to (select from Person where ident = 3)
create edge NEXT from (select from Person where ident = 2) to (select from Person where ident = 4)
create edge NEXT from (select from Person where ident = 8) to (select from Person where ident = 12)
create edge NEXT from (select from Person where ident = 5) to (select from Person where ident = 15)
create edge NEXT from (select from Person where ident = 15) to (select from Person where ident = 14)
create edge NEXT from (select from Person where ident = 7) to (select from Person where ident = 13)
create edge NEXT from (select from Person where ident = 13) to (select from Person where ident = 10)
Let's define a sequence as a traversal of nodes from a node with no incoming links (a start node). I am trying to write a query that will return me all sequences of nodes up until the first occurrence of a specific name encountered in the traversal. Suppose the specific name is 'Mike'. So for this data I would want the following sequences to be found:
("Laura", 75, 5) -> ("Mike", 16.3, 15),
("Laura", 46, 7) -> ("Mike", 16.3, 13),
("Mike", 16.3, 8)
I can get the sequence from a specific node to the node just before a 'Mike'. The following query returns me record ("Laura", 75, 5), as the record after that one has name 'Mike'
traverse out('NEXT') from (select from Person where ident = 5) while name <> 'Mike'
The following query returns me two rows, one with record ("Laura", 75, 5) and one with record ("Mike", 16.3, 15), as the record after Mike has name 'Alice'.
traverse out('NEXT') from (select from Person where ident = 5) while name <> 'Alice'
I have two problems with this - firstly I'd like to include the node which matches the condition in the sequence (i.e. when checking for a Person called 'Mike', I'd like 'Mike' to be the final node in the sequence returned)
For that I assume I need to store the traversal in an object, and request one more out-Next for that object before returning. I've tried various approaches to storing the traversal in an object in the middle of a query, but I'm just not getting it. Here is an example (which errors):
select from (
select $seq from
(select from Person where ident = 5)
let $seq = traverse out('NEXT') from $current while name <> 'Alice'
<... here append the next node ...>
)
Secondly, this query just starts from one node - I'd like to start at all starting nodes and return a sequence ending in 'Mike' wherever there is one. I'm hoping that once I can store the traversal in an object, it should be relatively straight-forward to just run from multiple starting points rather than just one.
(Of course, another option for this specific query is to find all the nodes that match the specific condition (e.g. name= 'Mike') and work backwards from those, but I'd really like to see it work in the way I described initially as I'll need that general approach for more things later.)
I suspect quite a lot of my issue is that I'm really struggling to work out how to use the let statement in OrientDB - I'm really not understanding how the scope works, which objects exist at what stages of the query. If anyone knows any good documentation out there other than the official docs that would be really useful as I've read those and I'm still not getting it.
So any helpful hints on how to answer this question, or where to find more information on how to write this type of query would be really useful.

I hope it can help you
select expand($c) let $b=(select expand(out("NEXT")[name="Alice"]) from (select expand(last($a)) from (select from Person where ident = 5)
let $a = (traverse out('NEXT') from $current while name <> 'Alice')) limit 1), $c=unionAll($a,$b)

select name, list from (select name,$c.name as list from Person
let $b=( select expand(out("NEXT")[name="Alice"]) from (select expand(last($a)) from $parent.$current
let $a = (traverse out('NEXT') from $current while name <> 'Alice')) limit 1),
$c=unionAll($a,$b) where in("NEXT").size()=0)
where list contains "Alice"

Related

Writing a query in SQLAlchemy to count occurrences and store IDs

I'm working with a postgres db using SQLAlchemy.
I have a table like this
class Author(Base):
__tablename__ = "Author"
id = Column(BIGINT, primary_key=True)
name = Column(Unicode)
and I want to identify all homonymous authors and save their id in a list.
For example if in the database there are 2 authors named "John" and 3 named "Jack", with ID respectively 11, 22, 33, 44 a 55, I want my query to return
[("John", [11,22]), ("Jack", [33,44,55])]
For now I've been able to write
[x for x in db_session.query(
func.count(Author.name),
Author.name
).group_by(Author.name) if x[0]>1]
but this just gives me back occurrences
[(2,"John"),(3,"Jack")]
Thank you very much for the help!
The way to do this in SQL would be to use PostgreSQL's array_agg function to group the ids into an array:
SELECT
name,
array_agg(id) AS ids
FROM
my_table
GROUP BY
name
HAVING
count(name) > 1;
The array_agg function collects the ids for each name, and the HAVING clause excludes those with only a single row. The output of the query would look like this:
name │ ids
═══════╪════════════════════
Alice │ {2,4,9,10,16}
Bob │ {1,6,11,12,13}
Carol │ {3,5,7,8,14,15,17}
Translated into SQLAlchemy, the query would look like this:
import sqlalchemy as sa
...
q = (
db_session.query(Author.name, sa.func.array_agg(Author.id).label('ids'))
.group_by(Author.name)
.having(sa.func.count(Author.name) > 1)
)
Calling q.all() will return a list of (name, [ids]) tuples like this:
[
('Alice', [2, 4, 9, 10, 16]),
('Bob', [1, 6, 11, 12, 13]),
('Carol', [3, 5, 7, 8, 14, 15, 17]),
]
In SQLAlchemy 1.4/2.0-style syntax equivalent would be:
with Session() as s:
q = (
sa.select(Author.name, sa.func.array_agg(Author.id).label('ids'))
.group_by(Author.name)
.having(sa.func.count(Author.name) > 1)
)
res = s.execute(q)

Postgresql data calculation

Im trying to do some calculations using postgres, but no sucess so far. My query goes something like this:
select ....,
(select json_agg(data_table)
from (..... HERE GOES DE RESULT OF THE CALCULATION + a lot of business and data.... ) as data_table)
from foo
So i gonna exemplify with a table:
create temp table tbdata (id smallint, parent_id smallint, value numeric(25,2));
insert into tbdata values(1, null, 100), (2, 1, 50), (3, 1, 49), (4, 3, 20), (5, 3, 29);
select * from tbdata;
I need to calculate the difference between the sum of the siblings and the parent value. Example:
ID 2(50) + ID 3(49) = 99
ID 1(parent) = 100
so i need to add 1 to any of the childs (lets say 3), the result gonna be:
ID 2(50) + ID 3(49 + 1) = 100
ID 1(parent) = 100
After that, my ID3 have changed, so i need to update any of his childs:
ID 4(20) + ID 5(29) = 49
ID 3(parent) = 50
then again, updating value of ID 5 with the difference (50 - 49)
ID 4(20) + ID 5(29 + 1) = 50
ID 3(parent) = 50
I tried using recursive queries, windows function, and cte, but i always stuck in something. I was able to do using a function with a loop, but i dont want to do that.
Theres any way i can do it with a single SQL?

Postgresql update column with integer values

I've a column with jsonb type and contains list of elements either in string or integer format.
What I want now is to make all of them as same type e.g either all int or all string format
Tried: this way I get single element but I need to update all of the elements inside of the list.
SELECT parent_path -> 1 AS path
FROM abc
LIMIT 10
OR
Update abc SET parent_path = ARRAY[parent_path]::TEXT[] AS parent_path
FROM abc
OR
UPDATE abc SET parent_path = replace(parent_path::text, '"', '') where id=123
Current Output
path
[6123697, 178, 6023099]
[625953521394212864, 117, 6023181]
["153", "6288361", "553248635949090971"]
[553248635358954983, 178320, 174, 6022967]
[6050684, 6050648, 120, 6022967]
[653, 178238, 6239135, 38, 6023117]
["153", "6288496", "553248635977039112"]
[553248635998143523, 6023185]
[553248635976194501, 6022967]
[553248635976195634, 6022967]
Expected Output
path
[6123697, 178, 6023099]
[625953521394212864, 117, 6023181]
[153, 6288361, 553248635949090971] <----
[553248635358954983, 178320, 174, 6022967]
[6050684, 6050648, 120, 6022967]
[653, 178238, 6239135, 38, 6023117]
[153, 6288496, 553248635977039112] <----
[553248635998143523, 6023185]
[553248635976194501, 6022967]
[553248635976195634, 6022967]
Note: Missing double quotes on the list. I've tried several methods from here but no luck
You will have to unnest them, cleanup each element, then aggregate it back to an array:
The following converts all elements to integers:
select (select jsonb_agg(x.i::bigint order by idx)
from jsonb_array_elements_text(a.path) with ordinality as x(i, idx)
) as clean_path
from abc a;
You can use a scalar subquery to select, unnest, and aggregate the elements:
WITH mytable AS (
SELECT row_number() over () as id, col::JSONB
FROM (VALUES ('[6123697, 178, 6023099]'),
('["6123697", "178", "6023099"]')) as bla(col)
)
SELECT id, (SELECT JSONB_AGG(el::int) FROM jsonb_array_elements_text(col) as el)
FROM mytable

Having trouble with psql \copy command

I'm a postrgresql noob. I'm using psql on a remote server. I've made a query that I'd like to download as a csv. The query runs just fine on its own.
There are two tables involved in the query. The first:
CREATE TABLE players (username varchar(100), skill varchar(100),id int);
INSERT INTO players VALUES
('user1', 'flight', 1),
('user2', 'run', 2),
('user3', 'run', 3),
('user4', 'flight', 4),
('user5', 'flight', 5),
('user6', 'climb', 6),
('user7', 'flight', 7),
('user8', 'flight', 8),
('user9', 'flight', 9),
('user10', 'climb', 10);
The second table is temporary, I only need it to exist to extract this csv:
CREATE TEMP TABLE specific_players (username varchar(100));
INSERT INTO specific_players VALUES
('user2'),
('user5'),
('user7'),
('user9');
my query looks like this:
select * from players p inner join specific_players sp USING (username);
Like I said, that works just fine.
But when I try to wrap it in a copy command
\copy (select * from players inner join specific_players USING (username)) to '/Users/me/path/to/folder/filename.csv' with csv
I get an error:
ERROR: syntax error at or near "("
LINE 1: COPY ( select * from players p join specific_playe...
The file IS created on my computer, but it's empty. Any idea what I'm doing wrong?

zend framework get last insert id of multi row insert using execute

How would I get the last inserted ID using a multirow insert?
Here is my code:
$sql='INSERT INTO t (col1, col2, col3) VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)'; // example
$stmt = $contactsTable->getAdapter()->prepare($sql);
$stmt->execute();
$rowsAdded=$stmt->rowCount(); // mysql_affected_rows
$lastId=$stmt->lastInsertId();
echo '<br>Last ID: '.$lastId;
Also, is there a method in ZF to get the next insert id before an insert?
thanks
$lastId=$contactsTable->getAdapter()->lastInsertId();
This worked.
So, here is the full working code I'm using to create a multi-row insert, getting the rows added and the last inserted id:
$contactsTable = new Contacts_Model_Contacts();
$sql='INSERT INTO t (col1, col2, col3) VALUES (1, 2, 3), (4, 5, 6), (7, 8, 9)'; // example
$stmt = $contactsTable->getAdapter()->prepare($sql);
$stmt->execute();
$rowsAdded=$stmt->rowCount(); // mysql_affected_rows
$lastId=$contactsTable->getAdapter()->lastInsertId(); // last inserted id
echo '<br>Last ID: '.$lastId;
An alternate solution. Move off sql code from controllers and place them in models. That is what they are for.
If you are using models, you can given the name of table which has auto increment column, in class variable.
protected $_name="<table_name>";
Then in your model class method, you can get last insert id from
$insertID= $this->getAdapter()->lastInsertId();
that code should work, but it will only get you the id of your last insert.
you can get the next autoincrement with this mysql query:
SELECT Auto_increment FROM information_schema.tables WHERE TABLE_SCHEMA = 'your_db_name' AND TABLE_NAME='the_table_you_want';