Recursive query to get desired resultset - postgresql

I have three tables: Superobject, object_master and object_child.
SuperObject contains superobj_id and obj_id. obj_master contains all the details about the object.
Object_child has two columns: obj_id and child_id. It contains object and its child. A child can also have a subchild. So, an object can have multiple childs.
SuperObject Table object_child table
sobj1 obj1 obj1 ch_obj1
sobj1 obj2 obj1 ch_obj2
sobj1 obj3 ch_obj1 ch_obj3
I want resultset in format:
obj1 ch_obj1
obj1 ch_obj2
obj1 ch_obj3
obj2 ------
obj2 ------
obj3 ------
I am using the following query:
with recursive objects as (
select objectid
from object_masster
where objectid in (obj1, obj2, obj3)
union
select a.child_id
from object_child a a join objects b on a.objectid = b.objectid
)
select * from objects
It is returning me all the children for the above objects but not in the desired format.

The trick with recursive queries is that you need to store all of the data in the resultset of the seed bit and recursive bit of the union so you can have it to: A) perform the next lookup, B) display whatever you need when you select on the recursive CTE you've built.
So, for your requirements, we need to store the root node (the first objectid you are selecting from your master table), and then parent and child as we recursively select.
Also, because you want that root node to make it through to the end of all the recursive lookup, you need to keep selecting that through in your recursive bit of the union.
This will look something like:
WITH RECURSIVE objects AS (
SELECT objectid AS root, CAST(NULL AS VARCHAR(10)) AS parent, objectid AS child
FROM object_master
WHERE objectid IN (obj1, obj2, obj3)
UNION
SELECT b.root AS root, b.child AS parent, a.child_id AS child
FROM object_child a
INNER JOIN objects b
ON a.objectid = b.child
)
SELECT root, child FROM objects

Related

How to identify and list Circular Reference elements in Postgres

I have an employee , manager hierarchy which could end up being circular.
Ex:
28397468N>88518119N>87606705N>28397468N
Create Table emp_manager ( Emp_id varchar(30), Manager_id varchar(30));
Insert into emp_manager values ('28397468N','88518119N');
Insert into emp_manager values ('88518119N','87606705N');
Insert into emp_manager values ('87606705N','28397468N');
My requirement is:
When my proc is called and there are circular hierarchies in the emp_manager table, we should return an error listing the employees in the hierarchy.
The below link contains some useful info:
https://mccalljt.io/blog/2017/01/postgres-circular-references/
I have modified it as below:
select * from (
WITH RECURSIVE circular_managers(Emp_id, Manager_id, depth, path, cycle) AS (
SELECT u.Emp_id, u.Manager_id, 1,
ARRAY[u.Emp_id],
false
FROM emp_manager u
UNION ALL
SELECT u.Emp_id, u.Manager_id, cm.depth + 1,
(path || u.Emp_id)::character varying(32)[],
u.Emp_id = ANY(path)
FROM emp_manager u, circular_managers cm
WHERE u.Emp_id = cm.Manager_id AND NOT cycle
)
select
distinct (path) d
FROM circular_managers
WHERE cycle
AND path[1] = path[array_upper(path, 1)]) cm
BUT, the problem is, it is returning all combinations of the hierarchy:
{28397468N,88518119N,87606705N,28397468N}
{87606705N,28397468N,88518119N,87606705N}
{88518119N,87606705N,28397468N,88518119N}
I need a simple answer like this:
28397468N>88518119N>87606705N>28397468N
even this will do:
28397468N>88518119N>87606705N
Please help!
So all references:
{28397468N,88518119N,87606705N,28397468N}
{87606705N,28397468N,88518119N,87606705N}
{88518119N,87606705N,28397468N,88518119N}
are correct but just start from different element.
I need a simple answer like this: 28397468N>88518119N>87606705N>28397468N
So what's needed is a filter for the same circle refs.
Let's do that in a way:
sort distinct items in arrays
aggregate them back - so for all references it will be '{28397468N,87606705N,88518119N}'
use produced value for DISTINCT FIRST_VALUE
WITH D (circle_ref ) AS (
VALUES
('{28397468N,88518119N,87606705N,28397468N}'::text[]),
('{87606705N,28397468N,88518119N,87606705N}'::text[]),
('{88518119N,87606705N,28397468N,88518119N}'::text[])
), ordered AS (
SELECT
D.circle_ref,
(SELECT ARRAY_AGG(DISTINCT el ORDER BY el) FROM UNNEST(D.circle_ref) AS el ) AS ordered_circle
FROM
D
)
SELECT DISTINCT
FIRST_VALUE (circle_ref) OVER (PARTITION BY ordered_circle ORDER BY circle_ref) AS circle_ref
FROM
ordered;
circle_ref
{28397468N,88518119N,87606705N,28397468N}
DB Fiddle: https://www.db-fiddle.com/f/6ytb2v11s8T95PPLoTZZed/0
To prevent circular references, you can use a closure table and a trigger - as explained in https://stackoverflow.com/a/38701519/5962802
The closure table will also allow you to easily get all subordinates for a given supervisor (no matter how deep in the hierarchy) - or all direct bosses of a given employee (up to the root).
Before using the rebuild_tree stored procedure you will have to remove all circular references from the hierarchy.

