SQL Server parent child account relationship - sql-server

I have below table with values as this. The highlighted values are newly added rows. Looking the levels I want to get the parent account for those values.
Eg for CGT losses level1ID = 3 Level2ID = 1 and level3ID = 2 means its parent of LevelDescription"CGT Losses Arising During The Tax Year From Sales of Investments"
Need help in grouping ID
I want output as

It's seems that you need to do a self join. You have not given your table properly (please display as text not image) and I haven't tested, the following should do what you need.
SELECT
c.level4ID account_no,
p.LevelDescription parent,
c.LevelDescription child
FROM
(SELECT * FROM table_name
WHERE Level4Id <> 0) c
JOIN
(SELECT * FROM table_name
WHERE Level4ID = 0 ) p
ON
c.level1Id=p.level1Id,
c.level2Id=p.level2Id,
c.level3Id=p.level3Id
ORDER BY
c.level4Id;

Related

How to do an SQL Server Recursive CTE

I have a table where each record contains a unique Numeric id and 2 parent ids (mother, father). I would like to find a way to list the parents(2), grandparents(4), great grandparents(8) and so on down to a specified level. Before I give up on pure SQL and do it in Python, can anyone tell me a way to do this?
You can try something like below, where once you pass child numericId, you get corresponding parents and from there, recursively you get higher levels.
You can filter the levels using parentlevel filter.
DECLARE #childNumericId AS INT
;WITH CTE_Ancestory AS (
SELECT numericId AS child, ParentId1 as father, parentId2 as mother, 1 as parentlevel
FROM tableName
WHERE numericId = #childNumericId
UNION ALL
SELECT t.NumericId AS child, t.ParentId1 as father, t.parentId2 as mother, c.parentlevel + 1 AS parentLevel
FROM tableName AS t
INNER JOIN CTE_Ancestory AS c ON t.numericId IN (c.father, c.mother)
)
SELECT *
from CTE_Ancestory
Where parentlevel < 4 -- number of levels you need

Join two queries that are grouped by same column from same tables but different parameters to join

