Oracle query to extract distinct routes - sql

Burning out a brain cell... there has to be a simple way to do this.
I've inherited the following tables:
approval_path
approval_path_steps
applications
application_roles
requests
role_approvals
A user requests an application role for an application, which must go through an approval path, the steps of which are defined in approval_path_steps. The approval history for each step of the approval path is stored in role_approvals. So:
approval_path:
-> (p)approval_path_id
|
-------------------------
|
approval_path_steps: |
(p)approval_path_id --|
--> (p)sequence_nbr |
| approver |
| |
| |
| applications: |
| -> (p)application_id |
| | approval_path_id --
| |
| -------------------------
| |
| application_roles: |
| -> (p)role_id |
| | application_id ---
| |
| -------------------------
| |
| requests: |
| -> (p)request_nbr |
| | role_id ---
| | requestor
| |
| -------------------------
| |
| role_approvals: |
| (p)request_nbr ---
---- (p)sequence_nbr (NOT ACTUALLY KEYED!!! ENTERED MANUALLY!!)
approver
status
where (p) indicates the primary key. Fields not immediately relevant have been omitted. (btw, this was not my design)
The problem: Approval path steps have changed over time for a given approval path; steps have been added, removed, or changed from one approver to another. Therefore, the approval_path_steps that were actually taken for a request don't match the approval_path_steps that are currently defined for the requested role's approval_path.
What I need: I need to query the role_approvals table in such a way that I can list the distinct paths that were used. So:
role_approvals
--------------
1000 role1 1 manager approved
1000 role1 2 hr_mgr approved
1000 role1 3 app_owner approved
1001 role1 1 manager approved
1001 role1 2 hr_mgr approved
1001 role1 3 app_owner approved
1002 role1 1 app_owner approved
1002 role1 2 manager approved
The results I want:
id seq_nbr approver
-- ------- --------
1 1 manager
1 2 hr_mgr
1 3 app_owner
2 1 app_owner
2 2 manager
where 'id' can be calculated in some identifying way, it doesn't matter how, to identify that unique approval path that was taken.
Any ideas?
Thanks in advance!
James

This is only a partial solution. Sadly, I am getting ORA-600 errors when I try to build on this to convert it back into the original format. But at least it will get you the distinct paths.
Basically, it seems you need to aggregate the approver text field by the request number, and find distinct values of the aggregate. XML functions are the only (built-in) text aggregation method that I know of in Oracle 10g.
select
distinct xmlserialize(CONTENT approver_path AS VARCHAR2(2000)) distinct_path
from (
select
request_nbr,
xmlagg(xmlelement("Approver",approver) order by sequence_nbr) approver_path
from
role_approvals
group by
request_nbr
)

Related

PostgreSQL row-level security involving foreign key with other table

I wonder if the following is possible in PostgreSQL using RLS (or any other mechanism). I want a user to be able to get certain rows of a table if its id matches a column in another table.
For e.g. we have following tables:
"user" table:
columns: id, name
| id | name |
| --- | --- |
| 1 | one |
| 2 | two |
| 3 | three|
| 4 | four |
"tenant" table:
columns: id, name
| id | name |
| --- | --- |
| 1 | t1 |
| 2 | t2 |
"user_tenant" table:
columns: user_id, tenant_id
| user_id | tenant_id|
| --- | --- |
| 1 | t1 |
| 2 | t2 |
| 3 | t1 |
| 4 | t2 |
Now I want only users who has same tenant_id.
output:
| id | name |
| --- | --- |
| 1 | one |
| 3 | three|
To achieve this, I need to create policy something like this:
CREATE POLICY tenant_policy ON "user" USING (tenant_id = current_setting('my_user.current_tenant')::uuid);
but with above policy it's not working as I am getting all users.
Note: user & tenant table have many-to-many relationship.
P.S. I know we can do this either by join or some other condition. But I want to achieve the above output using PostgreSQL using RLS(row level security)
Thanks in advance!!
If row level security is not working that may be because one of the following applies:
you didn't enable row level security:
ALTER TABLE "user" ENABLE ROW LEVEL SECURITY;
the user owns the table
You can enable row level security for the owner with
ALTER TABLE "user" FORCE ROW LEVEL SECURITY;
you are a superuser, which is always exempt from RLS
you are a user defines with BYPASSRLS
the parameter row_security is set to off
Other than that, you will probably have to join with user_tenant in your policy:
CREATE POLICY tenant_policy ON "user"
USING (
EXISTS(SELECT 1 FROM user_tenant AS ut
WHERE ut.user_id = "user".id
AND ut.tenant_id = current_setting('my_user.current_tenant')::uuid
)
);

