Get the last children from database - sql

My situation:
Table A
(
ID
Parent_Id
TimeStamp
)
The root has Parent_Id null and children has Id of its father.
I simple want to get all LAST children of every Table A.
Father and Children I don't want. (except last one).
Is it possible to build a SQL to get this?
PS: I'm on sql anywhere 11. Maybe an ansi sql can solve this, i'm not sure.
EDIT: (edited to give additional details)
I don't want the last children from an element.
Example:
Id 1
Parent NULL
Id 2
Parent 1
Id 3 (the last child)
Parent 1
Id 4
Parent NULL
Id 5 (the last child)
parent 4
I want to get:
Id 3
Id 5

Using stored function
create function LastChild(in parent integer)
returns integer
begin
declare res integer;
select top 1 id into res from TableA where parent_id = parent order by timeCol desc;
return res;
end
select
select Id, lastchild(id) from TAbleA where parent_id is null
I'll work on another solution without stored function.
EDIT: without stored function:
select Id, (select top 1 id from TableA childs where parent_id = TableA.id order by timeCol desc) from TableA where parent_id = 0

If by "last children" you mean items that themselves have no children (and often referred to as leaf-level items), something like this should do:
SELECT ID
from A
where ID not in (select Parent_Id from A)
The correlated subquery version is a bit tricker to understand, but would work faster on large tables:
SELECT ID
from A OuterReference
where not exists (select 1 from A where Parenti_ID = OuterReference.ID)
("OuterReference" is an alias for table A)
I use SQL Server, but this is pretty basic syntax and should work for you with minimal modification.

select * from a where id not in (select parent_id from table a)
In other words, select everything from table a where the ID of the item is not the parent ID of any other item. This will give you all the leaf nodes of the graph.
EDIT:
Your edit is a bit confusing, and ID's aren't typically used as ordering mechanisms, but regardless, the example you give can be accomplished by this query
SELECT MAX( id )
FROM a
WHERE id NOT IN
(SELECT parent_id
FROM a
WHERE parent_id IS NOT NULL
)
GROUP BY parent_id

I had to update the query a little to get only child categories, for Postgres 9.4
select count(id) from A as outer_ref where not exists(
select 1 from A where parent_id=outer_ref.id) and parent_id is not null;

Related

How to pull up a root element in a table referencing itself with a foreign key ? (loop ?)

For instance lets say that you have a table Person like so :
Id Name Birthdate Parent
1 Hans 1960/10/15 null
2 Svend 1985/01/23 1
3 Peter 2004/03/02 2
Parent is a foreign key on the Person table.
I want to go back all the way to the oldest parent starting from a child.
For example, starting from Peter, is it possible to retrieve Hans in SQL ?
There can be possibly dozens of intermediary rows between the starting row and the ending row.
A Recursive CTE (Recursive Common Table Expression) will do what you want:
with recursive
x as (
select *, 1 as my_level from my_table where id = 3 -- Peter's id
union all
select
t.*, x.my_level + 1
from my_table t
join x on x.parent = t.id
)
select * from x order by my_level desc limit 1

Find gaps in auto incremented values

Imagine having a table as the one below:
create table test (
id int auto_increment,
some int,
columns int
)
And then this table get used alot. Rows are inserted and rows are deleted and over time there might be gaps in the number that once was auto incremented. As an example, if I at some point make the following query:
select top 10 id from test
I might get something like
3
4
6
7
9
10
13
14
18
19
How do I design a query that returns the missing values 1,2,5,8 etc?
The easiest way is to get ranges of missing values:
select (id + 1) as firstmissing, (nextid - 1) as lastmissing
from (select t.id, lead(id) over (order by id) as nextid
from test t
) t
where nextid is not null and nextid <> id + 1;
Note this uses the lead() function, which is available in SQL Server 2012+. You can do something similar with apply or a subquery in earlier versions. Here is an example:
select (id + 1) as firstmissing, (nextid - 1) as lastmissing
from (select t.id, tt.id as nextid
from test t cross apply
(select top 1 id
from test t2
where t2.id > t.id
order by id
) tt
) t
where nextid is not null and nextid <> id + 1;
Simple way is by using cte..
;WITH cte
AS (SELECT 1 id
UNION ALL
SELECT id + 1 id from cte
WHERE id < (SELECT Max(id)
FROM tablename))
SELECT *
FROM cte
WHERE id NOT IN(SELECT id
FROM tablename)
Note: this will start from 1. If you want start from the min value of your table just replace
"SELECT 1 id" to "SELECT Min(id) id FROM tablename"
Why does it matter? I'm not trying to be snarky, but this question is usually asked in the context of "I want to fill in the gaps" or "I want to compress my id values to be contiguous". In either case, the answer is "don't do it". In your example, there was at some point a row with id = 5. If you're going to do either of the above, you'll be assigning a different, unrelated set of business data that id. If there's anything that references the id external to your database, now you've just invented a problem that you didn't have before. The id should be treated as immutable and arbitrary for all intents and purposes. If you really require it to be gapless, don't use identity and never do a hard delete (i.e. if you need to deactivate a row, you need a column which says whether it's active or not).

