oracle sql parent child with for multiple columns - sql

I have a users table having two columns for Approval Hierarchy , Table structure is like below
User_ID Submit_to Approve_to
1 2 3
2 4 5
3 6 2
4 2 3
5 1 0
Data is just For example 0 Mention no Approver :
Submit to and Approve to both will be Approvers
I need a Query which can give the Approves details in sequence that who will be next approver for entry user have created .

Using CONNECT BY, you can connect the parent/child relationship within a table. The query below will show the approval path for each user.
Query
WITH
d (user_id, submit_to, approve_to)
AS
(SELECT 1, 2, 3 FROM DUAL
UNION ALL
SELECT 2, 4, 5 FROM DUAL
UNION ALL
SELECT 3, 6, 2 FROM DUAL
UNION ALL
SELECT 4, 2, 3 FROM DUAL
UNION ALL
SELECT 5, 1, 0 FROM DUAL)
SELECT d.user_id, LTRIM (SYS_CONNECT_BY_PATH (user_id, '<'), '<') AS approval_path
FROM d
START WITH approve_to = 0
CONNECT BY NOCYCLE PRIOR user_id = approve_to
ORDER BY user_id;
Result
In the APPROVAL_PATH column, the left most number is the USER_ID giving final approval and the right most number is the USER_ID initially submitting whatever needs to be approved.
USER_ID | APPROVAL_PATH
-----------------------
1 | 5<2<3<1
2 | 5<2
3 | 5<2<3
4 | 5<2<3<4
5 | 5

Related

How to modify Column Data in BigQuery Table

I've been trying to search online however, I am only able to see how to add, remove, change a column in a table. Basically, I need to go through an entire column of email addresses in BigQuery and add a 2nd email address in each of the rows.
ID|Name |email
1 |Name1|email1#address.com
2 |Name2|email2#address.com
3 |Name3|email3#address.com
4 |Name4|email4#address.com
5 |Name5|email5#address.com
6 |Name6|email6#address.com
What I am looking for is some script that will go through a column and add a 2nd emailadd with a "," in the middle so that it'll look like this:
ID|Name |email
1 |Name1|email1#address.com,secondemail#address.com
2 |Name2|email2#address.com,secondemail#address.com
3 |Name3|email3#address.com,secondemail#address.com
4 |Name4|email4#address.com,secondemail#address.com
5 |Name5|email5#address.com,secondemail#address.com
6 |Name6|email6#address.com,secondemail#address.com
While all of the beginning data remains intact. Please let me know if this is possible. Also the "secondemail#address.com" is just one email address it doesn't change per user. I just need this format for a business reason.
Below is for BigQuery Standard SQL
#standardSQL
SELECT * REPLACE(email || ',secondemail#address.com' AS email)
FROM `project.dataset.table`
You can test, play with above using sample data from your question, as in below example
#standardSQL
WITH `project.dataset.table` AS (
SELECT 1 id, 'Name1' name, 'email1#address.com' email UNION ALL
SELECT 2, 'Name2', 'email2#address.com' UNION ALL
SELECT 3, 'Name3', 'email3#address.com' UNION ALL
SELECT 4, 'Name4', 'email4#address.com' UNION ALL
SELECT 5, 'Name5', 'email5#address.com' UNION ALL
SELECT 6, 'Name6', 'email6#address.com'
)
SELECT * REPLACE(email || ',secondemail#address.com' AS email)
FROM `project.dataset.table`
with output
Row id name email
1 1 Name1 email1#address.com,secondemail#address.com
2 2 Name2 email2#address.com,secondemail#address.com
3 3 Name3 email3#address.com,secondemail#address.com
4 4 Name4 email4#address.com,secondemail#address.com
5 5 Name5 email5#address.com,secondemail#address.com
6 6 Name6 email6#address.com,secondemail#address.com

Oracle - Loop through hierarchised records