Aggregate a boolean array in a LEFT JOIN LATERAL in Postgres

Question
How can I achieve aggregation of the boolean array in the LEFT JOIN LATERAL?
A result can have many owners.
There can only by one ownership record that is original - that's because we are supporting the assignment of a result by the original owner to a new owner.
In this case, we would have two ownership records, differing in the owner_id as well as the original boolean value of the row.
ERD
Note: There is a small error in this ERD. The owners table has id INT and owner UUID (not just a single id column). This probably doesn't contribute to the issue - but it does make the SQL a little more gnarly than it should be.
Results
guid
assigned
061c840a-f8d2-4200-8d17-40b35650de40
{true}
074e3e67-c22a-4681-a5da-844b943a0cf9
"{false,true}"
084de451-6531-4997-b788-17819f29a6cb
{true}
Desired Results
guid
assigned
061c840a-f8d2-4200-8d17-40b35650de40
true
074e3e67-c22a-4681-a5da-844b943a0cf9
false
084de451-6531-4997-b788-17819f29a6cb
true
SQL
SELECT DISTINCT ON (results.id) results.id as guid,
assigned
FROM results
LEFT JOIN LATERAL ( SELECT *,
(SELECT array(SELECT ownership.original
FROM ownership
where ownership.result_id = results.id)) as assigned
FROM (ownership
LEFT JOIN owners on (ownership.owner_id = owners.id))) as resultOwnership
ON results.id = resultOwnership.result_id
WHERE resultOwnership.owner = '2a09be27-3fed-4c48-9c63-6877dddc95f5';

PostGres joins Using JSONB

I have two tables which look like such
Organizations
Id (primary_key. big int)
Name (text)
CustomInformations
Id (primary_key. big int)
ConnectedIdentifiers (JSONB)
Info (text)
CustomInformations's ConnectedIdentifiers column contains a JSONB structure which looks like
{ 'organizations': [1,2,3] }
This means that there's an array of organizations with those ids 1, 2, 3, which are related to that particular CustomInformation
I'm trying to do a JOIN where given a CustomInformation Id will also get me all the Organizations names
I tried this after looking at some examples:
SELECT * FROM CustomInformations ci
INNER JOIN Organizations o on jsonb_array_elements(ci.ConnectedIdentifiers->'19') = o.id
WHERE
ci.id = 5
I got an error No operator matches the given name and argument type(s). You might need to add explicit type casts.
Is this the right approach? And if so what is wrong with my syntax?
Thanks
You cannot use jsonb_array_elements() in this way because the function returns set of rows. It should be placed in a lateral join instead. Use jsonb_array_elements_text() to get array elements as text and cast these elements to bigint:
select ci.*, o.*
from custominfo ci
-- lateral join
cross join jsonb_array_elements_text(ci.connectedidentifiers->'organizations') ar(elem)
join organizations o
on elem::bigint = o.id
where ci.id = 5

