SQL: Selecting Where Latest Sub Child - sql

I have a few model which looks a little something like this: Parent has a 1-2-M relationship with Child, and Child has a 1-2-M relationship with Sub-Child.
Parent
------
Parent_ID
Child
-----
Child_ID,
Parent_ID
Sub-Child
---------
Child_ID,
Version_Number (numeric),
Sent (date),
Void (date)
I want a query which returns a list of unique parent_id's where the latest version (judged by the version_number) of a related sub-child is 'sent' == null, but 'void' != null.
I've been chewing this over in my head and can't figure things out.
Any advice would be greatly appreciated.
Thanks,
Robert

It'll be something like:
;WITH CTE_LatestSubChildren AS
(
SELECT Parent_ID, Latest_Version_Number = max(sc.Version_Number)
FROM
Child c
JOIN [Sub-Child] sc on c.Child_ID = sc.Child_ID
GROUP BY c.Parent_ID
)
SELECT
ParentID
FROM
CTE_LatestSubChildren lsc
JOIN Child c
on lsc.Parent_ID = c.Parent_ID
JOIN [Sub-Child] sc
ON sc.Child_ID = c.Child_ID
AND sc.version_number = lsc.Latest_Version_Number
AND sc.Sent IS NULL
AND sc.Void IS NOT NULL
Note that this may require amendments as its not tested, and its not completely clear what should happen about multiple child records where the latest version is the same.

I'm not where I can test this, but it sounds like you'll need a subquery to pull the max version numbers of each child, then a self-join to get the rest of the sub-child information. Something like this is what I'm thinking:
SELECT DISTINCT
Parent_ID
FROM
Parent JOIN Child
ON Parent.Parent_ID = Child.Parent_ID
JOIN (
SELECT Child_ID, MAX(Version_Number)
FROM Sub-Child
GROUP BY Child_ID ) AS MaxSubchild
JOIN Sub-Child
ON Sub-Child.Child_ID = MaxSubchild.Child_ID AND
Sub-Child.Version_Number = MaxSubchild.Version_Number
WHERE
SUb-Child.Sent IS NULL AND
Sub-Child.Void IS NOT NULL;

Start by getting the max version by child_id:
select child_id, max(version_number) as version_number
from subchild
group by child_id
Then join it as a subquery, with subchild and child, and apply your where condition.

Or, without subqueries, try,
SELECT DISTINCT p.parent_id
FROM sub_children sc
LEFT JOIN children c ON sc.parent_id = c.child_id
LEFT JOIN parents p ON c.parent_id = p.parent_id
WHERE sc.sent == null, but sc.void != null

You can also use Rank():
SELECT DISTINCT TOP 100 PERCENT ST.Parent_ID
FROM
(
SELECT RANK() OVER (PARTITION BY C.Parent_ID ORDER BY SC.Version_Number DESC) AS [RANK],
C.Parent_ID, SC.Sent, SC.Void
FROM Child C
INNER JOIN Sub_Child SC ON C.Child_ID = SC.Child_ID
) ST
WHERE [RANK] = 1
AND [Sent] IS NULL AND [Void] IS NOT NULL

Related

Count total number of rows where this.row is related with rows in another table

I have two simple tables, parents and children. I am trying to count the number of parents who have at least one child.
create table People(
id integer unique,
name varchar(120),
primary key (id)
);
create table children(
id integer unique,
name varchar(120),
parentId integer,
primary key(id),
foreign key (parentId) references People(id)
);
This is the code I tried but it gives me the total number of children instead:
select count(*)
from (people p join children ch on ch.parentid = p.id)
having count(ch.id) > 0;
I am trying to count the number of parents who have at least one children.
This should be as simple as:
SELECT COUNT(*)
FROM people p
WHERE EXISTS (SELECT 1 FROM children c WHERE c.parentid = p.id)
Using EXISTS is usually the most efficient way to check that something, well, exists.
You're close. You just need to make the check for children on a per-parent basis:
SELECT COUNT(*) AS parents_with_children
FROM (SELECT p.name, COUNT(c.id) AS num_children
FROM people p
JOIN children c ON c.parentid = p.id
GROUP BY p.name
HAVING COUNT(c.id) > 0) p
Demo on dbfiddle
SELECT COUNT(*),p.*
FROM People p JOIN children c ON c.parnetId=p.id
WHERE NOT c.parnetId IS NULL
GROUP BY (p.id)
(no need for having since it only joins existing children anyways)
select count(p.*)
from people p inner join children ch
on ch.parentid = p.id
You could try something like this,
SELECT COUNT(DISTINCT children.parentid)
FROM People
INNER JOIN children
ON children.parentid = people.id;
With EXISTS:
select count(distinct p.id) counter from people p
where exists (
select 1 from children
where parentid = p.id
)
or even better:
select count(distinct parentid) counter
from children
because all the info you need is in the table children, so just count the distinct values in column parentid

Keep only one record from left table

