Oracle SQL query to count "children" in current query set - sql

I have got an SQL query in Oracle with a multilevel subquery for generating my website navigation in the database. This query has a multilevel subquery because for each user I have to check whether they have the right to access this part of the navigation. The result looks kind of like the following:
ID | ID_PARENT | NAME | LINK
------------------------------------------
1 Main ~/
2 1 Sub1 ~/Sub1
3 1 Sub2 ~/Sub2
4 2 Sub1.1 ~/Sub1.1
5 2 Sub1.2 ~/Sub1.2
6 2 Sub1.3 ~/Sub1.3
The ID_PARENT column refers to the ID column of another row in the same table.
Now what I need is a query that, for each row, gives me the amount of rows in the current query set (because there exist other navigation entries that some users do not have the rights to, and I want to avoid making the same subquery twice) that have the current ID as ID_PARENT, so basically counts the children. With the example above the result I need should look like the following:
ID | ID_PARENT | NAME | LINK | CHILDREN
---------------------------------------------------------
1 Main ~/ 2
2 1 Sub1 ~/Sub1 3
3 1 Sub2 ~/Sub2 0
4 2 Sub1.1 ~/Sub1.1 0
5 2 Sub1.2 ~/Sub1.2 0
6 2 Sub1.3 ~/Sub1.3 0
I have tried a fair share of SQL queries, but none of them get me the result I need. Can anybody help me with this?

You can count() separately the record for your ID_PARENT and then join it with your main query. Something like this:
SELECT A.*, COALESCE(B.RC ,0) AS CHILDREN_NUMBER
FROM YOURTABLE A
LEFT JOIN ( SELECT ID_PARENT,COUNT(*) AS RC FROM YOURTABLE GROUP BY ID_PARENT) B ON A.ID = B.ID_PARENT;
Ouput:
ID ID_PARENT NAME LINK CHILDREN_NUMBER
1 NULL Main / 2
2 1 SUB1 /Sub1 3
3 1 SUB2 /Sub2 0
4 2 SUB1.1 /Sub1.1 0
5 2 SUB1.2 /Sub1.2 0
6 2 SUB1.3 /Sub1.3 0

For example
with q(ID, ID_PARENT, NAME, LINK) as (
-- original query
)
select ID, ID_PARENT, NAME, LINK
,(select count(*) from q q2 where q2.ID_PARENT = q.ID) CHILDREN
from q

Try like this, this is same as above answer by etsa.
select
n.id,n.parent_id,n.name,n.link,coalesce(b.children,0)
from navigation n
left join (select
parent_id as parent,count(id) as children
from navigation group by parent_id) b
on n.id=b.parent;

Related

How to check the count of each values repeating in a row

I have two tables. Data in the first table is:
ID Username
1 Dan
2 Eli
3 Sean
4 John
Second Table Data:
user_id Status_id
1 2
1 3
4 1
3 2
2 3
1 1
3 3
3 3
3 3
. .
goes on goes on
These are my both tables.
I want to find the frequency of individual users doing 'status_id'
My expected result is:
username status_id(1) status_id(2) status_id(3)
Dan 1 1 1
Eli 0 0 1
Sean 0 1 2
John 1 0 0
My current code is:
SELECT b.username , COUNT(a.status_id)
FROM masterdb.auth_user b
left outer join masterdb.xmlform_joblist a
on a.user1_id = b.id
GROUP BY b.username, b.id, a.status_id
This gives me the separate count but in a single row without mentioning which status_id each column represents
This is called pivot and it works in two steps:
extracts the data for the specific field using a CASE statement
aggregates the data on users, to make every field value lie on the same record for each user
SELECT Username,
SUM(CASE WHEN status_id = 1 THEN 1 END) AS status_id_1,
SUM(CASE WHEN status_id = 2 THEN 1 END) AS status_id_2,
SUM(CASE WHEN status_id = 3 THEN 1 END) AS status_id_3
FROM t2
INNER JOIN t1
ON t2.user_id = t1._ID
GROUP BY Username
ORDER BY Username
Check the demo here.
Note: This solution assumes that there are 3 status_id values. If you need to generalize on the amount of status ids, you would require a dynamic query. In any case, it's better to avoid dynamic queries if you can.

Create multiple rows based on 1 column

