Distinct results from view - selecting the first record - sql

I am searching parents according to child properties
I have this view returning parent and child information, I cannot change the view definition because I need to provide the child info for filtering.
Since different children can be related to the same parents, I don't get distinct results
How can I force SQL to return single parent results by using the first child found?
For example:
parent1 foo1 child1 data a
parent1 foo1 child3 data a
parent2 foo2 child1 data a
parent2 foo2 child3 data a
parent2 foo2 child4 data a
What I would be happy to get is:
parent1 foo1 child1 data a
parent2 foo2 child1 data a
Sample scripts:
drop table relations;
CREATE TABLE relations(
parent_id nchar(10) NULL,
child_id nchar(10) NULL
)
drop table items;
CREATE TABLE items(
item_id nchar(10) NULL,
data nchar(10) NULL
);
insert into items values('parent1', 'data x');
insert into items values('parent2', 'data y');
insert into items values('child1', 'data a');
insert into items values('child2', 'data b');
insert into items values('child3', 'data a');
insert into items values('child4', 'data a');
insert into relations values('parent1', 'child1');
insert into relations values('parent1', 'child3');
insert into relations values('parent2', 'child1');
insert into relations values('parent2', 'child2');
insert into relations values('parent2', 'child3');
insert into relations values('parent2', 'child4');
drop view v_parent_child;
create view v_parent_child as
select rel.parent_id,parent.data as parent_data ,rel.child_id, child.data as child_data
from relations as rel
join items as parent on rel.parent_id = parent.item_id
join items as child on rel.child_id = child.item_id
;
select * from v_parent_child where child_data = 'data a';

you can do row over partition in the join.
select parent_id, parent_data , child_id, child_data from (
select *,
row_number() over (partition by parent_id order by parent_id) as rn
FROM v_parent_child) t
where t.rn = 1 ;
like here: Oracle 'Partition By' and 'Row_Number' keyword

Try the below view.
create view v_parent_child as
select rel.parent_id,parent.data as parent_data ,rel.child_id, child.data as child_data
from ( select parent_id,
child_id
from
(
select parent_id,
child_id,
row_number() over (partition by parent_id order by child_id) rn
from relations ) oneChild
where rn = 1 ) rel
join items parent on rel.parent_id = parent.item_id
join items child on rel.child_id = child.item_id

Related

Multi-level hierarchy data display in SQL Server Management Studio

I want to fetch a multi-level hierarchy in a sequential manner. I have a table BOMHierarchy in which I have this sample data:
Parent
Child
Parent1
Child1
Parent1
child2
Child1
Child3
Child1
Child4
Child3
Child5
Child3
Child6
I want to show the above data like below in proper hierarchical manner:
Parent
Child
Parent1
Child1
Child1
Child3
Child3
Child5
Child3
Child6
Child1
Child4
Parent1
Child2
I am stuck at fetching this sequential data according to the hierarchy. Can anyone please provide a solution?
I have tried using a CTE and while loop but I'm not getting the required result.
Looks like a classic problem of how to recursively scan a tree. In SQL is simple, what you just need is to create the right ORDER BY.
Try something like this
DECLARE #BOM table (Parent varchar(20), Child varchar(20))
INSERT INTO #BOM(Parent, Child)
VALUES ('Parent1', 'Child1'),
('Parent1', 'Child2'),
('Child1', 'Child3'), ('Child1', 'Child4'),
('Child3', 'Child5'), ('Child3', 'Child6')
-- find all possible combinations recursively
;WITH cte AS
(
SELECT
Parent, Child,
CONVERT(VARCHAR(MAX),Parent + '|' + Child) Chain
FROM
#BOM root
WHERE
NOT EXISTS (SELECT 1
FROM #BOM NotRoot
WHERE root.Parent = NotRoot.Child)
UNION ALL
SELECT
BOM.Parent, BOM.Child, cte.Chain + '|' + CONVERT(VARCHAR(MAX), BOM.Child) Chain
FROM
cte
INNER JOIN
#BOM BOM ON cte.Child = BOM.Parent
)
SELECT
Parent, Child
FROM
cte
ORDER BY
Chain

Recursive SQL query with Ecto.Query

