Determine whether a specific entry has any children in a hierarchical database - sql

I'm working on a recursive query for a hierarchal table in psql. While I'm able to produce an ordered, hierarchal list, I cannot figure out how to determine whether a parent has any children.
My code at the moment:
WITH RECURSIVE q AS (
SELECT h, 1 AS level, ARRAY[ordering] AS ordered_path, ARRAY[id] AS breadcrumb
FROM report h
WHERE parent IS NULL
UNION ALL
SELECT hi, q.level + 1 AS level, ordered_path || ordering, breadcrumb || id FROM q
JOIN report hi ON hi.parent = (q.h).id )
SELECT (q.h).id, (q.h).parent, (q.h).name, array_to_json(breadcrumb) AS breadcrumbs,
row_number() OVER (order by ordered_path) AS flat_order
FROM q
ORDER BY ordered_path
Which produces the following table:
id | parent | name | ordering | trail | rownum
----+--------+-----------------------+----------+--------------+--------
1 | | Entry 1 | 1 | [1] | 1
2 | 1 | Entry 2 | 1 | [1,2] | 2
15 | 2 | Entry 3 | 1 | [1,2,15] | 3
159 | 2 | Entry 4 | 2 | [1,2,159] | 4
16 | 2 | Entry 5 | 3 | [1,2,16] | 5
Essentially, I'd like a column that shows if a specific entry has any children. In this example, Entry 5 has no children.
The format of the original table is:
id | name | type | parent | ordering
-----+-----------------------------------------+---------+--------+----------
186 | Entry 1 | page | 172 | 23
154 | Entry 2 | page | 63 | 3
169 | Entry 3 | page | 163 | 3
Thanks!

You could use a correlated subquery as an extra field:
exists (select 1 from report where parent = q.id) as has_children
It's not necessarily the most efficient — though tbh, given the query, I'm can't think of anything better off the top of my head. But it'll work.

sql below fill find the child and will count you can change how you want the output by using case statements I tested the code, seems to be working
select x.Col1, count(y.Col1) as child from Table1 x
inner join Table2 y on x.Col1 = y.Col1
group by x.Col1

Related

Can I generate a map that shows a particular row was in a particular group in SQLite?

Say I have the following data:
+--------+-------+
| Group | Data |
+--------+-------+
| 1 | row 1 |
| 1 | row 2 |
| 1 | row 3 |
| 20 | row 1 |
| 20 | row 3 |
| 10 | row 1 |
| 10 | row A |
| 10 | row 2 |
| 10 | row 3 |
+--------+-------+
Is it possible to draw a map that shows which groups have which rows? Groups may not be contagious, so they can be placed into a separate table and use the row index for the string index instead. Something like this:
+-------+
| Group |
+-------+
| 1 |
| 20 |
| 10 |
+-------+
+-------+----------------+
| Data | Found in group |
+-------+----------------+
| row 1 | 111 |
| row A | 1 |
| row 2 | 1 1 |
| row 3 | 111 |
+-------+----------------+
Where the first character represents Group 1, the 2nd is Group 20 and the 3rd is Group 10.
Ordering of the Group rows isn't critical so long as I can reference which row goes with which character.
I only ask this because I saw this crazy example in the documentation generating a fractal, but I can't quite get my head around it.
Is this doable?
To find the missing values, first thing is to prepare a dataset which have all possible combination. You can achieve that using CROSS JOIN.
Once you have that DataSet, compare it with the actual DataSet.
Considering the Order by is done in the Grp column, you can achieve it using below.
SELECT
a.Data,group_concat(case when base.Grp is null then "." else "1" end,'') as Found_In_Group
,group_concat(b.Grp) as Group_Order
FROM
(SELECT Data FROM yourtable Group By Data)a
CROSS JOIN
(SELECT Grp FROM yourtable Group By Grp Order by Grp)b
LEFT JOIN yourtable base
ON b.Grp=base.Grp
AND a.Data=base.Data
GROUP BY a.Data
Note: Considered . instead of blank for better visibility to represent missing Group.
Data
Found_In_Group
Group_Order
row 1
111
1,10,20
row 2
11.
1,10,20
row 3
111
1,10,20
row A
.1.
1,10,20
Demo: Try here
SELECT Data, group_concat("Group") AS "Found in group"
FROM yourtable
GROUP BY Data
will give you a CSV list of groups.

Split data by levels in hierarchy

Example of initial data:
| ID | ParentID |
|------|------------|
| 1 | NULL |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
| 5 | NULL |
| 6 | 2 |
| 7 | 3 |
In my initial data I have ID of element and his parent ID.
Some elements has parent, some has not, some has a parent and his parent has a parent.
The maximum number of levels in this hierarchy is 3.
I need to get this hierarchy by levels.
Lvl 1 - elements without parents
Lvl 2 - elements with parent which doesn't have parent
Lvl 3 - elements with parent which has a parent too.
Expected result looks like:
| Lvl1 | Lvl2 | Lvl3 |
|-------|----------|----------|
| 1 | NULL | NULL |
| 1 | 2 | NULL |
| 1 | 3 | NULL |
| 1 | 2 | 4 |
| 5 | NULL | NULL |
| 1 | 2 | 6 |
| 1 | 3 | 7 |
How I can do it?
For a fixed dept of three, you can use CROSS APPLY.
It can be used like a JOIN, but also return extra records to give you the NULLs.
SELECT
Lvl1.ID AS lvl1,
Lvl2.ID AS lvl2,
Lvl3.ID AS lvl3
FROM
initial_data AS Lvl1
CROSS APPLY
(
SELECT ID FROM initial_data WHERE ParentID = Lvl1.ID
UNION ALL
SELECT NULL AS ID
)
AS Lvl2
CROSS APPLY
(
SELECT ID FROM initial_data WHERE ParentID = Lvl2.ID
UNION ALL
SELECT NULL AS ID
)
AS Lvl3
WHERE
Lvl1.ParentID IS NULL
ORDER BY
Lvl1.ID,
Lvl2.ID,
Lvl3.ID
But, as per my comment, this is often a sign that you're headed down a non-sql route. It might feel easier to start with, but later it turns and bites you, because SQL benefits tremendously from normalised structures (your starting data).