Embedded Select for From value

Having difficulty framing my question for Google.
I am trying to embed a select statement which pulls partition table names from a view. I want to cycle through these tables and do a search within them for a value count.
I have:
SELECT COUNT(objectA)
FROM (SELECT partitiontablename
FROM partitions
WHERE tablename = 'x')
AS tableNameQuery
WHERE objectB = 1
I am getting ERROR: column "objectB" does not exist
The partitions tables do have objectB (they are the same table structure). Can you guide me to what i am doing wrong?
Thank you!
Try this query:
SELECT COUNT(objectA)
FROM (
SELECT partitiontablename, objectB, objectA
FROM partitions
WHERE tablename = 'x'
) AS tableNameQuery
WHERE objectB = 1
The subquery in your query retrieves only partitiontablename column, so the outer query sees only that column, but doesn't see objectB.
The same problem is with objectA used in COUNT() in the outer query.

Multiple WHERE clause for same Columns in TSQL

I am trying to query two tables that are in 1-to-many relationship.
What I've done is create a View knowing that i might end up with multiple records for the first table.
My scenario is as follows: I have a table "Items" and table "Properties".
"Properties" table contains an ItemsId column, PropertyId, PropertyValueId columns.
"Items" table/object contains a list of "Properties".
How would I query that "View" such that, I want to get all "Items" records that have a combination of "PropertyId" & "PropertyValueId" values.
In other words something similar to:
WHERE
(PropertyId = #val1 AND PropertyValueId = #val2) OR
(PropertyId = #val3 AND PropertyValueId = #val4) OR
(PropertyId = #val5 AND PropertyValueId = #val6)
WHERE clause is just a loop over "Items.Properties" collection.
"Items" represents a table of Items being stored in the database. Each & every Item has some dynamic properties, one or more. That's why I have another table called "Properties". Properties table contains columns:
ItemId, PropertyId, PropertyValue
"Item" object has a collection of Properties/Values. Prop1:val1, Prop2:val2, etc ...
Thanks
I may not have understood your requirement (despite the update) - if this or any other answer doesn't solve the problem please add some sample data for Items, Properties and the output and then hopefully it would become clear.
If Items is a specification of the property name-value pairs that you need (and has nothing to do with ItemId on Properties which seems strange...)
select p.itemid
from properties p
where exists (select 1 from items i where i.propertyId = p.propertyId and i.propertyValueId = p.propertyValueId)
group by p.itemid
having count(distinct p.propertyid) = (select count(*) from items)
This returns a set of itemids that have one (and only one) property value for each property defined in items. You can put the items count into a variable if you want.
I would use a query like this:
SELECT ItemId
FROM ItemView
WHERE (PropertyId = #val1 AND PropertyValueId = #val2)
OR (PropertyId = #val3 AND PropertyValueId = #val4)
OR (PropertyId = #val5 AND PropertyValueId = #val6)
GROUP BY ItemId
HAVING COUNT(*) = 3
The WHERE clause is the same as in your question, it only allows a row to be selected if the row has a matching property. You only need to make sure additionally that the items obtained have all the properties in the filter, which is done in the above query with the help of the HAVING clause: you are requesting items with 3 specific properties, therefore the number of properties per item in your result set (COUNT(*)) should be equal to 3.
In a more general case, when the number of properties queried may be arbitrary, you should probably consider passing the arguments in the form of a table and join the view to it:
…
FROM ItemView v
INNER JOIN RequestedProperties r ON v.PropertyId = r.Id
AND v.PropertyValueId = r.ValueId
GROUP BY v.ItemId
HAVING COUNT(*) = (SELECT COUNT(*) FROM RequestedProperties)