I need to get records after inner join without duplicate data from left table.
Parent table :
Parent id parent name
1 Douglas
Child table :
Parent id child name
1 George
1 Michael
With classic Oracle Inner join I get this :
Parent id parent name child name
1 Douglas George
1 Douglas Michael
But I need this result:(i need null values instead of parents names)
Parent id parent name child name
1 Douglas George
Null Null Michael
Of course, there are more parents and children.
Use row_number() or lag() or lead(), like here:
select case when rn = 1 then parent_id end parent_id,
case when rn = 1 then parent_name end parent_name,
child_id, child_name
from (select p.parent_id, p.parent_name, c.child_id, c.child_name,
row_number() over (partition by p.parent_id order by c.child_id) rn
from parents p join children c on p.parent_id = c.parent_id)
rextester demo
PARENT_ID PARENT_NAME CHILD_ID CHILD_NAME
---------- ----------- ---------- ----------
1 Douglas 1 George
2 Michael
You want to do a right join on child id
select p.parentid,p.parentname,c.childname
from parent p
right join child c
on p.parentid=c.childid
One way of doing this is with a CURSOR EXPRESSION:
select parent_id
, parent_name
, cursor ( select child_id
, child_name
from child
where child.parent_id = parent.parent_id )
from parent
How well this renders depends on the client you use to run it. Some handle the output better than others.
Alternatively you can treat it as a display issue. For instance, in SQL*Plus you could use BREAK ON parent_id ON parent_name to suppress repeating values. Find out more.
select parent.parent_id
, parent.parent_name
, child.child_id
, child.child_name
from parent
join child
on child.parent_id = parent.parent_id
order by parent.parent_id, child.child_id;
To make this work you have to have ordering.
"It seems more complicated than I thought. Is there other way without Cursor?"
That's because result sets are meant to be flat not jagged. Tweaking the query to implement display features often results in clunky or verbose SQL.
Talking of which, here's another solution for you:
with cte as (
select parent.parent_id
, parent.parent_name
, child.child_id
, child.child_name
, row_number() over (partition by parent.parent_id
order by child.child_id ) as prn
, row_number() over (order by parent.parent_id , child.child_id ) as rn
from parent
join child
on child.parent_id = parent.parent_id
)
select case when prn = 1 then parent_id else null end as parent_id
, case when prn = 1 then parent_name else null end as parent_name
, child_id
, child_name
from cte
order by rn
This generates two row numbers, one to track the parents, one to sort the entire row set.

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

How to do conditional update on columns using CTE?

I have a table CUST with following layout. There are no constraints. I do see that one ChildID has more than one ParentID associated with it. (Please see the records for ChildID = 115)
Here is what I need -
Wherever one child has more than 1 parent, I want to update those ParentID and ParentName with the ParentID and ParentName which has max match_per. So in the below image, I want ParentID 1111 and ParentName LEE YOUNG WOOK to update all records where ChildId = 115 (since the match_per 0.96 is maximum within the given set). In case there are two parents with equal max match_per, then I want to pick any 1 one of them.
I know it is possible using CTE but I don't know how to update CTE. Can anybody help?
One way of doing it
WITH CTE1 AS
(
SELECT *,
CASE WHEN match_per =
MAX(match_per) OVER (PARTITION BY ChildId)
THEN CAST(ParentId AS CHAR(10)) + ParentName
END AS parentDetailsForMax
FROM CUST
), CTE2 AS
(
SELECT *,
MAX(parentDetailsForMax) OVER (PARTITION BY ChildId) AS maxParentDetailsForMax
FROM CTE1
)
UPDATE CTE2
SET ParentId = CAST(LEFT(maxParentDetailsForMax,10) AS int),
ParentName = SUBSTRING(maxParentDetailsForMax,10,8000)
Getting both the parent id and parent name is a bit tricky. I think the logic is easiest using cross apply:
with toupdate as (
select t.*, p.parentId as new_parentId, p.parentName as new_parentName
max(match_per) over (partition by childid) as max_match_per,
count(*) over (partition by childid) as numparents
from table t cross apply
(select top 1 p.*
from table p
where p.childid = t.childid
order by match_per desc
) p
)
update toupdate
set parentId = new_ParentId,
parentName = new_ParentName
where numparents > 1;
As a note: the fact that parent id and parent name are both stored in the table, potentially multiple times seems like a problem. I would expect to look up the name, given the id, to reduce data redundancy.
Try something like this?? The first CTE will get Max(match_per) for each ChildID. Then, the second will use the new MaxMatchPer to find what its corresponding ParentID should be.
; WITH CTE AS (
SELECT ChildID,MAX(match_per) AS MaxMatchPer
FROM tbl
GROUP BY ChildID
), CTE1 AS (
SELECT t.ParentID, c.ChildID
FROM tbl t
JOIN CTE c
ON c.ChildID = t.ChildID
AND c.MaxMatchPer = t.match_per
)
UPDATE t
SET ParentID = c.ParentID
FROM tbl t
LEFT JOIN CTE1 c
ON c.ChildID = t.ChildID
Also, this is poor normalization. You should not have ParentName nor ChildName in this table.

How to get all child of a given id in SQL Server query

I have two tables in SQL Server database:
category(
itemid,
parentid
)
ArticleAssignedCategories(
categid,
artid
)
categid is a foreign key of itemid
I want to get count of artids and child of that for given itemid (child means categories with parentid of given itemid.)
For example; If given itemid = 1 and in table category have (3,1),(4,1)(5,3)
All of 3, 4, 5 are child of 1
Can anyone help me to write a good query?
Recursive queries can be done using CTE
with CTE(itemid, parentid)
as (
-- start with some category
select itemid, parentid
from category where itemid = <some_itemid>
union all
-- recursively add children
select c.itemid, c.parentid
from category c
join CTE on c.parentid = CTE.itemid
)
select count(*)
from ArticleAssignedCategories a
join CTE on CTE.itemid = a.categid
Here is the query. I hope this may help you
select b.artid,count(b.artid) from category a
inner join ArticleAssignedCategories b on a.itemid = b.artid
group by b.artid