Set-based way to calculate family ranges in SQL?

I have a table that contains parents and 0 or more children for each parent, with a flag indicating which records are parents. All of the members of a given family have the same parent id, and the parent always has the lowest id in a given family. Also, each child has a value associated with it. (Specifically, this is a database of emails and attachments, where each parent is an email and the children are the attachments.)
I have two fields I need to calculate:
Range = {lowest id in family} - {highest id in family} [populated for all members]
Value-list = {delimited list of the values of each child, in id order} [only for parent]
So, given this:
Id | Parent| HasChildren| Value | Range | Value-list
----------------------------------------|-----------
1 | 1 | 1 | | |
2 | 1 | 0 | a | |
3 | 1 | 0 | b | |
4 | 4 | 1 | | |
5 | 4 | 0 | c | |
6 | 6 | 0 | | |
I would like to end up with this:
Id | Parent| HasChildren| Value | Range | Value-list
----------------------------------------|-----------
1 | 1 | 1 | | 1-3 | a;b
2 | 1 | 0 | a | 1-3 |
3 | 1 | 0 | b | 1-3 |
4 | 4 | 1 | | 4-5 | c
5 | 4 | 0 | c | 4-5 |
6 | 6 | 0 | | 6-6 |
How can I do this efficiently? Ideally, I'd like to do this with just set-based logic, without cursors, or even stored procedures. Temporary tables are fine.
I'm working in T-SQL, if that makes a difference, though I'd be curious to see platform agnostic answers.
The following SQLFiddle Solution should do the job for you, however as #Allan mentioned, you might want to revise your database structure.
Using CTE's:
Note: my query uses table1 as name of Your table
with cte as(
select parent
,ValueList= stuff(( select ';' +isnull(t2.Value, '')
from table1 t2
where t1.parent=t2.parent
order by t2.value
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)'), 1, 2, '')
from table1 t1
group by parent
),
cte2 as (select parent
, min(id) as firstID
, max(id) as LastID
from table1
group by parent)
select *
,(select FirstID from cte2 t2 where t2.parent=t1.parent)+'-'+(select LastID from cte2 t2 where t2.parent=t1.parent) as [Range]
,(select ValueList from cte t2 where t1.parent=t2.parent and t1.[haschildren]='1') as [Value -List]
from table1 t1

Selecting column from one table and count from another

t1
id | name | include
-------------------
1 | foo | true
2 | bar | true
3 | bum | false
t2
id | some | table_1_id
-------------------------
1 | 42 | 1
2 | 43 | 1
3 | 42 | 2
4 | 44 | 1
5 | 44 | 3
Desired output:
name | count(some)
------------------
foo | 3
bar | 1
What I have currently from looking through other solutions here:
SELECT a.name,
COUNT(r.some)
FROM t1 a
JOIN t2 r on a.id=r.table_1_id
WHERE a.include = 'true'
GROUP BY a.id,
r.some;
but that seems to get me
name | count(r.some)
--------------------
foo | 1
foo | 1
bar | 1
foo | 1
I'm no sql expert (I can do simple queries) so I'm googling around as well but finding most of the solutions I find give me this result. I'm probably missing something really easy.
Just remove the second column from the group by clause
SELECT a.name,
COUNT(r.some)
FROM t1 a
JOIN t2 r on a.id=r.table_1_id
WHERE a.include = 'true'
GROUP BY a.name
Columns you want to use in an aggregate function like sum() or count() must be left out of the group by clause. Only put the columns in there you want to be unique outputted.
This is because multiple column group requires the all column values to be same.
See this link for more info., Using group by on multiple columns
Actually in you case., if some are equal, table_1_id is not equal (And Vice versa). so grouping cannot occur. So all are displayed individually.
If the entries are like,
id | some | table_1_id
-------------------------
1 | 42 | 1
2 | 43 | 1
3 | 42 | 2
4 | 42 | 1
Then the output would have been.,
name | count
------------------
foo | 2 (for 42)
foo | 1 (for 43)
bar | 1 (for 42)
Actually, if you want to group on 1 column as Juergen said, you could remove r.some; from groupby clause.

SQL Server: How to select second-highest parentid?

I have a SQL Server database with these pages:
+------------+--------------+-------------------------------+
| pageid | parentid | title |
+------------+--------------+-------------------------------+
| 1 | null | Home |
+------------+--------------+-------------------------------+
| 2 | 1 | News |
+------------+--------------+-------------------------------+
| 3 | 1 | User |
+------------+--------------+-------------------------------+
| 4 | 3 | Edit profile |
+------------+--------------+-------------------------------+
| 5 | 3 | Messages |
+------------+--------------+-------------------------------+
| 6 | 5 | View all |
+------------+--------------+-------------------------------+
How do I select the second-highest (in level) parentid for any row? So for pageid=6 (View all) it should return parentid->3 (User).
For a fixed and known number of steps up the parent hierachy, use explicit joins:
select l2.*
from table t
join table l1 on t.parent_id = l1.pageid
join table l2 on l1.parent_id = l2.pageid
where t.pageid = 6;
For an unknow number of steps in the hierachy, use a recursive cte, but you need a stop criteria, see Recursive Queries Using Common Table Expressions.
Try:
select max(thing) from table where thing < (select max(thing) from table)
I couldn't pick from your question and your sample whether you want pageid or parentid.