I currently have a table with a quantity in it.
ID Code Quantity
1 A 1
2 B 3
3 C 2
4 D 1
Is there anyway to write a sql statement that would get me
ID Code Quantity
1 A 1
2 B 1
2 B 1
2 B 1
3 C 1
3 C 1
4 D 1
I need to break out the quantity and have that many number of rows
Thanks
Here's one option using a numbers table to join to:
with numberstable as (
select 1 AS Number
union all
select Number + 1 from numberstable where Number<100
)
select t.id, t.code, 1
from yourtable t
join numberstable n on t.quantity >= n.number
order by t.id
Online Demo
Please note, depending on which database you are using, this may not be the correct approach to creating the numbers table. This works in most databases supporting common table expressions. But the key to the answer is the join and the on criteria.
One way would be to generate an array with X elements (where X is the quantity). So for rows
ID Code Quantity
1 A 1
2 B 3
3 C 2
you would get
ID Code Quantity ArrayVar
1 A 1 [1]
2 B 3 [1,2,3]
3 C 2 [2]
using a sequence function (e.g, in PrestoDB, sequence(start, stop) -> array(bigint))
Then, unnest the array, so for each ID, you get a X rows, and set the quantity to 1. Not sure what SQL distribution you're using, but this should work!
You can use connect by statement to cross join tables in order to get your desired output.
check my solution it works pretty robust.
select
"ID",
"Code",
1 QUANTITY
from Table1, table(cast(multiset
(select level from dual
connect by level <= Table1."Quantity") as sys.OdciNumberList));

How to arrange Sql rows in a specific format

I have table with up to 50 rows... like given below.
ID menu dispOdr ParntID
---------------------
1 abc 1 0
2 cde 2 0
3 fgh 1 2
4 ghdfdj 2 2
5 tetss 1 1
6 uni 3 0
but I want to be sorted
Like
ID menu dispOdr ParntID
---------------------
1 abc 1 0
5 tetss 1 1
2 cde 2 0
3 fgh 1 2
4 ghdfdj 2 2
6 uni 3 0
If have any query please let me know.. thanks in advance.
I am using sql server 2014
I think you need your current vs. desired output reversed. You say you want the menu column sorted, but it appears that it already is.
So assuming you are actually starting with the second table, you can sort the menu column simply using ORDER BY:
SELECT *
FROM mytable
ORDER BY menu ASC
I think that the query below produces the required output:
SELECT t1.ID, t1.menu, t1.dispOdr, t1.ParntID
FROM mytable AS t1
LEFT JOIN mytable AS t2 ON t1.ParntID = t2.ID
ORDER BY CASE
WHEN t1.ParntID = 0 THEN t1.dispOdr
ELSE t2.dispOdr
END,
CASE
WHEN t1.ParntID = 0 THEN 1
ELSE 2
END,
t1.dispOdr
The first CASE expression groups records according to the dispOdr of their parent. The second CASE places parent on the top of its subgroup. Finally, the last expression used in the ORDER BY clause orders all child records within a subgroup.
Note: The above query works with one level of nesting.

Pre-order sorting of parents and children

