PL SQL Pivot Table VS Custom Json solution - sql

I'm at a point within one of my Oracle APEX projects where I need to implement different levels of security for specific individuals for specific applications.
To start, I created a cartesian containing the information from the user table, the app table, and the role table.
It looks like this:
SELECT
A.user_id, B.app_id, C.role_id
FROM user A, app B, role C
ORDER BY A.user_id ASC, B.app_id ASC, C.role_id ASC
This allows me to return EVERY combination of user, app, and role. w/o using a where clause it returns over 303k rows. currently almost 500 users, 6 roles, and over 100 apps.
when I select from this view for a specific user its returning in approximately 10 ms which is acceptable.
Now, I also have a vw that stores each user's app/role assignment. I've joined this table to the cartesian in the following fashion.
SELECT
A.*,
DECODE(B.app_right_id, null, 0, 1) AS user_access
FROM
vw_user_app_role A -- My cartesian view
LEFT JOIN vw_tbl_user_app_role B
ON A.user_id = B.user_id
AND A.app_id = B.app_id
AND A.role_id = B.role_id
This returns a very usable set of data that resembles
user_id app_id role_id user_access
50 5 1 0
50 10 2 1
50 15 3 1
75 5 1 1
75 10 2 0
75 15 3 0
I'm considering what my next step should be, If I should create a pivot of the data where the app_id would be the row, the role_id would be the columns, and the user_access would be the "data". The "data" would ultimately be rendered as a check box on a website with the appropriate row/column headings.
I'm also considering using a pure ajax/json solution where I will build the json string using pl sql and return the entire string to the client to be processed via jquery.
I'm concerned with the difficulty of the first option (i'm very new to pl sql, and I'm unsure of how to generate a pivot table to be used in this version of oracle (v 10) ) and I'm concerned with the expense of creating an entire json string that will contain so much data.
Any suggestions would be greatly appreciated.
EDIT
I've achieved the pivot table that I desired via the following sql:
SELECT
B.application_nm,
A.user_id,
MAX(DECODE(b.role_name, 'role 1', A.USER_ACCESS, NULL)) "role 1",
MAX(DECODE(b.role_name, 'role 2', A.USER_ACCESS, NULL)) "role 2",
MAX(DECODE(b.role_name, 'role 3', A.USER_ACCESS, NULL)) "role 3",
MAX(DECODE(b.role_name, 'role 4', A.USER_ACCESS, NULL)) "role 4",
MAX(DECODE(b.role_name, 'role 5', A.USER_ACCESS, NULL)) "role 5",
MAX(DECODE(b.role_name, 'role 6', A.USER_ACCESS, NULL)) "role 6"
FROM
vw_user_app_access A LEFT JOIN vw_tbl_app B ON A.app_id = B.app_id
LEFT JOIN vw_tbl_roles C ON A.role_id = C.role_id
GROUP BY B.application_name, A.user_id
ORDER BY A.user_id DESC
Only problem is when in the future we have to add 'role 7'. I have to then go back into this query and add the line MAX(DECODE(b.role_name, 'role 7', A.USER_ACCESS, NULL)) "role 7"
Thinking ahead, this may be an inconvenience, but considering APEX's framework, I would have to go into the report any way to update the number of columns manually i believe.
I'm thinking this may be the "best" solution for now, unless anyone has any other suggestions...

It is possible for an Apex report region based on a dynamic SQL query to return a different number of columns as the query changes. I have set up a simple demo on apex.oracle.com. Type a new column name into the Columns tabular form and press "Add Row", and the Matrix report is re-drawn with an extra column of that name.
You have to:
Base the report on a function that returns the SQL to be run as a string
Select the region attribute "Use Generic Column Names (parse query at runtime only)"
Set the report Headings Type to PL/SQL and then use a function to dynamically return the required column headings as a colon-separated list. Note that this can be different from the column name, although my example uses the same text for both.
If my example isn't clear enough I'll add more info later - I'm out of time now.

Related

How to query many-to-many relations based on logical expression with multiple parameters?

