Filter data dynamically based on a hierarchical user id in cube.js - postgresql

I have two tables called Writers and Publications where there is a foreign key publications.writer_id = writers.id
Writers
id (int)
parent_id (int)
role (varchar)
name (varchar)
path (ltree)
1
ADMIN
Firstname Lastname
2
1
EDITOR
Anon Anon
1.2
3
2
WEB EDITOR
Maisy Tickles
1.2.3
4
2
WEB EDITOR
Jack Beanstalk
1.2.4
5
3
WEB PROOFREADER
Sunny Ray
1.2.3.5
Publications
id (int)
writer_id (FK)
publication_name (varchar)
word_length (int)
published (datetime)
1
2
My First Magazine
6000
2019-09-09 09:00:00
2
2
My Second Magazine
6000
2019-09-16 09:00:00
3
3
My First Article
1000
2019-09-23 09:00:00
4
4
My First Article
1500
2019-09-23 09:00:00
5
4
My Second Article
600
2019-10-01 09:00:00
6
5
My First Piece
600
2020-10-01 09:00:00
I want to do a proof of concept in cube.js Developer Playground to show various charts. Is it possible to filter dynamically based on the user_id so that they can only access content that is equal to or in their subtree i.e.
If an ADMIN/EDITOR is using, they can see all the publications
If the WEB EDITOR (writers.id=4) is using the application, they can only see their own articles (publications.id in (4,5))
If the WEB EDITOR (writers.id=3) is using the application, they can see their publication and the WEB PROOFREADER's one (publications.id in (3,6))
The WEB PROOFREADER should only see their publication (publications.id=6)
These are the models I have set up so far
cube(`Writers`, {
sql: `SELECT * FROM public.writers`,
preAggregations: {},
joins: {
Publications:{
sql: `${CUBE}.id = ${Publication}.writer_id`,
relationship: `hasMany`
}
},
measures: {
count: {
type: `count`,
drillMembers: [id, name, created]
}
},
dimensions: {
id: {
sql: `id`,
type: `number`,
primaryKey: true
},
role: {
sql: `role`,
type: `string`,
},
};
cube(`Publications`, {
sql: `SELECT * FROM public.publications`,
preAggregations: {},
joins: {
Writer:{
sql: `${CUBE}.writer_id = ${Writers}.id`,
relationship: `hasOne`
}
},
measures: {
count: {
type: `count`,
drillMembers: [id, name, created]
}
},
dimensions: {
id: {
sql: `id`,
type: `number`,
primaryKey: true
},
wordLength: {
sql: `word_length`,
type: `number`,
},
};
I know there are filters and segments but these appear to be static. Is there any way to pass a writer_id to filter the relevant data dynamically? (I have no previous knowledge of JS)

I think these recipes can help you:
https://cube.dev/docs/recipes/role-based-access
https://cube.dev/docs/recipes/column-based-access
https://cube.dev/docs/recipes/passing-dynamic-parameters-in-a-query

I think you can query the cube from your front-end application with a filter of id.

Related

Use dimension when creating measure with Case statement in Looker

I am trying to create a measure conditionally based on a dimension.
My dimensions:
dimension_group: date {
hidden: yes
type: time
timeframes: [
raw,
date,
week,
month,
quarter,
year
]
convert_tz: no
datatype: date
sql: ${TABLE}.date ;;
}
dimension: status {
type: string
sql: CASE
WHEN UPPER(${TABLE}.status) ='APPROVED' THEN 'Approved'
WHEN UPPER(${TABLE}.status) ='PENDING' THEN 'Pending'
END;;
}
My Measures:
measure: xyz {
type: sum
value_format: "$#,##0.00"
sql: ${TABLE}.xyz ;;
}
measure: abc {
type: sum
value_format: "$#,##0.00"
sql: ${TABLE}.abc ;;
}
Measure with conditions:
measure: conditional {
type: number
value_format: "$#,##0.00"
sql: CASE WHEN ${status} = 'Pending' THEN ${xyz}
ELSE ${abc}
END;;
}
On my Explore, when I select date and conditional.  I keep getting the error:
ERROR: column "table.status" must appear in the GROUP BY clause or be used in an aggregate function
I understand what the error is. I am just not sure how to fix this. How do I resolve this error? I need all the dimensions and measures.
You can create a dimension conditional:
dimension: conditional {
type: number
value_format: "$#,##0.00"
sql: CASE WHEN ${status} = 'Pending' THEN ${xyz}
ELSE ${abc}
END;;
}
And then create a measure sum_conditional on that dimension:
measure: sum_conditional {
type: sum
value_format: "$#,##0.00"
sql: ${conditional};;
}

JOOQ: how to use multiset

Given these 3 tables with a many-to-many relationship
book
book_id
title
isbn
num_pages
1
JOOQ
1234
123
2
SQL
2345
155
book_author
book_id
author_id
1
1
2
1
2
2
author
author_id
author_name
1
Lucas
2
Jose
And records
public record Book(Integer id, String title, String isbn, List<Author> authors) {}// don't need nr of pages
public record Author(Integer id, String name){}
How to get a List<Book> using multiset?
List<Book> books = dsl.select(
BOOK_AUTHOR.BOOK_ID,
select(BOOK.TITLE).from(BOOK).where(BOOK.BOOK_ID.eq(BOOK_AUTHOR.BOOK_ID)).asField("title"),
select(BOOK.ISBN).from(BOOK).where(BOOK.BOOK_ID.eq(BOOK_AUTHOR.BOOK_ID)).asField("isbn"),
multiset(
selectFrom(AUTHOR).where(AUTHOR.AUTHOR_ID.eq(BOOK_AUTHOR.AUTHOR_ID))
).as("authors").convertFrom(record -> record.map(Records.mapping(Author::new)))
).from(BOOK_AUTHOR)
.where(BOOK_AUTHOR.BOOK_ID.in(1, 2))
.fetchInto(Book.class);
books.forEach(System.out::println);
Expected a list with 2 books, but getting 3 books instead !?...
Book{id=1, title='JOOQ', isbn='1234', authors=[Author{name='Lucas'}]}
Book{id=2, title='SQL', isbn='2345', authors=[Author{name='Lucas'}]}
Book{id=2, title='SQL', isbn='2345', authors=[Author{name='Jose'}]}
What is the problem?
The generated SQL:
select "public"."book_author"."book_id", (select "public"."book"."title" from "public"."book" where "public"."book"."book_id" = "public"."book_author"."book_id") as "title", (select "public"."book"."isbn" from "public"."book" where "public"."book"."book_id" = "public"."book_author"."book_id") as "isbn", (select coalesce(jsonb_agg(jsonb_build_array("v0", "v1")), jsonb_build_array()) from (select "public"."author"."author_id" as "v0", "public"."author"."author_name" as "v1" from "public"."author" where "public"."author"."author_id" = "public"."book_author"."author_id") as "t") as "authors" from "public"."book_author" where "public"."book_author"."book_id" in (1, 2)
Used:
JAVA 17
POSTGRESQL
spring-boot (2.6.1) with spring-boot-starter-jooq
JOOQ 3.15.4 (allows the use of the multiset function)
JOOQ code generation (maven plugin)
The problem is that you are starting from BOOK_AUTHOR that has 3 entries. You should select from BOOK:
List<Book> books = dsl.select(
BOOK.BOOK_ID,
BOOK.TITLE,
BOOK.ISBN,
multiset(
select(AUTHOR.AUTHOR_ID,AUTHOR.AUTHOR_NAME)
.from(AUTHOR).innerJoin(BOOK_AUTHOR)
.on(BOOK_AUTHOR.AUTHOR_ID.eq(AUTHOR.AUTHOR_ID))
.where(BOOK_AUTHOR.BOOK_ID.eq(BOOK.BOOK_ID))
).as("authors")
.convertFrom(record -> record.map(Records.mapping(Author::new)))
).from(BOOK)
.where(BOOK.BOOK_ID.in(1, 2))
.fetchInto(Book.class);

Convert individual postgres jsonb array elements to row elements

I have to query a table with 2 columns, id and content. Id is just a uuid and the content column looks like
{
"fields": [
{
"001": "mig00004139229"
},
{
"856": {
"ind1": " ",
"ind2": " ",
"subfields": [
{
"u": "https://some.domain.com"
},
{
"z": "some text"
}
]
}
},
{
"999": {
"subfields": [
{
"i": "81be1acf-11df-4d13-a5c6-4838e3a808ee"
},
{
"s": "3a6aa357-8fd6-4451-aedc-13453c1f2296"
}
]
}
}
]
}
I need to select the id, 001, and 856 elements where the subfield "u" domain matches a string "domain.com" so the output would be
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
mig00004139229
https://some.domain.com
If this were a flat table, the query would correspond with "select id, 001, 856 from table where 856 like '%domain.com%'"
I can select individual columns based on the criteria I need, but they appear in separate rows except the id which appears with any other individual field in a regular select statement. How would I get the other fields to appear in the same row since it's part of the same record?
Unfortunately, my postgres version doesn't support jsonb_path_query, so I've been trying something along the lines of:
SELECT id, jsonb_array_elements(content -> 'fields') -> '001',
jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields'
FROM
mytable
WHERE....
This method returns the data I need, but the individual elements arrive on separate rows with the with the id in the first column and nulls for every element that is neither the 001 nor 856 e.g.
id
001
856
id_for_first_record
001_first_record
null
id_for_first_record
null
null
id_for_first_record
null
null
id_for_first_record
null
856_first_record
id_for_second_record
001_second_record
null
id_for_second_record
null
null
id_for_second_record
null
null
id_for_second_record
null
856_second_record
Usable, but clunky so I'm looking for something better
I think my query can help you. There are different ways to resolve this, I am not sure if this is the best approach.
I use jsonb_path_query() function with the path for the specified JSON value.
SELECT
id,
jsonb_path_query(content, '$.fields[*]."001"') AS "001",
jsonb_path_query(content, '$.fields[*]."856".subfields[*].u') AS "856"
FROM t
WHERE jsonb_path_query_first(content, '$.fields[*]."856".subfields[*].u' )::text ilike '%domain%';
Output:
id
001
856
81be1acf-11df-4d13-a5c6-4838e3a808ee
"mig00004139229"
"https://some.domain.com"
UPDATED: because of Postgresql version is prior to 12.
You could try something like this, but I think there must be a better approach:
SELECT
t.id,
max(sq1."001") AS "001",
max(sq2."856") AS "856"
FROM t
INNER JOIN (SELECT id, (jsonb_array_elements(content -> 'fields') -> '001')::text AS "001" FROM t) AS sq1 ON t.id = sq1.id
INNER JOIN (SELECT id, (jsonb_array_elements(jsonb_array_elements(content -> 'fields') -> '856' -> 'subfields') -> 'u')::text AS "856" FROM t) AS sq2 ON t.id = sq2.id
WHERE sq2."856" ilike '%domain%'
GROUP BY t.id;

how create left join query with sails.js

I would like do a left join query in sails.js. I think i should use populate
I have three models
caracteristique{
id,
name,
races:{
collection: 'race',
via: 'idcaracteristique',
through: 'racecaracteristique'
},
}
race{
id,
name,
caracteristiques:{
collection: 'caracteristique',
via: 'idrace',
through: 'racecaracteristique'
}
}
RaceCarecteristique{
idrace: {
model:'race'
},
idcaracteristique: {
model: 'caracteristique'
},
bonusracial:{
type: 'number',
}
My data are:
Table Caracteristiques
id name
1 | strength
2 | dex
3 | Charisme
Table Race
id name
1 | human
2 | Org
TableRaceCarecteristique
idrace idcaracteristique bonusracial
1 | 2 | +2
This sql request give me for human, all caracteristiques and if exist bonusracial
'SELECT caracteristique.id, caracteristique.name, bonusracial
FROM caracteristique
LEFT OUTER JOIN (select idcaracteristique, bonusracial
from racecaracteristique
where idrace=$1 ) as q
ON q.idcaracteristique = caracteristique.id';
I have this result:
caracteristique.id, caracteristique.name, bonusracial
1 | strength | null
2 | dex | 2
3 | Charisme | null
How use populate to do this ?
When using a SQL-database adapter (MySQL, PQSL etc) you can utilise a method for performing actual, handwritten SQL statements. When all else fails, this might be your best bet to find an acceptable solution, within the framework.
The .sendNativeQuery() method sends your parameterized SQL statement to the native driver, and responds with a raw, non-ORM-mangled result. Actual database-schema specific tables and columns appear in the result, so you need to be careful with changes to models etc. as they might change the schema in the backend database.
The method takes two parameters, the parameterized query, and the array of values to be inserted. The array is optional and can be omitted if you have no parameters to replace in the SQL statement.
Using your already parameterized query from above, I'm sending the query to fetch the data for an "org" (orc perhaps?) in the example below. See the docs linked at the bottom.
Code time:
let query = `
SELECT caracteristique.id, caracteristique.name, bonusracial
FROM caracteristique
LEFT OUTER JOIN (select idcaracteristique, bonusracial
from racecaracteristique
where idrace=$1 ) as q
ON q.idcaracteristique = caracteristique.id`;
var rawResult = await sails.sendNativeQuery(query, [ 2 ]);
console.log(rawResult);
Docs: .sendNativeQuery()

Selecting rows only if meeting criteria

I am new to PostgreSQL and to database queries in general.
I have a list of user_id with university courses taken, date started and finished.
Some users have multiple entries and sometimes the start date or finish date (or both) are missing.
I need to retrieve the longest course taken by a user or, if start date is missing, the latest.
If multiple choices are still available, then pick random among the multiple options.
For example
on user 2 (below) I want to get only "Economics and Politics" because it has the latest date;
on user 6, only "Electrical and Electronics Engineering" because it is the longer course.
The query I did doesn't work (and I think I am off-track):
(SELECT Q.user_id, min(Q.started_at) as Started_on, max(Q.ended_at) as Completed_on,
q.field_of_study
FROM
(select distinct(user_id),started_at, Ended_at, field_of_study
from educations
) as Q
group by Q.user_id, q.field_of_study )
order by q.user_id
as the result is:
User_id Started_on Completed_on Field_of_studies
2 "2001-01-01" "" "International Economics"
2 "" "2002-01-01" "Economics and Politics"
3 "1992-01-01" "1999-01-01" "Economics, Management of ..."
5 "2012-01-01" "2016-01-01" ""
6 "2005-01-01" "2009-01-01" "Electrical and Electronics Engineering"
6 "2011-01-01" "2012-01-01" "Finance, General"
6 "" "" ""
6 "2010-01-01" "2012-01-01" "Financial Mathematics"
I think this query should do what you need, it relies on calculating the difference in days between ended_at and started_at, and uses 0001-01-01 if the started_at is null (making it a really long interval):
select
educations.user_id,
max(educations.started_at) started_at,
max(educations.ended_at) ended_at,
max(educations.field_of_study) field_of_study
from educations
join (
select
user_id,
max(
ended_at::date
-
coalesce(started_at, '0001-01-01')::date
) max_length
from educations
where (started_at is not null or ended_at is not null)
group by user_id
) x on educations.user_id = x.user_id
and ended_at::date
-
coalesce(started_at, '0001-01-01')::date
= x.max_length
group by educations.user_id
;
Sample SQL Fiddle