How to create column for selected parameter?

I have this query that pulls results based on the selected LOV parameter: nvl(:Role, role))
SELECT role,subject
FROM HS_SUBJECT_INCIDENTS
WHERE entitlement not in(select entitlement from HS_SUBJECT_INCIDENTS where role in nvl(:Role, role))
AND subject in (select subject from HS_SUBJECT_INCIDENTS where role in nvl(:Role, role))
So if the user selected 'Finance' for the parameter value, the results now show:
| Role | Subject |
------------------------
| Marketing | Business |
| Marketing | Business |
| Analytics | Business |
I want the results to show like this:
| Role | Subject | SelectedParameter |
--------------------------------------------
| Marketing | Business | Finance |
| Marketing | Business | Finance |
| Analytics | Business | Finance |
What do I have to put in the select statement to include a column for the parameter value that was selected?
Just select it:
SELECT role, subject, :Role as SelectedParameter
FROM . . .

SQLite3 select last event by user

I have the following table 'events'.
| id | event_type | by_user | asset | time |
| 1 | owner | a | 10 | 1111111111 |
| 2 | updated | b | 20 | 1111111112 |
| 3 | owner | a | 30 | 1111111113 |
| 4 | owner | c | 20 | 1111111114 |
| 5 | updated | a | 10 | 1111111115 |
| 6 | owner | a | 20 | 1111111118 |
I would like to select the assets where user 'a' was the last user
with an 'owner' event_type. So in this example the id's 1, 3 and 6 (the
assets 10, 20 and 30 are owned by user 'a').
Basically, based on the events, I want to find the assests owned by user 'a'.
This is what correlated subqueries are for:
SELECT * FROM events e
WHERE event_type='owner'
AND time=(SELECT MAX(e_inner.time) FROM events e_inner
WHERE e_inner.asset=e.asset AND e_inner.event_type='owner')
Will give you the event that is "for each asset, show the last ownership event". If you want it for specific assets or specific owners, just add an appropriate WHERE clause
Your question is ripe for breakage if you aren't guaranteeing uniqueness of {time, event_type, asset}. This will return all n rows if you have n users being assigned ownership at the exact same time.

Combining the datas of two columns with the same name to create a view

I am working on a project and for my login credentials checking process I am trying to create a view in which the name,surname,username and password of customers,workers and admins are stored so that I can search faster and I have two questions.
Do you think it is a good idea to do that ?
If yes, can you help me how to do that?
Thank you in advance.
1) yes, but for simplicity rather than performance (and a few other reasons)
2) CREATE OR REPLACE VIEW viewname AS your_select_statement;
If the front-end is a single interface for both customers and employees then the tables should not be separated in the first place. If you have a person who is both a customer and a worker then they would appear on two tables and it is possible the data would not be synchronized between the two and if you create a view then they would appear twice. Instead create a single table for all people and have separate tables for data specific to customers, workers and admins.
Something like:
People
id | firstname | surname | username | password_hash | password_salt
--------------------------------------------------------------------
1 | alice | abbot | aa | abc | 123
2 | bob | barnes | bb | def | 456
3 | charlotte | carol | cc | ghi | 789
4 | daniel | david | dd | jkl | 036
Customers
id | Credit_Limit | has_Trade_Account
-------------------------------------
2 | 0 | 0
3 | 2000 | 1
Workers
id | Joining_Date | Grade
--------------------------
1 | 2015-01-01 | 5
3 | 2000-12-25 | 3
Admins
id | Edit_Permissions
----------------------
3 | Orders
3 | Stock

How do you merge rows from 2 SQL tables without duplicating rows?