I have 3 tables with items, owners and statuses and I need to show the count of items that were sold/discarded grouped by every owner for the year passed as parameter.
I am able to get ownername and soldcount as one query and ownername and discardcount as second query but is there a way to structure so that ownername, soldcount and discardcount come in one query?
declare #QueryYear integer = 2020
--SOLD
select O1.pk_owner_id,count(P1.pk_Property_ID) as [SaleCount]
from
Item P1, Owner O1, Status S1
WHERE
(C1.fkl_owner_ID = O1.pk_owner_ID and C1.fkl_item_ID=P1.pk_item_ID and O1.isactive=1 and year(P1.dtList_Date)=#QueryYear and P1.fkl_status_ID=1)
group by
O1.pk_owner_id
--DISCARD
select O2.pk_owner_id,count(P2.pk_item_ID) as [DiscardCount]
from
item P2, owner O2, status C2
WHERE
(C2.fkl_Owner_ID = O2.pk_owner_ID and C2.fkl_item_ID=P2.pk_item_ID and O2.isactive=1 and year(P2.dtList_Date)=#QueryYear and P2.fkl_item_status_ID=2)
group by
O2.pk_owner_id
I used a Union and it gives answer in 2 columns only.
Move your status filter to case statements in your select clause.
select o.pk_owner_id,
SaleCount = count(case when i.fkl_status_ID = 1 then 1 end),
DiscardCount = count(case when i.fkl_item_status_ID = 2 then 1 end)
from Status s
join Item i on s.fkl_item_ID = i.pk_item_ID
join Owner o on s.fkl_owner_ID = o.pk_owner_ID
where o.isactive = 1
and year(i.dtList_Date) = #QueryYear
group by o.pk_owner_id
Also, use relational operators to express relationships between tables, don't use the where clause. In this case, because the nature of the relationship is 'horizontal' in that each row in one table matches to each row in another table, you're looking for an (inner) join.
Finally, if you have more status types than '1' and '2', then you can add another condition to your joining of status with item, or put it in your where statement. Namely, you can do something like:
and i.fkl_status_ID in (1,2)
But I notice that the status_id columns have different names for SaleCount and DiscardCount. So if that's not an error, you'll need to do a parenthesized or version. But the main point is that your query will be more efficient if the processor knows to ignore statuses that are not '1' or '2'.

Need Sql query for returning true or false based on table unique column value

I have two tables one master and one child and both contains a unique column field i need to return a false if child table contains a value other than master table value in unique column field .
Table 1: Master (Master always contains only one row)
Unique Column is PLID and Value is 10
Case: 1
Table 2: Child
Unique Column is PLID
it contains 3 rows
PLID
====
10
20
30
Then it contains other than master table PLID unique field values so I need to return False
Case: 2
Table 2: Child
Unique Column is PLID
it contains 3 rows
PLID
====
10
10
10
Case 2 contains Values same as in master table so need to return True
Need a function for this in SQL.
First, you can use a LEFT JOIN to find any rows that exist in one table but not the other.
You can then use an EXISTS clause to check if that query return any rows or not.
Many SQL dialects don't have boolean types though, in which case you could then wrap it all in a CASE expression.
SELECT
CASE WHEN
EXISTS (
SELECT *
FROM table_child c
LEFT JOIN table_parent p
ON p.PLID = c.PLID
WHERE p.PLID IS NULL
)
THEN
0
ELSE
1
END
Edit
The original question was tagged with sql (referring to ANSI SQL) and this answer uses standard ANSI SQL.
However it turns out, the DBMS being used is SQL Server and that does not support boolean expressions like that. This answer will not work with Microsoft SQL Server.
But I'm leaving the answer nonetheless for reference.
You can use a co-related sub-query with a NOT EXISTS condition:
select plid, not exists (select *
from child_table ct
where ct.plid <> mt.plid) as has_no_other_values
from master_table mt;
Online example: https://rextester.com/EYWXLQ53937
select c.plid, case when p.plid is null then 0 else 1 end as match
from child c
left outer join parent p in p.plid = c.plid
Select from child and join it to parent, matching parent to child with the same plid. However, 'left outer join' will return child rows where no matching parent is found. From that lot, where parent plid is null - that is, a matching parent row was not found - show 0 else show 1.
Another option not mentioned before is to use the LEFT JOIN condition togheter with IF(ISNULL).
SELECT IF(ISNULL(child.PLID,1,0))
FROM table_parent parent
LEFT JOIN table_child child ON child.PLID != parent.PLID
LIMIT 1
select count (*) from
(select max(ChildPKID) as ID from Child where MasterPKID = 800
group by PLID)s
select case
when
(select Count(select 1 from childTable as c where c.PLID = (select PLID from masterTable) )) = (select Count(*) from childTable)
then 'True'
else 'False'
end

SQL Join one-to-many tables, selecting only most recent entries

This is my first post - so I apologise if it's in the wrong seciton!
I'm joining two tables with a one-to-many relationship using their respective ID numbers: but I only want to return the most recent record for the joined table and I'm not entirely sure where to even start!
My original code for returning everything is shown below:
SELECT table_DATES.[date-ID], *
FROM table_CORE LEFT JOIN table_DATES ON [table_CORE].[core-ID] = table_DATES.[date-ID]
WHERE table_CORE.[core-ID] Like '*'
ORDER BY [table_CORE].[core-ID], [table_DATES].[iteration];
This returns a group of records: showing every matching ID between table_CORE and table_DATES:
table_CORE date-ID iteration
1 1 1
1 1 2
1 1 3
2 2 1
2 2 2
3 3 1
4 4 1
But I need to return only the date with the maximum value in the "iteration" field as shown below
table_CORE date-ID iteration Additional data
1 1 3 MoreInfo
2 2 2 MoreInfo
3 3 1 MoreInfo
4 4 1 MoreInfo
I really don't even know where to start - obviously it's going to be a JOIN query of some sort - but I'm not sure how to get the subquery to return only the highest iteration for each item in table 2's ID field?
Hope that makes sense - I'll reword if it comes to it!
--edit--
I'm wondering how to integrate that when I'm needing all the fields from table 1 (table_CORE in this case) and all the fields from table2 (table_DATES) joined as well?
Both tables have additional fields that will need to be merged.
I'm pretty sure I can just add the fields into the "SELECT" and "GROUP BY" clauses, but there are around 40 fields altogether (and typing all of them will be tedious!)
Try using the MAX aggregate function like this with a GROUP BY clause.
SELECT
[ID1],
[ID2],
MAX([iteration])
FROM
table_CORE
LEFT JOIN table_DATES
ON [table_CORE].[core-ID] = table_DATES.[date-ID]
WHERE
table_CORE.[core-ID] Like '*' --LIKE '%something%' ??
GROUP BY
[ID1],
[ID2]
Your example field names don't match your sample query so I'm guessing a little bit.
Just to make sure that I have everything you’re asking for right, I am going to restate some of your question and then answer it.
Your source tables look like this:
table_core:
table_dates:
And your outputs are like this:
Current:
Desired:
In order to make that happen all you need to do is use a subquery (or a CTE) as a “cross-reference” table. (I used temp tables to recreate your data example and _ in place of the - in your column names).
--Loading the example data
create table #table_core
(
core_id int not null
)
create table #table_dates
(
date_id int not null
, iteration int not null
, additional_data varchar(25) null
)
insert into #table_core values (1), (2), (3), (4)
insert into #table_dates values (1,1, 'More Info 1'),(1,2, 'More Info 2'),(1,3, 'More Info 3'),(2,1, 'More Info 4'),(2,2, 'More Info 5'),(3,1, 'More Info 6'),(4,1, 'More Info 7')
--select query needed for desired output (using a CTE)
; with iter_max as
(
select td.date_id
, max(td.iteration) as iteration_max
from #table_dates as td
group by td.date_id
)
select tc.*
, td.*
from #table_core as tc
left join iter_max as im on tc.core_id = im.date_id
inner join #table_dates as td on im.date_id = td.date_id
and im.iteration_max = td.iteration
select *
from
(
SELECT table_DATES.[date-ID], *
, row_number() over (partition by table_CORE date-ID order by iteration desc) as rn
FROM table_CORE
LEFT JOIN table_DATES
ON [table_CORE].[core-ID] = table_DATES.[date-ID]
WHERE table_CORE.[core-ID] Like '*'
) tt
where tt.rn = 1
ORDER BY [core-ID]

SQL Case statements, making sub selections on a condition?

I've come across a scenario where I need to return a complex set of calculated values at a crossover point from "legacy" to current.
To cut a long story short I have something like this ...
with someofit as
(
select id, col1, col2, col3 from table1
)
select someofit.*,
case when id < #lastLegacyId then
(select ... from table2 where something = id) as 'bla'
,(select ... from table2 where something = id) as 'foo'
,(select ... from table2 where something = id) as 'bar'
else
(select ... from table3 where something = id) as 'bla'
,(select ... from table3 where something = id) as 'foo'
,(select ... from table3 where something = id) as 'bar'
end
from someofit
No here lies the problem ...
I don't want to be constantly doing that case check for each sub selection but at the same time when that condition applies I need all of the selections within the relevant case block.
Is there a smarter way to do this?
if I was in a proper OO language I would use something like this ...
var common = GetCommonSuff()
foreach (object item in common)
{
if(item.id <= lastLegacyId)
{
AppendLegacyValuesTo(item);
}
else
{
AppendCurrentValuesTo(item);
}
}
I did initially try doing 2 complete selections with a union all but this doesn't work very well due to efficiency / number of rows to be evaluated.
The sub selections are looking for total row counts where some condition is met other than the id match on either table 2 or 3 but those tables may have millions of rows in them.
The cte is used for 2 reasons ...
firstly it pulls only the rows from table 1 i am interested in so straight away im only doing a fraction of the sub selections in each case.
secondly its returning the common stuff in a single lookup on table 1
Any ideas?
EDIT 1 :
Some context to the situation ...
I have a table called "imports" (table 1 above) this represents an import job where we take data from a file (csv or similar) and pull the records in to the db.
I then have a table called "steps" this represents the processing / cleaning rules we go through and each record contains a sproc name and a bunch of other stuff about the rule.
There is then a join table that represents the rule for a particular import "ImportSteps" (table 2 above - for current data), this contains a "rowsaffected" column and the import id
so for the current jobs my sql is quite simple ...
select 123 456
from imports
join importsteps
for the older legacy stuff however I have to look through table 3 ... table 3 is the holding table, it contains every record ever imported, each row has an import id and each row contains key values.
on the new data rowsaffected on table 2 for import id x where step id is y will return my value.
on the legacy data i have to count the rows in holding where col z = something
i need data on about 20 imports and this data is bound to a "datagrid" on my mvc web app (if that makes any difference)
the cte i use determines through some parameters the "current 20 im interested in" those params represent start and end record (ordered by import id).
My biggest issue is that holding table ... it's massive .. individual jobs have been known to contain 500k + records on their own and this table holds years of imported rows so i need my lookups on that table to be as fast as possible and as few as possible.
EDIT 2:
The actual solution (suedo code only) ...
-- declare and populate the subset to reduce reads on the big holding table
declare table #holding ( ... )
insert into #holding
select .. from holding
select
... common stuff from inner select in "from" below
... bunch of ...
case when id < #legacy then (select getNewValue(id, stepid))
else (select x from #holding where id = ID and ... ) end as 'bla'
from
(
select ROW_NUMBER() over (order by importid desc) as 'RowNum'
, ...
) as I
-- this bit handles the paging
where RowNum >= #StartIndex
and RowNum < #EndIndex
i'm still confident i can clean it up more but my original query that looked something like bills solution was about 45 seconds in execution time, this is about 7
I take it the subqueries must return a single scalar value, correct? This point is important because it is what ensures the LEFT JOINs will not multiply the result.
;with someofit as
(
select id, col1, col2, col3 from table1
)
select someofit.*,
bla = coalesce(t2.col1, t3.col1),
foo = coalesce(t2.col2, t3.col2),
bar = coalesce(t2.bar, t3.bar)
from someofit
left join table2 t2 on t2.something=someofit.id and somefit.id < #lastLegacyId
left join table3 t3 on t3.something=someofit.id and somefit.id >= #lastLegacyId
Beware that I have used id >= #lastLegacyId as the complement of the condition, by assuming that id is not nullable. If it is, you need an IsNull there, i.e. somefit.id >= isnull(#lastLegacyId,somefit.id).
Your edit to the question doesn't change the fact that this is an almost literal translation of the O-O syntax.
foreach (object item in common) --> "from someofit"
{
if(item.id <= lastLegacyId) --> the precondition to the t2 join
{
AppendLegacyValuesTo(item); --> putting t2.x as first argument of coalesce
}
else --> sql would normally join to both tables
--> hence we need an explicit complement
--> condition as an "else" clause
{
AppendCurrentValuesTo(item); --> putting t3.x as 2nd argument
--> tbh, the order doesn't matter since t2/t3
--> are mutually exclusive
}
}
function AppendCurrentValuesTo --> the correlation between t2/t3 to someofit.id
Now, if you have actually tried this and it doesn't solve your problem, I'd like to know where it broke.
Assuming you know that there are no conflicting ID's between the two tables, you can do something like this (DB2 syntax, because that's what I know, but it should be similar):
with combined_tables as (
select ... as id, ... as bla, ...as bar, ... as foo from table 2
union all
select ... as id, ... as bla, ...as bar, ... as foo from table 3
)
select someofit.*, combined_ids.bla, combined_ids.foo, combined_ids.bar
from someofit
join combined_tables on someofit.id = combined_tables.id
If you had cases like overlapping ids, you could handle that within the combined_tables() section

Resources