Considering the table with the following schema, I'm trying to create a dynamic query that accepts a logical expression with multiple variables, so I could filter employees that have skills only with specific technology or category names.
For example, I'm inputing pseudo expression (technology = 'aws cognito' OR technology = 'azure active directory') AND category = 'framework', so I know that the first two variables are technologies and the last one is a category of technologies.
How the possible query could look like, considering that the number of variables can differ and overall the expression may be multi-leveled? The way that the query will be generated is another question, but I'm having troubles with the actual structure of it.
I've tried querying a very simple case like that (schema name is skills):
SELECT *
FROM "skills"."employees" "e"
LEFT JOIN "skills"."skills" "s" ON "s"."employee_id"="e"."id"
LEFT JOIN "skills"."technologies" "t" ON "t"."id"="s"."technology_id"
WHERE ("t"."name" = 'aws cognito'
AND "t"."name" = 'azure active directory')
However, the query does not return any entries, but I have employees in my database that have skills with both of these technologies, so I thought that the query will return them. It works with the OR operator, but it was expected. I have a very bad feeling that I'm doing something fundamentally wrong, but, to be honest, I just cannot wrap my head around it.
Update: Sample data
Employees
id first_name
1 Andrew
2 James
Technologies
id name
1 aws cognito
2 azure active directory
Skills
id employee_id technology_id
1 1 1
2 1 2
3 2 1
Based on the data and on the logical expression technology = 'aws cognito' AND technology = 'azure active directory' I'm expecting the result to be only the employee with id 1 (Andrew).
For an arbitrary set of requried skills you can use a grouping and counting method
with skillset(tname) as(
values
('aws cognito'),
('azure active directory')
)
SELECT e.id, e.first_name
FROM "skills"."employees" e
JOIN "skills"."skills" s ON s.employee_id=e.id
JOIN "skills"."technologies" t ON t.id=s.technology_id
JOIN skillset st ON t.name = st.tname
GROUP BY e.id, e.first_name
HAVING count(*) = (select count(*) from skillset)

How to groupby in subquery SQL

