Complex recursive query postgreSQL - postgresql

I have a table agent with an id and parent id. This is an adjacency list.
Each agent can sponsor multiple agents.
ID
sponsorId
1
null
2
1
3
2
4
1
5
3
I want to keep the query with a maximum of 4 level of depth.
I already build a recursive like this to get children per level for a specific user, here is the query
WITH RECURSIVE subordinates(id, sponsorId, LEVEL) AS(
SELECT "id","sponsorId",0 AS LEVEL
FROM "Agent" AS t
WHERE id = 1
UNION ALL
SELECT e."id", e."sponsorId", s."level" + 1 AS leve
FROM "Agent" AS e
INNER JOIN subordinates s ON s."id" = e."sponsorId"
WHERE LEVEL < 4 )
SELECT s."level", array_agg(id ORDER BY s."level") AS childrens
FROM subordinates AS s
GROUP BY s."level";
This query give an array of ids per level for a specific user:
ID
sponsorId
0
[1]
1
[2, 4]
2
[3]
3
[5]
4
[]
But I also want the list of all user with a column per level and a total column, like this but I can't get to make this working, like in this table
ID
level1
level2
level 3
level 4
1
[2, 4]
[3]
[5]
[]
2
[3]
[]
[]
[]
3
[5]
[]
[]
[]
4
[]
[]
[]
[]
5
[]
[]
[]
[]
Could you help me writing this query ? I have only small experience with recursive query?
I try a lot of different things to make it works but I don't get how I can create a specific column a bundle the result of the recursive iteration on this new column.
I did manage to get children array for a specific user but the result I get is "vertical", I would like to get it like it the example table above.
I guess I can maybe find something like PIVOT to achieve that, but I never used it so I don't really know how to handle that.

Related

How to create an inline table from an existing table

I have a table in qlik Sense loaded from the database.
Example:
ID
FRUIT
VEG
COUNT
1
Apple
5
2
Figs
10
3
Carrots
20
4
Oranges
12
5
Corn
10
From this I need to make a filter that will display all the Fruit/Veg records along with records from other joined tables, when selected.
The filter needs to be something like this :
|FRUIT_XXX|
|VEG_XXX |
Any help will be appreciated.
I do not know how to do it in qlicksense, but in SQL it's like this:
SELECT
ID
CASE WHEN FRUIT IS NULL THEN VEG ELSE FRUIT END as FruitOrVeg,
COUNT
FROM tablename
Not sure if its possible to be dynamic. Usually I solve these by creating a new field that combines the values from both fields into one field
RawData:
Load * Inline [
ID , FRUIT ,VEG , COUNT
1 , Apple , , 5
2 , Figs , , 10
3 , ,Carrots , 20
4 , Oranges , , 12
5 , ,Corn , 10
];
Combined:
Load
ID,
'FRUIT_' & FRUIT as Combined
Resident
RawData
Where
FRUIT <> ''
;
Concatenate
Load
ID,
'VEG_' & VEG as Combined
Resident
RawData
Where
VEG <> ''
;
This will create new table (Combined) which will be linked to the main table by ID field:
The new Combined field will have the values like this:
And the UI:
P.S. If further processing is needed you can join the Combined table to the RawData table. This way the Combined field will become part of the RawData table. To achieve this just extend the script a bit:
join (RawData)
Load * Resident Combined;
Drop Table Combined;

How to join tables by overlapping jsonb values? (PostgreSQL)

