MySQL: group by and IF statement - sql

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

Related

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.

Linking together different columns in a SQL table?

I'm not very experienced with advance SQL queries, I'm familiar with basic statements and basic joins, currently trying to figure out how to write a query that seems to be out of my depth and I haven't been able to find a solution from google so far and I'm hoping somebody might be able to point me in the right direction.
The table I'm working with has an ID column, and a 'parent id' column.
I'm looking for all descendants of ID '1' - rows with a parent ID of '1', rows with a parent ID equal to any row's ID with a parent ID of '1' etc. Currently I've been doing this manually but there are hundreds of descendants so far and I feel like there's a way to put this into one query.
Any help would be appreciated, if this is unclear I can also try to clarify.
EDIT - I got it working with the following query:
with cteMappings as (
select map_id, parent_map_id, map_name
from admin_map
where map_id = '1'
union all
select a.map_id, a.parent_map_id, a.map_name
from admin_map a
inner join cteMappings m
on a.parent_map_id = m.map_id
)
select map_id, parent_map_id, map_name
from cteMappings
Sounds like it can be achieved by Common Table Expression:
DECLARE #temp TABLE (id INT IDENTITY(1, 1), parent_id INT);
INSERT #temp
SELECT NULL
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT NULL
SELECT * FROM #temp
; WITH HierarchyTemp (id, parent_id, depth) AS (
SELECT id, parent_id, 0
FROM #temp
WHERE id = 1
UNION ALL
SELECT t.id, t.parent_id, ht.depth + 1
FROM #temp t
INNER JOIN HierarchyTemp ht ON ht.id = t.parent_id
)
SELECT *
FROM HierarchyTemp
So the above example is creating a table variable with 4 rows:
id parent_id
1 NULL
2 1
3 2
4 NULL
Result of descendent of id '1' (also including itself, but can be excluded with an additional WHERE clause):
id parent_id depth
1 NULL 0
2 1 1
3 2 2

sql function for checking if two persons have common ancestors

I have to solve the following problem: create a sql function that check if two persons have common ancestors, but I'm stuck.
I create
create type person as object
(
first_name varchar2(10),
last_namevarchar2(10)
);
and Persons table
create table client_iulia
(Person_Id varchar2(13)constraint pk_id_client primary key,
Mother_Id varchar2(13),
Father_Id varchar2(13),
Name person);
What I am trying to do is a function with three parameters (two people and search level) to return 1 if there are common ancestors and 0 otherwise)
Please, if anyone has any idea, help me.
Sorry for my poor english.
Recursive CTE is the way to go. I think it is the easiest to understand. There may be faster ways, but this should be clear how it works. I don't have access to an oracle server so I might have typos, I show two steps below so you can test and understand how it works.
1) Find all ancestors for a single input
with ancestors as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPerson
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestors a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
)
SELECT *
FROM ancestors
2) Find all ancestors for two targets
with ancestorsA as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPersonA
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestorsA a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
), ancestorsB as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPersonB
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestorsB a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
)
SELECT A.*
FROM ancestorsA A
JOIN ancestorsB B ON A.Person_Id = B.Person_Id
Wow, that's tricky. You need recursive queries to get all ancestors of either persons. As there are two parents, you will get pairs of mother and father, however, which you need to split into single ancestors. Then look if the sets have at least one ancestor in common. This statement gives you 1 when there is at least one common ancestor, else 0:
with ancestors_of_person_1 as
(
select mother_id, father_id
from client_iulia
start with person_id = 1
connect by person_id = prior mother_id or person_id = prior father_id
)
, ancestors_of_person_2 as
(
select mother_id, father_id
from client_iulia
start with person_id = 2
connect by person_id = prior mother_id or person_id = prior father_id
)
select count(*)
from
(
(
select mother_id as ancestor from ancestors_of_person_1
union
select father_id as ancestor from ancestors_of_person_1
)
intersect
(
select mother_id as ancestor from ancestors_of_person_2
union
select father_id as ancestor from ancestors_of_person_2
)
)
where rownum = 1;
EDIT: Here is an sqlfiddle. Person 1 and 3 have ancestor 122 in common, whereas person 1 and 2 have none in common. Try it: sqlfiddle

Get the last children from database

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;