So, since I struggled to find an accurate title, I think a detailled shema will be much more understandable.
I have this table PROGRAM that I will reduce to 3 fields for simplicity:
ID |NAME |ID_ORIGINAL_PROGRAM
1 |name_1 |
2 |name_2 |1
3 |name_3 |1
4 |name_4 |2
5 |name_5 |3
6 |name_6 |
7 |name_7 |6
I'm trying to find a query that will allow me, with any ID as parameter to gather all the related programs to this id.
And I need to be able to send a parameter than does not necessarily has to be the "father" id of the hierarchy.
For example, if parameter ID is 1, then results will be:
ID
2
3
4
5
If parameter ID is 4, then the results will be:
ID
1
2
3
5
It seems like I'm missing some kind "loop" logic that I can't clearly identify.
I looked up at "CONNECT BY PRIOR" but was not able to grasp the concept enough to understand how to deploy it.
Edit:
So it seems I found a way through:
SELECT ID
FROM PROGRAM
START WITH ID = 67256
CONNECT BY NOCYCLE ID_ORIGINAL_PROGRAM = PRIOR ID
OR ID = PRIOR ID_ORIGINAL_PROGRAM
order by ID
I'm a bit concerned by the performances though (it takes 1 second to perform)
I suppose you need
with program( id, id_original_program ) as
(
select 1, null from dual union all
select 2, 1 from dual union all
select 3, 1 from dual union all
select 4, 2 from dual union all
select 5, 3 from dual union all
select 6, null from dual union all
select 7, 6 from dual
)
select id, sys_connect_by_path(id, '->') as path
from program
where id_original_program is not null
connect by prior id = id_original_program
start with id = 1 -- 4
order by id;
ID PATH
2 ->1->2
3 ->1->3
4 ->1->2->4
5 ->1->3->5
if value 4 is substituted, then you get
ID PATH
4 ->4
only.
Whether you substitute 1 or 4, you'll get the same result for your query.

Oracle , select relation