Oracle CONNECT BY recursive child to parent query, include ultimate parent that self references

In the following example
id parent_id
A A
B A
C B
select id, parent_id
from table
start with id = 'A'
connect by nocycle parent_id = prior id
I get
A A
B A
C B
In my database I have millions of rows in the table and deep and wide hierarchies and I'm not interested in all children. I can derive the children I'm interested in. So I want to turn the query on its head and supply START WITH with the children ids. I then want to output the parent recursively until I get to the top. In my case the top is where the id and parent_id are equal. This is what I'm trying, but I can't get it to show the top level parent.
select id, parent_id
from table
START WITH id = 'C'
CONNECT BY nocycle id = PRIOR parent_id
This gives me
C B
B A
It's not outputting the A A. Is it possible to do this? What I'm hoping to do is not show the parent_id as a separate column in the output, but just show the name relating to the id. The hierarchy is then implied by the order.
I got that result by using WITH clause.
WITH REC_TABLE ( ID, PARENT_ID)
AS
(
--Start WITH
SELECT ID, PARENT_ID
FROM table
WHERE ID='C'
UNION ALL
--Recursive Block
SELECT T.ID, T.PARENT_ID
FROM table T
JOIN REC_TABLE R
ON R.PARENT_ID=T.ID
AND R.PARENT_ID!=R.ID --NoCycle rule
)
SELECT *
FROM REC_TABLE;
And it seems to work that way too.
select id, parent_id
from T
START WITH id = 'C'
CONNECT BY id = PRIOR parent_id and parent_id!= prior id;
-- ^^^^^^^^^^^^^^^^^^^^
-- break cycles
Hope it helps.

Find parent based on child table keywords

I'm trying to get the parents based on matching keywords using a child table:
AssetKeyword
============
AssetID (int)
KeywordID (int)
I'm trying to find the Assets having entries in the table, for example keywords 3 and 4 and 5.
I've tried subqueries and aggregates but can't get my head around it. Thankful for any help. Those fridays...
This isn't very dynamic i guess..
select
AssetID
from (
select distinct
AssetID,
KeywordID
from AssetKeyword
where
KeywordID in (3,4,5)
) t
group by
AssetID
having
COUNT(*) = 3
You can use EXISTS:
SELECT a.AssetID, a.Col2, ...
FROM dbo.Asset a
WHERE EXISTS
(
SELECT 1 FROM AssetKeyword ak -- it doesn't matter what you "select" here
WHERE ak.AssetID = a.AssetID
AND ak.KeywordID IN (3, 4, 5)
)
This selects all parent records where there is at least one child with at least one of those keywords.

MySQL: group by and IF statement

By default, parent_id = 0. I want to select all records with parent_id = 0 and only the last ones with parent_id > 0.
I tried this, but it didn't work:
SELECT * FROM `articles`
IF `parent_id` > 0 THEN
GROUP BY `parent_id`
HAVING COUNT(`parent_id`) >= 1
END;
ORDER BY `time` DESC
I mean, that if there are a few records with parent_id = 2, only one of them should be return. Also, if there a number of records with parent_id = 5, only one of them is returned. In other words, there should no more than one record of each parent_id apart from those having parent_id = 0.
What could be the solution?
If I understood you question well, you need all records with parent_id == 0 and only records with the greatest value of 'time' column for other parent_id's ? I suppose that parent_id + time is unique.
SELECT a.* FROM `articles` a
INNER JOIN
(SELECT parent_id, MAX(`time`) AS `time` FROM `articles` WHERE parent_id >0) t
ON (t.parent_id = a.parent_id AND a.`time` = t.`time`)
UNION
SELECT * FROM `articles` WHERE parent_id = 0;
You're mashing all the concepts together. WHERE, GROUP BY, and HAVING do different things. You probably need to read this page more thoroughly:
http://dev.mysql.com/doc/refman/5.1/en/select.html
But if I make some assumptions about what you're doing...
You probably want to start with this part:
SELECT * FROM articles WHERE parent_id > 0
But now you want to group together the articles that have the same parent. Assuming that each article has an id, perhaps you want
SELECT parent_id, COUNT(*) AS article_count FROM articles WHERE parent_id >= 0 GROUP BY parent_id
Now, all of these rows will be parent_id values with at least one child article, but if you wanted to only look at parents with more than 2 child articles, you might then want:
SELECT parent_id, COUNT(*) AS article_count
FROM articles
WHERE parent_id >= 0
GROUP BY parent_id
HAVING article_count >= 2
That will show you the identity of all parent articles with at least 2 child articles.
I think you want to do it as two queries and join the results with a UNION. I don't really have enough info from your post to know for sure what it would look like, but maybe something like this:
SELECT parent_id, time FROM articles WHERE parent_id = 0
UNION ALL
SELECT parent_id, MAX(TIME) FROM articles WHERE parent_id > 0
GROUP BY parent_id