I have the following query:
SELECT "IMPORTACIONCOLUMN3", "role", COUNT("IMPORTACIONCOLUMN1")
FROM MYTABLE
WHERE ID = 9
GROUP BY "IMPORTACIONCOLUMN3", "role"
This gives me the following result:
I would like to achieve the following:
The "unique" values are grouped together (i.e. Instead of having 4 values of "Robot 1" these are grouped togehter in just 1 cell summing the count values.
The second group by or subquery has to be the same count, but with role instead of "IMPORTACIONCOLUM3"
Is it possible (for the second picture) to "link" the values either by index or adding an extra column to reference them (i.e. There's two "Solicitante" with a count value of "52" but it refers to "Robot 1" and other to "Solicitante" with count value of "58" links to "Robot 2"
The second image represent visually what I'm trying to explain.
I have been trying on my own but only have reached the following:
select "IMPORTACIONCOLUMN3", count("IMPORTACIONCOLUMN1")
from
(
select "IMPORTACIONCOLUMN1", count("role"), "IMPORTACIONCOLUMN3"
from MYTABLE
WHERE ID = 9
group by "IMPORTACIONCOLUMN1", "IMPORTACIONCOLUMN3"
) as tmp
group by "IMPORTACIONCOLUMN3"
But it is not yet the result I am looking for.
Thanks in advance for your help and tips!
EDIT:
Explaining my desired output in detail
Each one of "Robot 1, 2, 3" have roles such as "Solicitante", "Gerente", etc. with different values.
i.e. The first row "Humano" value "243" is the sum of "Agente de Compras - 95", "Gerente Financiero - 37", "Gerente Solicitante - 45", "Proovedor - 31", "Solicitante - 60".
I am linking these by the column "GRAFICOCOLUMNARECURSIVOID" with contains the index of whatever "Robot" these "roles" are from.
I would like to achieve a query containing subquerys that allows me to have this output.
Try this for question number 1:
SELECT "IMPORTACIONCOLUMN3", COUNT("IMPORTACIONCOLUMN1")
FROM MYTABLE
WHERE ID = 9
GROUP BY "IMPORTACIONCOLUMN3"
the problem is Role: Robot 1 have 4 roles
and this for question 2:
SELECT "role", COUNT("IMPORTACIONCOLUMN1")
FROM MYTABLE
WHERE ID = 9
GROUP BY "role"
Question 3, I don't understand what you are asking for. Please make an example.

How to include static field without data from a dataset to carry it?

I'm improving a report that currently uses a static table using the lookup function to fill its data from a few different datasets. We're pretty sure this is causing the report to take a lot longer to run, so I'm trying to use a table that uses column groups to achieve the same effect from a single dataset.
Here's what my query currently looks like. This functions exactly as I want it to as long as there's data.
Select CatName, CatCount, Category = 'Category 1', Sorting = 1
FROM
(Select CatName, Count(CatName) as CatCount FROM DataSet WHERE Parameters)
UNION
Select CatName, CatCount, Category = 'Category 2', Sorting = 2
FROM
(Select CatName, Count(CatName) as CatCount FROM DataSet WHERE Parameters)
When there are CatNames and CatCounts to pull from the select statement, the Category works and is pulled by the table as a column group. I need all of the groups to exist at all times.
However, sometimes we don't have data that fits the parameters for a category. The result when that happens is that there isn't a row for the Category field to use and that group doesn't exist in the table. Is there any way I can force the Category field to exist regardless of the data?
If I understand the question correctly, then you may be able to use ISNULL. ISNULL returns either the value you were for which you were looking (check_expression) or the alternative (replacement_value) if check_expression is NULL.
ISNULL ( check_expression , replacement_value )
Select CatName, CatCount, Category = 'Category 2', Sorting = 2
FROM
(Select isnull(CatName,""), Count(CatName) as CatCount FROM DataSet WHERE Parameters)
EDIT
How about a left outer join?
Select b.CatName, b.CatCount, Category = 'Category 2', Sorting = 2
FROM
(select '' as CatName, 0 as Catcount) a left outer join (Select CatName, Count(CatName) as CatCount FROM DataSet WHERE Parameters) b on a.CatName = b.CatName
Found a solution. Took a few tries, not the prettiest, and we'll have to see if it actually improves performance, but it works the way we wanted. Generalized code:
Select C.CatName, C.CatCount, Category = 'Category 1', Sorting = 1
FROM
(Select Top 5 B.CatName, Count(B.CatName) as CatCount
FROM
(Select CatName = case when CatOnlyParam in (Category1Filter) then A.CatName else NULL end
FROM
(Select CatName FROM DataSet WHERE GeneralParameters) as A
) as B
order by CatCount
) as C
UNION
etc
Separating the parameters into different steps guarantees that there will be values for each category, even if those values are NULL. I'm sure there's a cleaner way to get the same effect, but this functions.
Working from the inside out:
Stage 1 (Select statement A): Selects the value from the dataset with very general parameters (between a start and end date, resolved or not, etc).
Stage 2 (Select statement B): Uses the case statement to only pull the data that is relevant for this department while leaving behind NULLs for the data that isn't.
Stage 3 (Select statement C): Takes the data from the list of names and NULLs and gets a count from it. Sorts by that count and takes the top 5. If a category has no data, then the nulls will get "counted" to 0 and passed on to the final step.
Stage 4 (Final select statement): Adds the static fields to the information from the previous step. A category without data will get passed to this as:
CatName: NULL
CatCount: 0
Category: "Category 1"
Sorting: 1
Then this is repeated for the other categories and UNION'd together. Any suggestions to improve this are more than welcome.

Combine Columns with similar measures in Crosstab in Cognos/SQL

So I have a crosstab that measures the amount of hours spent at a single facility. Our rows seperate the type of task for the hours worked, and the columns seperate the facility where the hours were worked.
The problem I have is that in our database we have 2 levels of facilities. Each Level 1 Facility has ten Level 2 "child" systems below it. What we want is to be able to roll all of the Level 2 facility columns' data into their respective Level 1 facility parent.
I've included an example below. The first t̶a̶b̶l̶e̶ crosstab is the one I have, and I want to get to the second t̶a̶b̶l̶e̶ crosstab.
So in our system the "parent" and "child" facilities are connected through a field called OBJ_PARENT. Each of the "children" have the name of its "parent" inside OBJ_PARENT.
I will give the SQL I have written up upon request.
EDIT: I've provided my current SQL below
select EVT_WORKORDER, EVT_DEPARTMENT, EVT_WOSTATUS, EVT_WOTYPE, EVT_FACILITY, OBJ_FACILITY, OBJ_PMD, OBJ_PARENT, BOO_HOURS, BOO_DATE, BOO_ENTERED, BOO_ACTIVITY, BOO_PERSON, ACT_ACTIVITY, ACT_TASK, ACT_WORKORDER, PRV_CODE, PRV_PROPERTY, PRV_VALUE,
sum(case when boo_person <> 'UNPAID' then boo_hours else 0 end ) "Paid Hours",
sum( boo_hours ) "All Hours"
FROM R5EVENTS
JOIN R5OBJECTS ON EVT_FACILITY = OBJ_FACILITY
JOIN R5BOOKEDHOURS ON BOO_EVENT = EVT_WORKORDER
join R5ACTIVITIES on EVT_WORKORDER = ACT_WORKORDER
JOIN r5propertyvalues ON ( ACT_TASK || '#0' = PRV_CODE)
where prv_property = 'CORESRV'
and ACT_ACTIVITY= BOO_ACTIVITY
AND EVT_DEPARTMENT = 'PK-MAINT'
AND EVT_WOTYPE IN ('JOB', 'PPM')
AND EVT_WOSTATUS IN ('R', 'FC', 'C', 'H', 'FI', 'CI', 'AP', 'IP', 'DF')
and OBJ_PMD in (#PROMPTMANY ('PMD')#)
and (OBJ_FACILITY in (#PROMPTMANY ('Park')#)
OR
OBJ_PARENT in ( SELECT OBJ_FACILITY from R5OBJECTS where OBJ_FACILITY in (#PROMPTMANY ('Park')#)))
group by EVT_WORKORDER, EVT_DEPARTMENT, EVT_WOSTATUS, EVT_WOTYPE, EVT_FACILITY, OBJ_FACILITY, OBJ_PMD, OBJ_PARENT, BOO_HOURS, BOO_DATE, BOO_ENTERED, BOO_ACTIVITY, BOO_PERSON, ACT_ACTIVITY, ACT_TASK, ACT_WORKORDER, PRV_CODE, PRV_PROPERTY, PRV_VALUE
BOO stands for "Booked"
The proper way would be:
Create a true hierarchy table in the database
Have all of the children (which, in this case with the way your data is stored, also includes what you refer to as Level 1) relate to a parent
Model in FM so that the fact table joins to the dimension table on the child
Expose the parent object from the hierarchy table
Bring the parent object into the top edge of the crosstab
If you want to do it quick and dirty, use some sort of string function to chop off the "child" part of the facility.

Comparing outputs from 2 separate sql queries

I am trying to write a query for an inventory system. In order to do this I have to count the number of duplicates in one table and then compare it to the default quantity values taken from another table.
Here are two queries which I am working with currently:
SELECT Template_ID,
COUNT(Template_ID) AS Howmuch, t.name
FROM consumables e, templates t
Where t.consumable_type_id = '2410980'
GROUP BY template_id, t.name
HAVING ( COUNT(Template_ID) > 1 )
The query above takes account of each unique Template Id and gives me a count of how many duplicates are present which tells me the amount of a single substance.
Select
property_descriptors.default_value,
templates.name
From
templates,
Property_descriptors
Where
templates.consumable_type_id = '858190' And
templates.audit_id = property_descriptors.audit_id And
property_descriptors.name = 'Reorder Point'
This query finds the amount of each individual substance we would like to have in our system.
My Issue is that I dont know of a way to compare the results from the 2 queries.
Ideally, I want the query to only give the substance which have a duplicate count lower than their default value (found using query 2).
any ideas would be appreciated!
here is the table schema for reference:
Consumables
ID|Template_ID|
Templates
ID|Property_Descriptor_ID|Name|audit_id
Property_Descriptors
ID| Name|Default_Value|audit_id
Thanks!
SELECT q1.name, q2.default_value - q1.Howmuch FROM
(SELECT Template_ID, COUNT(Template_ID) AS Howmuch, t.name
FROM consumables e, templates t
Where t.consumable_type_id = '2410980'
GROUP BY template_id, t.name
HAVING ( COUNT(Template_ID) > 1 )) q1,
(SELECT property_descriptors.default_value default_value,
templates.name name
FROM
templates,
Property_descriptors
WHERE
templates.consumable_type_id = '858190' And
templates.audit_id = property_descriptors.audit_id And
property_descriptors.name = 'Reorder Point') q2
where q1.name = q2.name
should do the trick you'll need to clean up the result a bit to work away negative results
or add q2.default_value - q1.Howmuch > 0 in the outer WHERE clause