I have 2 tables in oracle DB: Items and Relationship.
items:
ID
---
1
2
3
4
5
6
7
relationship:
ID parent child
--------------------
1 1 2
2 1 3
3 1 4
4 2 5
5 2 6
6 3 7
In the relationship table, I'm storing the hierarchial structure of the "items" (do not ask why it's stored in different tables).
The question:
When I execute this query:
SELECT PARENT_ID, CHILD_ID, CONNECT_BY_ISLEAF, MAX(LEVEL) OVER () + 1 - LEVEL as rev_level
FROM relationship
CONNECT BY PRIOR PARENT_ID = CHILD_ID
START WITH CHILD_ID = 7;
I do not see the root parent because he doesn't exist in this table as a child.
The question is how can I add the root parent(ID = 1) to the query result or join it whith the "items" table and keep the result columns (level and isleaf).
CONNECT_BY_ISLEAF works the other way around when using bottom up search (see this link http://technology.amis.nl/2009/11/14/oracle-11gr2-alternative-for-connect_by_isleaf-function-for-recursive-subquery-factoring-dedicated-to-anton/)
I assume that you want to show more data about the item (like name) If that is the case just left join the items table.
SELECT PARENT_ID AS PARENT_ID,CHILD_ID, i.name AS CHILD_NAME,
CONNECT_BY_ISLEAF,
MAX(LEVEL) OVER () + 1 - LEVEL AS rev_level
FROM items i
LEFT JOIN relationship r ON (i.id = r.CHILD_ID)
CONNECT BY PRIOR PARENT_ID = CHILD_ID
START WITH CHILD_ID = 7
ORDER BY REV_LEVEL;
check this SQLfiddle: http://sqlfiddle.com/#!4/5c9fa/17
In addition check this post about bottom up searches (http://bitbach.wordpress.com/2010/10/18/implementing-bottom-up-path-traversal-for-hierarchical-tables/)
Notice that you have both directions - parent and child.
pick one and dont mix the two.
1 with x as (
2 select 1 as id, 1 as parent, 2 as child from dual union all
3 select 2, 1 , 3 from dual union all
4 select 3 ,1, 4 from dual union all
5 select 4 ,2, 5 from dual union all
6 select 5 ,2, 6 from dual union all
7 select 6 ,3, 7 from dual)
8 select *
9 from x
10 sTART WITH child = 7
11* CONNECT BY PRIOR id= CHILD
SQL> /
ID PARENT CHILD
---------- ---------- ----------
6 3 7
5 2 6
4 2 5
3 1 4
2 1 3
1 1 2
connection is made by prior id = child and not prior parent = child

Select to build groups by (analytic) sum

Please help me to build a sql select to assign (software development) tasks to a software release. Actually this is a fictive example to solve my real business specific problem.
I have a relation Tasks:
ID Effort_In_Days
3 3
1 2
6 2
2 1
4 1
5 1
I want to distribute the Tasks to releases which are at most 2 days long (tasks longer than 2 shall still be put into one release). In my real problem I have much more "days" available to distribute "tasks" to. Expected output:
Release Task_ID
1 3
2 1
3 6
4 2
4 4
5 5
I think I need to use analytic functions, something with sum(effort_in_days) over and so on, to get the result. But I'm I haven't used analytic functions much and didn't find an example that's close enough to my specific problem. I need to build groups (releases) if a sum (>= 2) is reached.
I would do something like:
with data as (
select 3 ID, 3 Effort_In_Days from dual union all
select 1 ID, 2 Effort_In_Days from dual union all
select 6 ID, 2 Effort_In_Days from dual union all
select 2 ID, 1 Effort_In_Days from dual union all
select 4 ID, 1 Effort_In_Days from dual union all
select 5 ID, 1 Effort_In_Days from dual
)
select id, effort_in_days, tmp, ceil(tmp/2) release
from (
select id, effort_in_days, sum(least(effort_in_days, 2)) over (order by effort_in_days desc rows unbounded preceding) tmp
from data
);
Which results in:
ID EFFORT_IN_DAYS TMP RELEASE
---------- -------------- ---------- ----------
3 3 2 1
1 2 4 2
6 2 6 3
2 1 7 4
4 1 8 4
5 1 9 5
Basically, I am using least() to convert everything over 2 down to 2. Then I am putting all rows in descending order by that value and starting to assign releases. Since they are in descending order with a max value of 2, I know I need to assign a new release every time when I get to a multiple of 2.
Note that if you had fractional values, you could end up with releases that do not have a full 2 days assigned (as opposed to having over 2 days assigned), which may or may not meet your needs.
Also note that I am only showing all columns in my output to make it easier to see what the code is actually doing.
This is an example of a bin-packing problem (see here). There is not an optimal solution in SQL, that I am aware of, except in some boundary cases. For instance, if all the tasks have the same length or if all the tasks are >= 2, then there is an easy-to-find optimal solution.
A greedy algorithm works pretty well. This is to put a given record in the first bin where it fits, probably going through the list in descending size order.
If your problem is really as you state it, then the greedy algorithm will work to produce an optimal solution. That is, if the maximum value is 2 and the efforts are integers. There might even be a way to calculate the solution in SQL in this case.
Otherwise, you will need pl/sql code to achieve an approximate solution.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE data AS
select 3 ID, 3 Effort_In_Days from dual union all
select 1 ID, 2 Effort_In_Days from dual union all
select 6 ID, 2 Effort_In_Days from dual union all
select 2 ID, 1 Effort_In_Days from dual union all
select 4 ID, 1 Effort_In_Days from dual union all
select 5 ID, 1 Effort_In_Days from dual union all
select 9 ID, 2 Effort_In_Days from dual union all
select 7 ID, 1 Effort_In_Days from dual union all
select 8 ID, 1 Effort_In_Days from dual;
Query 1:
Give the rows an index so that they can be kept in order easily;
Assign groups to the rows where the Effort_In_Days is 1 so that all adjacent rows with Effort_In_Days of 1 are in the same group and rows separated by higher values for Effort_In_Days are in different groups;
Assign a cost of 1 to each row where the Effort_In_Days is higher than 1 or where Effort_In_Days is 1 and the row has an odd row number within the group; then
Finally, the release is the sum of all the costs for the row and all preceding rows.
Like this:
WITH indexes AS (
SELECT ID,
Effort_In_Days,
ROWNUM AS idx
FROM Data
),
groups AS (
SELECT ID,
Effort_In_Days,
idx,
CASE Effort_In_Days
WHEN 1
THEN idx - ROW_NUMBER() OVER ( PARTITION BY Effort_In_Days ORDER BY idx )
END AS grp
FROM indexes
ORDER BY idx
),
costs AS (
SELECT ID,
Effort_In_Days,
idx,
CASE Effort_In_Days
WHEN 1
THEN MOD( ROW_NUMBER() OVER ( PARTITION BY grp ORDER BY idx ), 2 )
ELSE 1
END AS cost
FROM groups
ORDER BY idx
)
SELECT ID,
Effort_In_Days,
SUM( cost ) OVER ( ORDER BY idx ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW ) AS Release
FROM costs
ORDER BY idx
Results:
| ID | EFFORT_IN_DAYS | RELEASE |
|----|----------------|---------|
| 3 | 3 | 1 |
| 1 | 2 | 2 |
| 6 | 2 | 3 |
| 2 | 1 | 4 |
| 4 | 1 | 4 |
| 5 | 1 | 5 |
| 9 | 2 | 6 |
| 7 | 1 | 7 |
| 8 | 1 | 7 |

Counting number of children in hierarchical SQL data

for a simple data structure such as so:
ID parentID Text Price
1 Root
2 1 Flowers
3 1 Electro
4 2 Rose 10
5 2 Violet 5
6 4 Red Rose 12
7 3 Television 100
8 3 Radio 70
9 8 Webradio 90
For reference, the hierarchy tree looks like this:
ID Text Price
1 Root
|2 Flowers
|-4 Rose 10
| |-6 Red Rose 12
|-5 Violet 5
|3 Electro
|-7 Television 100
|-8 Radio 70
|-9 Webradio 90
I'd like to count the number of children per level. So I would get a new column "NoOfChildren" like so:
ID parentID Text Price NoOfChildren
1 Root 8
2 1 Flowers 3
3 1 Electro 3
4 2 Rose 10 1
5 2 Violet 5 0
6 4 Red Rose 12 0
7 3 Television 100 0
8 3 Radio 70 1
9 8 Webradio 90 0
I read a few things about hierarchical data, but I somehow get stuck on the multiple inner joins on the parentIDs. Maybe someone could help me out here.
Using a CTE would get you what you want.
Recursively go through all children, remembering the root.
COUNT the items for each root.
JOIN these again with your original table to produce the results.
Test Data
DECLARE #Data TABLE (
ID INTEGER PRIMARY KEY
, ParentID INTEGER
, Text VARCHAR(32)
, Price INTEGER
)
INSERT INTO #Data
SELECT 1, Null, 'Root', NULL
UNION ALL SELECT 2, 1, 'Flowers', NULL
UNION ALL SELECT 3, 1, 'Electro', NULL
UNION ALL SELECT 4, 2, 'Rose', 10
UNION ALL SELECT 5, 2, 'Violet', 5
UNION ALL SELECT 6, 4, 'Red Rose', 12
UNION ALL SELECT 7, 3, 'Television', 100
UNION ALL SELECT 8, 3, 'Radio', 70
UNION ALL SELECT 9, 8, 'Webradio', 90
SQL Statement
;WITH ChildrenCTE AS (
SELECT RootID = ID, ID
FROM #Data
UNION ALL
SELECT cte.RootID, d.ID
FROM ChildrenCTE cte
INNER JOIN #Data d ON d.ParentID = cte.ID
)
SELECT d.ID, d.ParentID, d.Text, d.Price, cnt.Children
FROM #Data d
INNER JOIN (
SELECT ID = RootID, Children = COUNT(*) - 1
FROM ChildrenCTE
GROUP BY RootID
) cnt ON cnt.ID = d.ID
Consider using a modified preorder tree traversal way of storing the hierarchical data. See http://www.sitepoint.com/hierarchical-data-database/
Determining number of children for any node then becomes a simple:
SELECT (right-left-1) / 2 AS num_children FROM ...