I have the ff. query to show anyone under a Director's Hierarchy:
WITH RECURSIVE emptree AS (
SELECT e.win_id,
e.full_name,
e.current_sup_name,
e.sbu,
e.cost_center,
e.lob,
0 AS depth
FROM app_reports.vw_hiearchy e
WHERE e.win_id = xxxxxxx AND e.attrition_date IS NULL
UNION ALL
SELECT e.win_id,
e.full_name,
e.current_sup_name,
e.sbu,
e.cost_center,
e.lob,
t.depth + 1 AS depth
FROM app_reports.vw_hiearchy e
JOIN emptree t ON t.win_id = e.current_sup_win
WHERE e.attrition_date IS NULL
)
SELECT emptree.win_id,
emptree.full_name,
emptree.current_sup_name,
emptree.sbu,
emptree.cost_center,
emptree.lob,
emptree.depth
FROM emptree;
It works fine until I am requested to add another column (or more) to show who's the supervisor of a particular supervisor (technically adding columns dynamically - if possible, that shows all the supervisors from the bottom up). I'm not sure if it involves reversing this for me to actually get the hierarchy from the bottom-up and show it as current_sup_name_2, current_sup_name3, and so on. But I'm not sure how.
Thanks in Advance for any recommendations. :)
It should be possible to show the full hierarchy of supervisors inside a single field with a small modification to the existing query:
WITH RECURSIVE emptree AS (
SELECT e.win_id,
e.full_name,
e.current_sup_name,
e.sbu,
e.cost_center,
e.lob,
0 AS depth
FROM app_reports.vw_hiearchy e
WHERE e.win_id = xxxxxxx AND e.attrition_date IS NULL
UNION ALL
SELECT e.win_id,
e.full_name,
concat_ws(',', e.current_sup_name, t.current_sup_name) current_sup_name,
e.sbu,
e.cost_center,
e.lob,
t.depth + 1 AS depth
FROM app_reports.vw_hiearchy e
JOIN emptree t ON t.win_id = e.current_sup_win
WHERE e.attrition_date IS NULL
)
SELECT emptree.win_id,
emptree.full_name,
emptree.current_sup_name,
emptree.sbu,
emptree.cost_center,
emptree.lob,
emptree.depth
FROM emptree;
Related
Hello friendly internet wizards.
I am attempting to extract a levelled bill of materials (BOM) from a dataset, running in DB2 on an AS400 server.
I have constructed most of the query (with a lot of help from online resources), and this is what I have so far;
#set item = '10984'
WITH BOM (origin, PMPRNO, PMMTNO, BOM_Level, BOM_Path, IsCycle, IsLeaf) AS
(SELECT CONNECT_BY_ROOT PMPRNO AS origin, PMPRNO, PMMTNO,
LEVEL AS BOM_Level,
SYS_CONNECT_BY_PATH(TRIM(PMMTNO), ' : ') BOM_Path,
CONNECT_BY_ISCYCLE IsCycle,
CONNECT_BY_ISLEAF IsLeaf
FROM MPDMAT
WHERE PMCONO = 405 AND PMFACI = 'M01' AND PMSTRT = 'STD'
START WITH PMPRNO = :item
CONNECT BY NOCYCLE PRIOR PMMTNO = PMPRNO)
SELECT 0 AS BOM_Level, '' AS BOM_Path, MMITNO AS Part_Number, MMITDS AS Part_Name,
IFSUNO AS Supplier_Number, IDSUNM AS Supplier_Name, IFSITE AS Supplier_Part_Number
FROM MITMAS
LEFT OUTER JOIN MITVEN ON MMCONO = IFCONO AND MMITNO = IFITNO AND IFSUNO <> 'ZGA'
LEFT OUTER JOIN CIDMAS ON MMCONO = IDCONO AND IDSUNO = IFSUNO
WHERE MMCONO = 405
AND MMITNO = :item
UNION ALL
SELECT BOM.BOM_Level, BOM_Path, BOM.PMMTNO AS Part_Number, MMITDS AS Part_Name,
IFSUNO AS Supplier_Number, IDSUNM AS Supplier_Name, IFSITE AS Supplier_Part_Number
FROM BOM
LEFT OUTER JOIN MITMAS ON MMCONO = 405 AND MMITNO = BOM.PMMTNO
LEFT OUTER JOIN MITVEN ON IFCONO = MMCONO AND IFITNO = MMITNO AND IFSUNO <> 'ZGA' AND MMMABU = '2'
LEFT OUTER JOIN CIDMAS ON MMCONO = IDCONO AND IDSUNO = IFSUNO
;
This is correctly extracting the components for a given item, as well as the sub-components (etc).
Current data looks like this (I have stripped out some columns that aren't relevant to the issue);
https://pastebin.com/LUnGKRqH
My issue is the order that the data is being presented in.
As you can see in the pastebin above, the first column is the 'level' of the component. This starts with the parent item at level 0, and can theoretically go down as far as 99 levels.
The path is also show there, so for example the second component 853021 tells us that it's a 2nd level component, the paths up to INST363 (shown later in the list as a 1st level), then up to the parent at level 0.
I would like for the output to show in path order (for lack of a better term).
Therefore, after level 0, it should be showing the first level 1 component, and then immediately be going into it's level 2 components and so on, until no further level is found. Then at that point, it returns back up the path to the next valid record.
I hope I have explained that adequately, but essentially the data should come out as;
Level
Path
Item
0
10984
1
: INST363
INST363
2
: INST363 : 853021
853021
1
: 21907
21907
Any help that can be provided would be very much appreciated!
Thanks,
This is an interesting query. Frankly I am surprised it works as well as it does since it is not structured the way I usually structure queries with a recursive CTE. The main issue is that while you have the Union in there, it does not appear to be within the CTE portion of the query.
When I write a recursive CTE, it is generally structured like this:
with cte as (
priming select
union all
secondary select)
select * from cte
So to get a BOM from an Item Master that looks something like:
CREATE TABLE item (
ItemNo Char(10) PRIMARY KEY,
Description Char(50));
INSERT INTO item
VALUES ('Item0', 'Root Item'),
('Item1a', 'Second Level Item'),
('Item1b', 'Another Second Level Item'),
('Item2a', 'Third Level Item');
and a linkage table like this:
CREATE TABLE linkage (
PItem Char(10),
CItem Char(10),
Quantity Dec(5,0),
PRIMARY KEY (PItem, CItem));
INSERT INTO linkage
VALUES ('Item0', 'Item1a', 2),
('Item0', 'Item1b', 3),
('Item1b', 'Item2a', 5)
The recursive CTE to list a BOM for 'Item0' looks like this:
WITH bom (Level, ItemNo, Description, Quantity)
AS (
-- Load BOM with root item
SELECT 0,
ItemNo,
Description,
1
FROM Item
WHERE ItemNo = 'Item0'
UNION ALL
-- Retrieve all child items
SELECT a.Level + 1,
b.CItem,
c.Description,
a.Quantity * b.Quantity
FROM bom a
join linkage b ON b.pitem = a.itemno
join item c ON c.itemno = b.citem)
-- Set the list order
SEARCH DEPTH FIRST BY itemno SET seq
-- List BOM
SELECT * FROM bom
ORDER BY seq
Here are my results:
LEVEL
ITEMNO
DESCRIPTION
QUANTITY
0
Item0
Root Item
1
1
Item1a
Second Level Item
2
1
Item1b
Another Second Level Item
3
2
Item2a
Third Level Item
15
Notice the search clause, that generates a column named seq which you can use to sort the output either depth first or breadth first. Depth first is what you want here.
NOTE: This isn't necessarily an optimum query since the description is in the CTE, and that increases the size of the CTE result set without really adding anything to it that couldn't be added in the final select. But it does make things a bit simpler since the 'priming query' retrieves the description.
Note also: the column list on the with clause following BOM. This is there to remove the confusion that DB2 had with the expected column list when the explicit column list was omitted. It is not always necessary, but if DB2 complains about an invalid column list, this will fix it.
Let's say I am using materialized paths to store management chains:
Table: User
id name management_chain
1 Senior VP {1}
2 Middle Manager {1,2}
3 Cubicle Slave {1,2,3}
4 Janitor {1,2,4}
How do I construct a query given a user id that returns all of his direct reports, eg given the middle manager, it should return Cubicle Slave and Janitor, given the Senior VP it should return the Middle Manager. Put another way, what would be a good way to get all records where the management_chain contains the id queried for at a position that is second to last (given that the last item represent the user's own id).
In other words, how do I represent the following SQL:
SELECT *
FROM USER u
WHERE u.management_chain #> {stored_variable, u.id}
My current JS:
var collection = Users.forge()
.query('where', 'management_chain', '#>', [req.user.id, id]);
Which errors out with
ReferenceError: id is not defined
Assuming management_chain is an integer array (int[]) you could do the following (in plain SQL)
select *
from (
select id,
name,
'/'||array_to_string(management_chain, '/') as path
from users
) t
where path like '%/2/%';
This works, because array_to_string() will not append the delimiter to the end of the string. Therefore if a path contains the sequence /2/ it means there are more nodes "below" that one. The nodes where 2 is the last id in the management_chain will end with /2 (no trailing /) and will not be included in the result.
The expression will not make use of an index, so this might not be feasible for large tables.
However I don't know how this would translate into that JS thing.
SQLFiddle example: http://sqlfiddle.com/#!15/75948/2
Lookup WITH RECURSIVE
As an example take a look a this code:
CREATE VIEW
mvw_pre_import_cellpath_check
(
pkid_cell,
id_cell ,
id_parent,
has_child,
id_path ,
name_path,
string_path
) AS WITH RECURSIVE cell_paths
(
pkid_cell,
id_cell ,
id_parent,
id_path ,
name_path
) AS
(
SELECT
tbl_cell.pkid ,
tbl_cell.cell_id ,
tbl_cell.cell_parent_id ,
ARRAY[tbl_cell.cell_id] AS "array",
ARRAY[tbl_cell.cell_name] AS "array"
FROM
ufo.tbl_cell
WHERE
(((
tbl_cell.cell_parent_id IS NULL)
AND (
tbl_cell.reject_reason IS NULL))
AND (
tbl_cell.processed_dt IS NULL))
UNION ALL
SELECT
tbl_cell.pkid ,
tbl_cell.cell_id ,
tbl_cell.cell_parent_id ,
(cell_paths_1.id_path || tbl_cell.cell_id),
(cell_paths_1.name_path || tbl_cell.cell_name)
FROM
(cell_paths cell_paths_1
JOIN
ufo.tbl_cell
ON
((
tbl_cell.cell_parent_id = cell_paths_1.id_cell)))
WHERE
(((
NOT (
tbl_cell.cell_id = ANY (cell_paths_1.id_path)))
AND (
tbl_cell.reject_reason IS NULL))
AND (
tbl_cell.processed_dt IS NULL))
)
SELECT
cell_paths.pkid_cell,
cell_paths.id_cell ,
cell_paths.id_parent,
(
SELECT
COUNT(*) AS COUNT
FROM
ufo.tbl_cell x
WHERE
((
cell_paths.id_cell = x.cell_id)
AND (
EXISTS
(
SELECT
1
FROM
ufo.tbl_cell y
WHERE
(
x.cell_id = y.cell_parent_id))))) AS has_child,
cell_paths.id_path ,
cell_paths.name_path ,
array_to_string(cell_paths.name_path, ' -> '::text) AS string_path
FROM
cell_paths
ORDER BY
cell_paths.id_path;
There are plenty more examples to find on SO when looking for recursive CTE.
But in contrary with your example the top level cells (managers) have parent_id = NULL in my example. These are the starting points for the different branches.
HTH
Select *
from IWIN..VW_EMPMASTER E
where E.em_teamcd = 'C0015'
gives me:
C3892
C6331
which is list of employees which come immediately under C0015
Select *
from IWIN..VW_EMPMASTER E
where E.em_teamcd = 'C3892'
gives me list of all 14 employees which come immediately under C3892
I want to get list of ALL employees which come under 'em_empid' C0015 directly or indirectly.. and not just employees which come directly under him. How can I do so ?
Maximum level of hierarchy is 5.
Assuming em_teamcdis the identifier for the manager and em_empididentifies employees (including managers) you can use a recursive query using a common table expression, I adapted this from an example in the documentation:
WITH Tree (em_empid, em_teamcd, level)
AS
(
-- Anchor member definition
SELECT e.em_empid, e.em_teamcd, 0 AS Level
FROM dbo.VW_EMPMASTER AS e
WHERE em_teamcd = 'C0015'
UNION ALL
-- Recursive member definition
SELECT e.em_empid, e.em_teamcd, Level + 1
FROM dbo.VW_EMPMASTER AS e
INNER JOIN Tree AS d ON e.em_teamcd = d.em_empid
)
-- Statement that executes the CTE
SELECT * FROM Tree WHERE Level <= 5
The column em_empidwill hold all employees that has C0015above them.
A sample SQL Fiddle with some fake data.
I have a little problem with this recursive CTE, it works fine except when I have a user without root readable rights means no entry for this element. So if I run this query on a user with rights just on the leaves inside the tree the level part of this query won't work correctly.
It will show the real level hierarchy for example 6 but its the top first readable element for him so it should be 1.
WITH Tree
AS (
SELECT
id,
parent,
0 AS Level,
id AS Root,
CAST(id AS VARCHAR(MAX)) AS Sort,
user_id
FROM SourceTable
WHERE parent IS NULL
UNION ALL
SELECT
st.id,
st.parent,
Level + 1 AS Level,
st.parent AS Root,
uh.sort + '/' + CAST(st.id AS VARCHAR(20)) AS Sort,
st.user_id
FROM SourceTable AS st
JOIN Tree uh ON uh.id = st.parent
)
SELECT * FROM Tree AS t
JOIN UserTable AS ut ON ut.id = t.user_id AND ut.user_id = '141F-4BC6-8934'
ORDER BY Sort
the level is as follows
id level
5 0
2 1
7 2
4 2
1 2
6 1
3 2
8 2
9 3
When a user now just have read rights to id 8 and 9 the level from CTE stays at 2 for id 8 and 3 for id 9 but I need for id 8 level 1 if there is no one before
You haven't told us how you know whether a user has rights to a given id. That is a necessary piece of information. I'm going to put some code below that assumes you add a column to your query called hasRights and that this column will have a zero value if the user does not have rights and a value of one if they do. You may need to tweak this, since I have no data to test with but hopefully it will get you close.
Basically, the query is altered to only add 1 to the level if the user has rights. It also only adds to the sort path if the user has rights, otherwise an empty string is appended. So, if ids 8 and 9 are the only items the user has access to, you should see levels of 1 and 2 and sort paths similar to '5/8/9' rather than '5/6/8/9'. If you still aren't able to get it working, it would help us tremendously if you posted a sample schema on SqlFiddle.
WITH Tree
AS (
SELECT
id,
parent,
0 AS Level,
id AS Root,
hasRights AS HasRights,
CAST(id AS VARCHAR(MAX)) AS Sort,
user_id
FROM SourceTable
WHERE parent IS NULL
UNION ALL
SELECT
st.id,
st.parent,
Level + st.hasRights AS Level,
st.parent AS Root,
st.hasRights AS HasRights,
uh.sort + CASE st.hasRights WHEN 0 THEN '' ELSE '/' + CAST(st.id AS VARCHAR(20)) END AS Sort,
st.user_id
FROM SourceTable AS st
JOIN Tree uh ON uh.id = st.parent
)
SELECT * FROM Tree AS t
JOIN UserTable AS ut ON ut.id = t.user_id AND ut.user_id = '141F-4BC6-8934'
ORDER BY Sort
You requite something like if the higher level(0 or 1 ) is not in existence, then the next level become the higher level..
If yes, then you have to do this when the final result
insert all the results in temp table lets say #info (with same characteristics of data)
Now after all final data ready in the table,
Please check from the top.
Select * from #info where level= 0
if this returns 0 rows then you have to update each records level. to (level = level -1)
Now again same for Level=0, then level 1, then level 2 , then level 3 in recursion. this will be easy but not easy to code. So try without recursion then try final update.
I hope this will help :)
Please reply if you are looking for something else.
Try to perform the following select and let me know if it is your desired result:
SELECT *,
DENSE_RANK() OVER (PARTITION BY t.user_id ORDER BY t.LEVEL ASC) -1 as RelativeUserLevel
FROM Tree AS t
JOIN UserTable AS ut ON ut.id = t.user_id AND ut.user_id = '141F-4BC6-8934'
ORDER BY Sort
Is conversion of your table into hierarchical types an option:
hierarchyid data type (http://technet.microsoft.com/en-us/library/bb677290(v=sql.105).aspx)
or
xml data type
SOME TIMES option (maxrecursion 10000); is very useful
I don't have time to read your problem but here snipped
declare cursorSplit Cursor for
select String from dbo.SplitN(#OpenText,'~')
where String not in (SELECT [tagCloudStopWordText]
FROM [tagCloudStopList] where [langID]=#_langID)
option (maxrecursion 10000);
open cursorSplit
I'm sorry to be a party pooper and spoil the fun of creating such an interesting piece of SQL, but perhaps you should load all relevant access data into your application and determine the user levels in the application?
I bet it would result in more maintainable code..
I am working on an application that has to assign numeric codes to elements. This codes are not consecutives and my idea is not to insert them in the data base until have the related element, but i would like to find, in a sql matter, the not assigned codes and i dont know how to do it.
Any ideas?
Thanks!!!
Edit 1
The table can be so simple:
code | element
-----------------
3 | three
7 | seven
2 | two
And I would like something like this: 1, 4, 5, 6. Without any other table.
Edit 2
Thanks for the feedback, your answers have been very helpful.
This will return NULL if a code is not assigned:
SELECT assigned_codes.code
FROM codes
LEFT JOIN
assigned_codes
ON assigned_codes.code = codes.code
WHERE codes.code = #code
This will return all non-assigned codes:
SELECT codes.code
FROM codes
LEFT JOIN
assigned_codes
ON assigned_codes.code = codes.code
WHERE assigned_codes.code IS NULL
There is no pure SQL way to do exactly the thing you want.
In Oracle, you can do the following:
SELECT lvl
FROM (
SELECT level AS lvl
FROM dual
CONNECT BY
level <=
(
SELECT MAX(code)
FROM elements
)
)
LEFT OUTER JOIN
elements
ON code = lvl
WHERE code IS NULL
In PostgreSQL, you can do the following:
SELECT lvl
FROM generate_series(
1,
(
SELECT MAX(code)
FROM elements
)) lvl
LEFT OUTER JOIN
elements
ON code = lvl
WHERE code IS NULL
Contrary to the assertion that this cannot be done using pure SQL, here is a counter example showing how it can be done. (Note that I didn't say it was easy - it is, however, possible.) Assume the table's name is value_list with columns code and value as shown in the edits (why does everyone forget to include the table name in the question?):
SELECT b.bottom, t.top
FROM (SELECT l1.code - 1 AS top
FROM value_list l1
WHERE NOT EXISTS (SELECT * FROM value_list l2
WHERE l2.code = l1.code - 1)) AS t,
(SELECT l1.code + 1 AS bottom
FROM value_list l1
WHERE NOT EXISTS (SELECT * FROM value_list l2
WHERE l2.code = l1.code + 1)) AS b
WHERE b.bottom <= t.top
AND NOT EXISTS (SELECT * FROM value_list l2
WHERE l2.code >= b.bottom AND l2.code <= t.top);
The two parallel queries in the from clause generate values that are respectively at the top and bottom of a gap in the range of values in the table. The cross-product of these two lists is then restricted so that the bottom is not greater than the top, and such that there is no value in the original list in between the bottom and top.
On the sample data, this produces the range 4-6. When I added an extra row (9, 'nine'), it also generated the range 8-8. Clearly, you also have two other possible ranges for a suitable definition of 'infinity':
-infinity .. MIN(code)-1
MAX(code)+1 .. +infinity
Note that:
If you are using this routinely, there will generally not be many gaps in your lists.
Gaps can only appear when you delete rows from the table (or you ignore the ranges returned by this query or its relatives when inserting data).
It is usually a bad idea to reuse identifiers, so in fact this effort is probably misguided.
However, if you want to do it, here is one way to do so.
This the same idea which Quassnoi has published.
I just linked all ideas together in T-SQL like code.
DECLARE
series #table(n int)
DECLARE
max_n int,
i int
SET i = 1
-- max value in elements table
SELECT
max_n = (SELECT MAX(code) FROM elements)
-- fill #series table with numbers from 1 to n
WHILE i < max_n BEGIN
INSERT INTO #series (n) VALUES (i)
SET i = i + 1
END
-- unassigned codes -- these without pair in elements table
SELECT
n
FROM
#series AS series
LEFT JOIN
elements
ON
elements.code = series.n
WHERE
elements.code IS NULL
EDIT:
This is, of course, not ideal solution. If you have a lot of elements or check for non-existing code often this could cause performance issues.