I have a categories table with the following columns:
id
name
parent_id (nullable)
And a books table which has a category_id column inside of it.
I want a function that takes a list of category ids (category_ids) and returns a query that gets books which belong to one of the given categories or their children (recursive).
I've already written a query that returns all of a given category's children. I could use that to fetch all of the subcategories of category_ids categories and use the new list. But it would send several queries to the database and I want to do it in one query. Here's the query:
with recursive cat_tree as (
select id,
name,
parent_id
from categories
where id = $1
union all
select child.id,
child.name,
child.parent_id
from categories as child
join cat_tree as parent on parent.id = child.parent_id
)
select *
from cat_tree;
EDITED
#raw_sql """
select id,
name,
parent_id
from categories
where id in (?)
union all
select child.id,
child.name,
child.parent_id
from categories as child
join cat_tree as parent on parent.id = child.parent_id
"""
def category_search_query(query, []), do: query
def category_search_query(query, category_ids) do
query
|> recursive_ctes(true)
|> with_cte("cat_tree", as: fragment(#raw_sql, ^category_ids))
|> join(:inner, [b], c in "cat_tree", on: c.id == b.category_id)
end
But when I pass [12, 13] (for example) to the function, it gives me the following error:
(DBConnection.EncodeError) Postgrex expected an integer in -9223372036854775808..9223372036854775807, got '\f\r'. Please make sure the value you are passing matches the definition in your table or in your query or convert the value accordingly.
But when I pass just an integer (and not a list), it works correctly.
I would make a procedure to get list of categories as parameter (can be an array) and change your query to this :
create function funcName (categoryIds int[])
returns table ( bookid int ,categoryid int , ...<deffine book columns>)
as
$$
with recursive cat_tree as (
select
id,name,parent_id
from
categories
where id in (select unnest(categoryIds))
union all
select
child.id,child.name,child.parent_id
from
categories as child
join cat_tree as parent on parent.id = child.parent_id
)
select
b.*
from
cat_tree c
join books on c.id = b.categoryid
$$ Language sql;

SQL get Parent where Children have specific values

Given is a Parent with the field id and Child relation with parent_id and name. How would a query look like to get all Parents which have two children, one with the name 'John' and one with the name 'Mike'. My problem is, that I am not able to build a query which returns the Parents having both children. I used Where IN ('John', 'Mike') so I get also the Parents returned which have also one child with the name 'John' or 'Mike'. But I want only the Parents with both children only.
SELECT * FROM Parent
JOIN Child ON Child.parent_id = Parent.id
WHERE Child.name IN ('John', 'Mike')
My query is of course more complex and this is only an abstraction for what I want to achieve. I have in mind, that I first need to join the children on parent_id and make something with that, but no idea.
You can do two joins and look for your specific records. This example shows that parent 1 will return with both kiddos, but not parent 2 that only has a Mike.
DECLARE #parent TABLE (ID INT)
DECLARE #child TABLE (ID INT, parentID INT, name VARCHAR(100))
INSERT INTO #parent
VALUES
(1),
(2),
(3),
(4),
(5),
(6)
INSERT INTO #child (ID, parentID, name)
VALUES
(1, 1, 'Mike'),
(2, 1, 'John'),
(3, 2, 'Mike'),
(4, 2, 'Bill'),
(5, 3, 'Dave'),
(6, 4, 'Sam')
SELECT p.*
FROM #parent p
INNER JOIN #child c1
ON c1.parentID = p.id
AND c1.name = 'Mike'
INNER JOIN #child c2
ON c2.parentID = p.ID
AND c2.name = 'John'
Try having two steps in the where clause. Both conditions will have to be true to return a parent record.
where parent.id in (select parent_id from child where child.name='John')
and parent.id in (select parent_id from child where child.name='Mike')
something like this would work in postgres if you have having.
SELECT parent_id, SUM(num) FROM (
SELECT parent_id, 1 as num FROM Child Where name = 'John'
UNION
SELECT parent_id, 1 as num FROM Child Where name = 'Mike'
) parents
GROUP BY parent_id HAVING SUM(num) = 2
So,
added the solution with the double join into an Ecto query and it passed my tests :)
from c in Child,
join: p in Parent, on: c.parent_id = p.id,
join: cc in Child, on: p.id = cc.parent_id,
where: c.name == ^"John",
where: cc.name == ^"Mike"
select: count(p.id)
Thanks for the ideas and the fast help :)

sql - How to return NULL row from left table for each parent even when child records exist

I have 2 tables with a parent-child relation. I'd like to return a row with NULL child values for parents that have children, in addition to the child records.
For example, for tables parent and child:
SELECT parent.parentid, childid
FROM parent
LEFT JOIN child
ON parent.parentid=child.parentid
For a parent parentA with children childA1, childA2, I'd like a result:
parentA, NULL
parentA, childA1
parentA, childA2
A normal LEFT JOIN will not return the first row. A UNION query works, but I'm wondering if there is a simpler/better query, using standard SQL for Oracle and SQL Server. My UNION query is:
SELECT parent.parentid, childid
FROM parent
INNER JOIN child ON parent.parentid=child.parentid
UNION
SELECT parent.parentid, NULL
FROM parent
I think your query is better written as:
SELECT p.parentid, childid
FROM parent p INNER JOIN
child c
ON p.parentid = c.parentid
UNION ALL
SELECT p.parentid, NULL
FROM parent p;
That is UNION ALL is better than UNION, because UNION has additional overhead for removing duplicate values. If you can have duplicates in parent, just use SELECT DISTINCT in the second query.
Also, you might want to add an ORDER BY to get results in order the question seems to specify:
ORDER BY parentid, (CASE WHEN child is NULL then 0 else 1 end), chile
An alternative way of doing it would be
SELECT parent.parentid,
childid
FROM parent
LEFT JOIN child
ON parent.parentid = child.parentid
GROUP BY GROUPING SETS( ( parent.parentid ), ( parent.parentid, childid ) )
HAVING (childid IS NOT NULL) OR GROUPING(childid) =1
ORDER BY parentid ASC,
GROUPING(childid) DESC
If you only want the row for parents with children you should use an inner join
SQL Fiddle - Oracle
SQL Fiddle - SQL Server

Parent - child sql query with in a single table

I've a table:
Id | Name | ParentID
I want to select records whose parentid not equal to zero, with parent record(parent record have parentid = 0, but some parent record don't have child record I want to skip them )
Try this:
SELECT child.Id,
child.Name,
child.ParentId,
parent.Name as ParentName
FROM your_table child
JOIN your_table parent ON child.ParentId = parent.id;
Check this one:
select * from child c,parent p where c.ID=P.ParentID and c.ParentID !=0