I guess this query is a little basic and I should know more about SQL but haven't done much with joins yet which I guess is the solution here.
What I have is a table of people and a table of job roles they hold. A person can have multiple jobs and I wish to have one set of results with a row per person containing their details and their job roles.
Two example tables (people and job_roles) are below so you can understand the question easier.
People
id | name | email_address | phone_number
1 | paul | paul#example.com | 123456
2 | bob | bob#example.com | 567891
3 | bart | bart#example.com | 987561
job_roles
id | person_id | job_title | department
1 | 1 | secretary | hr
2 | 1 | assistant | media
3 | 2 | manager | IT
4 | 3 | finance clerk | finance
4 | 3 | manager | IT
so that I can output each person and their roles like such
Name: paul
Email Address: paul#example.com
Phone: 123456
Job Roles:
Secretary for HR department
Assistant for media department
_______
Name: bob
Email address: bob#example.com
Phone: 567891
Job roles:
Manager for IT department
So how would I get each persons information (from the people table) along with their job details (from the job_roles table) to output like the example above. I guess it would be some kind of way of merging their jobs and their relevant departments into a jobs column that can be split up for output, but maybe there is a better way and what would the sql look like?
Thanks
Paul
PS it would be a mySQL database if that makes any difference
It looks like a straight-forward join:
SELECT p.*, j.*
FROM People AS p INNER JOIN Roles AS r ON p.id = r.person_id
ORDER BY p.name;
The remainder of the work is formatting; that's best done by a report package.
Thanks for the quick response, that seems a good start but you get multiple rows per person like (you have to imagine this is a table as you don't seem to be able to format in comments):
id | Name | email_address | phone_number | job_role | department
1 | paul | paul#example.com | 123456 | secretary | HR
1 | paul | paul#example.com | 123456 | assistant | media
2 | bob | bob#example.com | 567891 | manager | IT
I would like one row per person ideally with all their job roles in it if that's possible?
It depends on your DBMS, but most available ones do not support RVAs - relation-valued attributes. What you'd like is to have the job role and department part of the result like a table associated with the user:
+----+------+------------------+--------------+------------------------+
| id | Name | email_address | phone_number | dept_role |
+----+------+------------------+--------------+------------------------+
| | | | | +--------------------+ |
| | | | | | job_role | dept | |
| 1 | paul | paul#example.com | 123456 | | secretary | HR | |
| | | | | | assistant | media | |
| | | | | +--------------------+ |
+----+------+------------------+--------------+------------------------+
| | | | | +--------------------+ |
| | | | | | job_role | dept | |
| 2 | bob | bob#example.com | 567891 | | manager | IT | |
| | | | | +--------------------+ |
+----+------+------------------+--------------+------------------------+
This accurately represents the information you want, but is not usually an option.
So, what happens next depends on your report generation tool. Using the one I'm most familiar with, (Informix ACE, part of Informix SQL, available from IBM for use with the Informix DBMSs), you would simply ensure that the data is sorted and then print the name, email address and phone number in the 'BEFORE GROUP OF id' section of the report, and in the 'ON EVERY ROW' section you would process (print) just the role and department information.
It is often a good idea to separate the report formatting from the data retrieval operations; this is an example of where it is necessary unless your DBMS has unusual features to help with the formatting of selected data.
Oh dear that sounds very complicated and not something I could run easily on a mySQL database in a PHP page?
The RVA stuff - you're right, that is not for MySQL and PHP.
On the other hand, there are millions of reports (meaning results from queries that are formatted for presentation to a user) that do roughly this. The technical term for them is 'Control-Break Report', but the basic idea is not hard.
You keep a record of the 'id' number you last processed - you can initialize that to -1 or 0.
When the current record has a different id number from the previous number, then you have a new user and you need to start a new set of output lines for the new user and print the name, email address and phone number (and change the last processed id number). When the current record has the same id number, then all you do is process the job role and department information (not the name, email address and phone number). The 'break' occurs when the id number changes. With a single level of control-break, it is not hard; if you have 4 or 5 levels, you have to do more work, and that's why there are reporting packages to handle it.
So, it is not hard - it just requires a little care.
RE:
I was hoping SQL could do something
clever and join the rows together
nicely so I had essentially a jobs
column with that persons jobs in it.
You can get fairly close with
SELECT p.id, p.name, p.email_address, p.phone_number,
group_concat(concat(job_title, ' for ', department, ' department') SEPARATOR '\n') AS JobRoles
FROM People AS p
INNER JOIN job_roles AS r ON p.id = r.person_id
GROUP BY p.id, p.name, p.email_address, p.phone_number
ORDER BY p.name;
Doing it the way you're wanting would mean the result set arrays could have infinite columns, which would be very messy. for example, you could left join the jobs table 10 times and get job1, job2, .. job10.
I would do a single join, then use PHP to check if the name ID is the same from 1 row to the next.
One way might be to left outer join the tables and then load them up into an array using
$people_array =array();
while($row1=mysql_fetch_assoc($extract1)){
$people_array[] = $row1;
}
and then loop through using
for ($x=0;$x<=sizeof($people_array;)
{
echo $people_array[$x][id];
echo $people_array[$x][name];
for($y=0;$y<=$number_of_roles;$y++)
{
echo $people_array[$x][email_address];
echo $people_array[$x][phone_number];
$x++;
}
}
You might have to play with the query a bit and the loops but it should do generally what you want.For it to work as above every person would have to have the same number of roles, but you may be able to fill in the blanks in your table