Given the following data:
id | parent | sort
--------------------
1 | null | 0
2 | null | 1
3 | 1 | 0
4 | 1 | 1
5 | 3 | 0
6 | 5 | 0
7 | 2 | 0
How do I do a pre-order sort, meaning parents first, then children, then grandchildren, etc...?
The sorted result I'm looking for is: 1, 3, 5, 6, 4, 2, 7
If at all possible, I'd like to do this without using a CTE (or a CTE I can understand). The way I'm doing it now is just selecting every record and checking "upwards" to see if there are any parents, grandparents and greatgrandparents. It makes more sense to do something for the records that don't have a parents (top items) and let it go on until there are no children anymore, right?
I just can't wrap my head around this...
This is an oversimplification of my actual query, but what I'm doing now is along the lines of:
SELECT ..some columns ..
FROM table t
LEFT JOIN table tparent WHERE tparent.ID = t.Parent
LEFT JOIN table tgrandparent WHERE tgrandparent.ID = tparent.Parent
LEFT JOIN table tgreatgrandparent WHERE tgreatgrandparent.ID = tgrandparent.Parent
This does use CTEs, but hopefully I can explain their usage:
;With ExistingQuery (id,parent,sort) as (
select 1,null,0 union all
select 2,null,1 union all
select 3,1 ,0 union all
select 4,1 ,1 union all
select 5,3 ,0 union all
select 6,5 ,0 union all
select 7,2 ,0
), Reord as (
select *,ROW_NUMBER() OVER (ORDER BY parent,sort) as rn from ExistingQuery
), hier as (
select id,parent,'/' + CONVERT(varchar(max),rn)+'/' as part
from Reord
union all
select h.id,r.parent,'/' + CONVERT(varchar(max),r.rn) + part
from hier h
inner join
Reord r
on
h.parent = r.id
)
select
e.*
from
hier h
inner join
ExistingQuery e
on
h.id = e.id
where
h.parent is null
order by CONVERT(hierarchyid,h.part)
ExistingQuery is just whatever you've currently got for your query. You should be able to just place your existing query in there (possibly with an expanded column list) and everything should just work.
Reord addresses a concern of mine but it may not be needed - if your actual data is actually such that the id values are indeed in the right order that we can ignore sort then remove Reord and replace the references to rn with id. But this CTE does that work to make sure that the children of parents are respecting the sort column.
Finally, the hier CTE is the meat of this solution - for every row, it's building up a hierachyid for that row - from the child, working back up the tree until we hit the root.
And once the CTEs are done with, we join back to ExistingQuery so that we're just getting the data from there, but can use the hierarchyid to perform proper sorting - that type already knows how to correctly sort hierarchical data.
Result:
id parent sort
----------- ----------- -----------
1 NULL 0
3 1 0
5 3 0
6 5 0
4 1 1
2 NULL 1
7 2 0
And the result showing the part column from hier, which may help you see what that CTE constructed:
id parent sort part
----------- ----------- ----------- --------------
1 NULL 0 /1/
3 1 0 /1/3/
5 3 0 /1/3/6/
6 5 0 /1/3/6/7/
4 1 1 /1/4/
2 NULL 1 /2/
7 2 0 /2/5/
(You may also want to change the final SELECT to just SELECT * from hier to also get a feel for how that CTE works)
I finally dove into CTE and got it working, here is the base of the query if anyone else may come across it. It's important to note that sort is a padded string, starting at 0000000001 and counting upwards.
WITH recursive_CTE (id, parentId, sort)
AS
(
-- CTE ANCHOR SELECTS ROOTS --
SELECT t.ID AS id, t.Parent as parentId, t.sort AS sort
FROM table t
WHERE t.Parent IS NULL
UNION ALL
-- CTE RECURSIVE SELECTION --
SELECT t.ID AS id, t.Parent as parentId, cte.sort + t.sort AS sort
FROM table t
INNER JOIN recursive_CTE cte ON cte.id = t.Parent
)
SELECT * FROM recursive_CTE
ORDER BY sort
I believe this is the main part needed to make this kind of query work. It's actually pretty fast if you make sure you're hitting the necessary indices.
Sort is built up by expanding a string.
So a parent would have sort '0000000001', his direct child will have '00000000010000000001' and his grandchild will have '000000000100000000010000000001' etc. His sibling starts at '0000000002' and so comes after all the 01 records.

Count number of not exist in child table

Essentially what I'm trying to do is count the number of rows something doesn't exist in an audit/history table. I'd like the following query to return a count of one per detail. Currently it gives me one per row in the history table.
--Detail Table
ID DETAIL_GROUP
1 A
2 B
3 B
--Detail History Table
DETAIL_ID_FK VALUE1
1 NOT_MATCH
1 NOT_MATCH
2 MATCH
2 NOT_MATCH
3 MATCH
3 NOT_MATCH
SELECT D.DETAIL_GROUP, COUNT(*)
FROM DETAIL D
WHERE (NOT EXISTS(
SELECT NULL
FROM DETAIL_HISTORY HI
WHERE HI.D_ID_FK = D.ID
AND HI.VALUE1 = 'MATCH'))
GROUP BY D.DETAIL_GROUP;
I'd like to see the following result:
DETAIL_GROUP COUNT(*)
A 1
but I'm receiving the following result:
DETAIL_GROUP COUNT(*)
A 2
Thank you in advance for any assistance provided.
Assuming that your detail table is as follows:
D_ID VALUE1
1 MATCH
1 NOT_MATCH
2 MATCH
2 NOT_MATCH
3 MATCH
3 NOT_MATCH
The below query:
SELECT d.detail_group, count(*)
FROM detail d
JOIN detail_history dh ON dh.d_id = d.id
WHERE dh.value1 = 'MATCH'
GROUP BY d.detail_group
Would produce:
DETAIL_GROUP COUNT(*)
A 1
B 2
The above query creates the groups matching the ids and then goes into each group and restricts the items based on value1.