To clarify the question, here's an example:
Table venues
id match_ids (jsonb)
---------------------
1 []
2 [112]
3 ["25", 112]
4 [25, 112]
5 ["112"]
6 ["999"]
Table sports
id object (jsonb)
--------------------
1 {"match_ids": [25, 112]}
2 {}
3
To join venues and sports so that the joined table has at least one element in common, how does the SQL statement have to look like?
Here's the expected outcome:
sports.id venue.id venue.match_ids
-------------------------------
1 2 [112]
1 3 ["25", 112]
1 4 [25, 112]
1 5 ["112"]
I wrote the following...
select * from venues
join sports on venues.match_ids <# sports.object::jsonb->'match_ids';
... but this resulted in
ERROR: operator does not exist: boolean -> unknown
LINE 4: sports.object::jsonb->'match_ids';
^
HINT: No operator matches the given name and argument types. You might need to add explicit type casts.
(Here's a fiddle: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=d5263069f490828288c30b6a51a181c8)
Any advice will be appreciated!
PS: I use PostgreSQL v13.
I managed to solve the issue by moving from jsonb to array and working with array intersect &&
select * from venues
join sports on ARRAY(SELECT jsonb_array_elements(match_ids)::varchar)
&&
ARRAY(SELECT jsonb_array_elements(sports.object::jsonb->'match_ids')::varchar);
https://dbfiddle.uk/?rdbms=postgres_13&fiddle=5143f236235e4411c729f41a60c69012
EDIT from the original poster
Thank you so much #Ftisiot for this brilliant solution.
However, I found that this approach works if match_ids in venues has integers only. If the column has texts, it may not work. To select both texts and integers, jsonb_array_elements_text should be used.
e.g.
select * from venues
join sports on ARRAY(SELECT jsonb_array_elements_text(match_ids))
&&
ARRAY(SELECT jsonb_array_elements_text(sports.object::jsonb->'match_ids'));
https://dbfiddle.uk/?rdbms=postgres_13&fiddle=64f8fac38ccf73d38834c22e90ba4b2c
Change the precedence with parentheses:
... on venues.match_ids <# (sports.object::jsonb->'match_ids')

Calculate value based on existence of records matching given criteria - FileMaker Pro 13

How can I write a calculation field in a table that outputs '1' if there are other (related) records in the same table that meet a given set of criteria and '0' otherwise?
Here's my problem explained in more detail:
I have a table containing 'students' and another containing 'exam results'. The 'exam results' table looks like this:
StudentID SubjectID Level Result
3234 1 2 A-
3234 2 4 B+
4739 1 4 C+
A student can only pass a Level 4 exam in subject 2 if they have also passed a Level 2 exam in subject 1 with a B+ or higher. I want to define a field in the 'students' table that contains a '1' if there exists an exam result belonging to the right student that meets these criteria and a '0' otherwise.
What would be the best way to do this?
Let us take an example of a Results table where the results are also calculated as a numeric value, e.g.
StudentID SubjectID Level Result cResultNum
3234 1 2 A- 95
3234 2 4 B+ 85
4739 1 4 C+ 75
and an Exams table with the following fields (among others):
RequiredSubjectID
RequiredLevel
RequiredResultNum
Given these, you can construct a relationship between Exams and (another occurrence of) Results as:
Exams::RequiredSubjectID = Results 2::SubjectID
AND
Exams::RequiredLevel = Results 2::Level
AND
Exams::RequiredResultNum ≤ Results 2::cResultNum
This allows each exam record to calculate a list of students that are eligible to take that exam as =
List ( Results 2::StudentID )
I want to define a field in the 'students' table that contains a '1'
if there exists an exam result belonging to the right student that
meets these criteria and a '0' otherwise.
This request is unclear, because there are many exams a student may want to take, and a field in the Students table can calculate only one result.
You need to do a self-join in the table for the field you want to check, for example:
Exam::Level = Exam2::Level
Exam::Student = Exam2::Student
And for the "was passed" criteria I think you could do an "If" on the calculation like this:
If ( Last(Exam2::Result) = "D" and ...(all the pass values) ; 1 ; 0 )
Edit:
It could be just with the not pass value hehe I miss that it will be like this:
If ( Last(Exam2::Result) = "F" ; 0 ; 1 )
I hope this helps you.

Postgres bitmask group by

I have the following flags declared:
0 - None
1 - Read
2 - Write
4 - View
I want to write a query that will group on this bitmask and get the count of each flag used.
person mask
a 0
b 3
c 7
d 6
The result should be:
flag count
none 1
read 2
write 3
view 2
Any tips would be appreciated.
For Craig
SELECT lea.mask as trackerStatusMask,
count(*) as count
FROM Live le
INNER JOIN (
... --some guff
) lea on le.xId = lea.xId
WHERE le.xId = p_xId
GROUP BY lea.mask;
SQL Fiddle
select
count(mask = 0 or null) as "None",
count(mask & 1 > 0 or null) as "Read",
count(mask & 2 > 0 or null) as "Write",
count(mask & 4 > 0 or null) as "View"
from t
Simplest - pivoted result
Here's how I'd approach it:
-- (after fixing the idiotic mistakes in the first version)
SELECT
count(nullif(mask <> 0, True)) AS "none",
count(nullif(mask & 2,0)) AS "write",
count(nullif(mask & 1,0)) AS "read",
count(nullif(mask & 4,0)) AS "view"
FROM my_table;
-- ... though #ClodAldo's version of it below is considerably clearer, per comments.
This doesn't do a GROUP BY as such; instead it scans the table and collects the data in a single pass, producing column-oriented results.
If you need it in row form you can pivot the result, either using the crosstab function from the tablefunc module or by hand.
If you really must GROUP BY, explode the bitmask
You cannot use GROUP BY for this in a simple way, because it expects rows to fall into exactly one group. Your rows appear in multiple groups. If you must use GROUP BY you will have to do so by generating an "exploded" bitmask where one input row gets copied to produce multiple output rows. This can be done with a LATERAL function invocation in 9.3, or with a SRF-in-SELECT in 9.2, or by simply doing a join on a VALUES clause:
SELECT
CASE
WHEN mask_bit = 1 THEN 'read'
WHEN mask_bit = 2 THEN 'write'
WHEN mask_bit = 4 THEN 'view'
WHEN mask_bit IS NULL THEN 'none'
END AS "flag",
count(person) AS "count"
FROM t
LEFT OUTER JOIN (
VALUES (4),(2),(1)
) mask_bits(mask_bit)
ON (mask & mask_bit = mask_bit)
GROUP BY mask_bit;
I don't think you'll have much luck making this as efficient as a single table scan, though.

how to get grouped query data from the resultset?

I want to get grouped data from a table in sqlite. For example, the table is like below:
Name Group Price
a 1 10
b 1 9
c 1 10
d 2 11
e 2 10
f 3 12
g 3 10
h 1 11
Now I want get all data grouped by the Group column, each group in one array, namely
array1 = {{a,1,10},{b,1,9},{c,1,10},{h,1,11}};
array2 = {{d,2,11},{e,2,10}};
array3 = {{f,3,12},{g,3,10}}.
Because i need these 2 dimension arrays to populate the grouped table view. the sql statement maybe NSString *sql = #"SELECT * FROM table GROUP BY Group"; But I wonder how to get the data from the resultset. I am using the FMDB.
Any help is appreciated.
Get the data from sql with a normal SELECT statement, ordered by group and name:
SELECT * FROM table ORDER BY group, name;
Then in code, build your arrays, switching to fill the next array when the group id changes.
Let me clear about GroupBy. You can group data but that time its require group function on other columns.
e.g. Table has list of students in which there are gender group mean Male & Female group so we can group this table by Gender which will return two set . Now we need to perform some operation on result column.
e.g. Maximum marks or Average marks of each group
In your case you want to group but what kind of operation you require on price column ?.
e.g. below query will return group with max price.
SELECT Group,MAX(Price) AS MaxPriceByEachGroup FROM TABLE GROUP BY(group)