Combine Parent-Child Rows - TSQL - sql-server

lI am trying to flatten/combine rows from a table with a parent-child hierarchy. I'm trying to identify the beginning and the end of each 'link' - so if a is linked to b, b is linked to c, and then c is linked to d, I want the output to link a to d.
I'm trying my best to avoid using a procedure with loops, so any advice would be much appreciated!
The original dataset and the required output is as follows:
personID | form | linkedform
---------|---------|---------
1 | a | b
1 | b | c
1 | c | d
1 | d | NULL
2 | e | f
2 | f | g
2 | g | NULL
2 | h | i
2 | i | NULL
3 | j | NULL
3 | k | l
3 | l | NULL
Desired output:
personID | form | linkedform
---------|---------|---------
1 | a | d
2 | e | g
2 | h | i
3 | j | NULL
3 | k | l
Each personID can have multiple links, and a link can be made of just one or multiple forms.

-- use a recursive cte to build the hierarchy
-- start with [linkedform] = null and work your way up
;WITH cte AS
(
SELECT *, [form] AS [root],
1 AS [Level]
FROM Table1
WHERE [linkedform] IS NULL
UNION ALL
SELECT t1.*,
[root],
[Level] + 1
FROM Table1 t1
JOIN cte ON cte.form = t1.linkedform
)
-- level 1 will be the last element, use row_number to get the first element
-- join the two together based on last and first level, that have the same personid and root ([linkedform] = null)
SELECT cte.personId,
cte2.form,
cte.form
FROM cte
JOIN ( SELECT *,
ROW_NUMBER() OVER (PARTITION BY personId, [root] ORDER BY Level DESC) Rn
FROM cte) cte2
ON cte2.Rn = cte.Level
AND cte2.personId = cte.personId
AND cte2.root = cte.root
WHERE cte.[Level] = 1
ORDER BY cte.personId, cte2.form

Related

Replace multiple Value from another table

I have 2 tables.
Table1
ID | String
1 | "A TEST B VALUE"
2 | "C TEST D DENT B"
Table 2
ID | Name | Value
1 | A | 1
1 | B | 2
2 | B | 3
2 | C | 4
2 | D | 5
The result I am hoping to get is
ID | String
1 | "1 TEST 2 VALUE"
2 | "4 TEST 5 5ENT 3"
I am trying to do this using with clause recursive
with QueryTable as (
select id, cast(String as nvarchar(max)) as 'String' ,1 as 'RN'
from [TABLE1]
group by id, String
union all
select a.id, cast(replace(a.String, b.Name, b.value) as nvarchar(max)) as 'Query', RN+1
from QueryTable a
inner join [TABLE2] b on a.id = b.id
)
Thank you so very much.
The solution Found in below link, so with cte recursive but compare between source and destination row num seem to solve the problem.
https://dba.stackexchange.com/questions/237182/how-to-replace-multiple-parts-of-a-string-with-data-from-multiple-rows
if anyone interested I can post the final solution

Finding Records with matching records in another table

I have 2 tables
Table 1
A | B | C | D | E | F
a Mi 2 1 4 001
b Ma 3 1 4 001
c NA 1 1 4 001
b Na 3 1 4 001
d Na 2 1 4 001
a Mi 2 1 4 002
b Na 3 1 4 002
c Ma 1 1 4 002
d Na 2 1 4 001
Table 2
A | B | C | D | E
a Mi 2 1 4
b Ma 3 1 4
c NA 1 1 4
d Na 2 1 4
OutPut :
F | D
001 1
So columns A, B, C, D, E and F are all columns that specific conditions in them. Table 1 is the table with data that needs to be compare to data in table2. If all records in different columns except F match from Table1 to the records in Table2, only those records should be selected in the output.
Only 001 from column F is displayed because it has all the 4 rows with the same values in the same columns as given in Table 2. Records with value 002 in column F are not selected because they do not have all the rows in table 2. They do have all 4 rows but the record with b does not have all the same matching values.
The final result need not be the output i have mentioned. It could just be all those rows that match the rows given in Table 2. The output is just what the last step is . I can achieve that if i get all rows that match all the records in table 2 like by like.
Something I tried-
select count(A) over(Partition by A,B,C,D,E,F) as rw,*
into #temp1
from Table1
select sum(rw) as sm, F
from #temp1 group by F
select F
from #temp
where sm = (select count(A) from Table2)
One of the issues with this logic is that 002 can have 2-3 duplicated rows which might result in the count being equal to the count of rows in table2 .
With a join of the tables and then group by F:
select t1.f, max(t1.d) d
from table2 t2 inner join (select distinct * from table1) t1
on t1.A = t2.A and t1.B = t2.B and t1.C = t2.C and t1.D = t2.D and t1.E = t2.E
group by t1.f
having count(*) = (select count(*) from table2)
I used max(t1.d) as it is not clear if the value of D is the same for each F.
See the demo.
Results:
> f | d
> :-- | -:
> 001 | 1
If you want the rows from table1 that match the rows from table2, use a CTE:
with cte as (
select t1.f
from table2 t2 inner join (select distinct * from table1) t1
on t1.A = t2.A and t1.B = t2.B and t1.C = t2.C and t1.D = t2.D and t1.E = t2.E
group by t1.f
having count(*) = (select count(*) from table2)
)
select t1.* from table1 t1
where
t1.f in (select f from cte)
and exists (
select 1 from table2 t2
where t1.A = t2.A and t1.B = t2.B and t1.C = t2.C and t1.D = t2.D and t1.E = t2.E
)
See the demo.
Results:
> A | B | C | D | E | F
> :- | :- | -: | -: | -: | :--
> a | Mi | 2 | 1 | 4 | 001
> b | Ma | 3 | 1 | 4 | 001
> c | NA | 1 | 1 | 4 | 001
> d | Na | 2 | 1 | 4 | 001
> d | Na | 2 | 1 | 4 | 001
If you want distinct rows use:
select distinct t1.* from table1 t1
instead.
Results:
> A | B | C | D | E | F
> :- | :- | -: | -: | -: | :--
> a | Mi | 2 | 1 | 4 | 001
> b | Ma | 3 | 1 | 4 | 001
> c | NA | 1 | 1 | 4 | 001
> d | Na | 2 | 1 | 4 | 001
Disregarding the suspect row I mention in the comment, I think this is what you want:
select *
from [Table 1] t1
where exists
(
select 1
from [Table 2] t2
where
t1.A=t2.A
and t1.B=t2.B
and t1.C=t2.C
and t1.D=t2.D
and t1.E=t2.E
)

How to get value conditionally from another row in sub table

Select * from LoanAccount main INNER JOIN LoanSubAccount sub
WHERE main.LoanAccountID = sub.LoanAccountID
AND sub.LoanStatus = 4
My objective is to retrieve rows with LoanStatus = 4 but replace the amount with records with LoanStatus = 2.
End result expected to be
WITH cte AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY LoanAccountID, LoanStatus
ORDER BY LoanSubAccountID) rn
FROM LoanSubAccount
)
SELECT t1.LoanSubAccountID,
t1.LoanAccountID,
t1.LoanStatus,
t1.CommodityType,
t2.Amount
FROM cte t1
INNER JOIN cte t2
ON t1.rn = t2.rn AND
t1.LoanStatus > t2.LoanStatus
Rather than giving a verbose explanation, I would rather show a table representing what the above CTE would look like:
rn | LoanSubAccountID | LoanAccountID | LoanStatus | CommodityType | Amount
1 | 1 | 1 | 2 | 1 | 100
2 | 2 | 1 | 2 | 2 | 200
1 | 3 | 1 | 4 | 3 | 150
2 | 4 | 1 | 4 | 4 | 150
If I read your requirement correctly, you want to connect rows having the same row number from the two different loan statuses. The join query I gave above does this.

SUM On Column With Group By SQL

I have following data:
+----------------+--------------+-----+
| StgDescription | ID | Amt |
+----------------+--------------+-----+
| A | OA17 | 11 |
| A | OA17 | 11 |
| A | OA17 | 11 |
| A | OA17 | 11 |
| B | ZA47/ A | 12 |
| B | ZA47/ A | 12 |
| B | ZA47/ B | 10 |
| B | ZA47/ B | 10 |
| B | ZA48/ A | 14 |
| B | ZA48/ F | 10 |
| B | ZA48 /G | 13 |
| B | ZA48 /H | 10 |
| B | ZA48/ I | 15 |
| B | ZA48/ J | 10 |
| B | ZA48/ K | 16 |
| B | ZA48/ L | 10 |
| c | FA01LM100340 | 10 |
| c | PA53 AE | 10 |
+----------------+--------------+-----+
I want to generate report in following format. The amount should be sum for ID for same StgDescription.
+----------------+-----+
| StgDescription | Amt |
+----------------+-----+
| a | 11 |
| b | 120 |
| c | 20 |
+----------------+-----+
I've written following query to get this result:
WITH CTE AS(
SELECT
distinct
s.StgDescription
,p.ID
,Amt
FROM [DinDb].[dbo].[tblTvlTransaction] t
JOIN tblstgmaster s on t.StgId=s.StgId
JOIN tblProjDocSt p on t.TDocID=p.DocId
JOIN [PdasDb].[dbo].[tblIDmaster] f ON p.ID=f.ID
where OptAuthoDateTime between '2015-07-27 00:00:00' and '2015-09-01 00:00:00')
select StgDescription,sum(AMT) from cte group by StgDescription
Is there any other efficient alternative to do this?
First in cte remove duplicates, then GROUP BY like:
WITH cte AS (
SELECT DISTINCT StgDescription, ID, Amt
FROM your_tab
)
SELECT
StgDescription,
Amt = SUM(Amt)
FROM cte
GROUP BY StgDescription;
OR:
WITH cte AS (
SELECT StgDescription, ID, Amt
FROM your_tab
GROUP BY StgDescription, ID, Amt
)
SELECT
StgDescription,
Amt = SUM(Amt)
FROM cte
GROUP BY StgDescription;
I hope that you get the data from a query, not from a table. It would not be good to store data thus redundantly. And it would not be gould to name a column ID which is not the unique identifier for a row in a table.
Your problem with the data is that you have duplicates, which prevents you from getting the sum directly. So use DISTINCT to make your data unique first.
If this data is from a query then simply add DISTINCT after the SELECT keyword. If not, use a derived table (i.e. a subquery) where you select distinct records from the table.
select stgdescription, sum(amt)
from
(
select distinct stgdescription, id, amt
from mydata
) distinct_data
group by stgdescription;
You may want to replace stgdescription with lower(stgdescription), though, if stgdescription can be 'A' or 'a' and you want to treat them the same.
I'd keep it as simple as possible, like this:
select StgDescription, sum(Amt) from
(
select distinct StgDescription, ID, Amt from tablename
) a
group by StgDescription
Hope it helps!
I suspect your duplicates are coming from [tblTvlTransaction], therefore, I would remove this table as a JOIN and use EXISTS to just check a record is there. So essentially the only tables in the FROM clause are those you actually need data from:
SELECT s.StgDescription, p.ID, s.Amt
FROM tblstgmaster AS s
INNER JOIN tblProjDocSt p on
t.TDocID = p.DocId
INNER JOIN [PdasDb].[dbo].[tblIDmaster] AS f
ON p.ID = f.ID
WHERE EXISTS
( SELECT 1
FROM [DinDb].[dbo].[tblTvlTransaction] AS t
WHERE t.OptAuthoDateTime BETWEEN '2015-07-27 00:00:00' AND '2015-09-01 00:00:00'
AND t.StgId = s.StgId
);
The advantage of EXISTS is that it can use a semi-join, which essentially means rather than pulling back all the rows from the transaction table, it will stop the seek/scan as soon as it finds one matching record. This should leave you without duplicates so you can do the SUM directly:
SELECT s.StgDescription, Amount = SUM(s.Amt)
FROM tblstgmaster AS s
INNER JOIN tblProjDocSt p on
t.TDocID = p.DocId
INNER JOIN [PdasDb].[dbo].[tblIDmaster] AS f
ON p.ID = f.ID
WHERE EXISTS
( SELECT 1
FROM [DinDb].[dbo].[tblTvlTransaction] AS t
WHERE t.OptAuthoDateTime BETWEEN '2015-07-27 00:00:00' AND '2015-09-01 00:00:00'
AND t.StgId = s.StgId
)
GROUP BY s.StgDescription;

Sql server join by group?

I have this table :
id | type | date
1 | a | 01/1/2012
2 | b | 01/1/2012
3 | b | 01/2/2012
4 | b | 01/3/2012
5 | a | 01/5/2012
6 | b | 01/5/2012
7 | b | 01/9/2012
8 | a | 01/10/2012
The POV is per date. if 2 rows contains the same date , so both will visible in the same line ( left join).
Same date can be shared by 2 rows max.
so this situation can't be :
1 | a | 01/1/2012
2 | b | 01/1/2012
3 | a | 01/1/2012
if in the same date there is group a and b show both of them in single line using left join
if in date there is only a group , show it as single line ( +null at the right side )
if in date there is only b group , show it as single line ( +null at the left side )
Desired result :
Date |typeA|typeB |a'id|b'id
01/1/2012 | a | b | 1 | 2
01/2/2012 | | b | | 3
01/3/2012 | | b | | 4
01/5/2012 | a | b | 5 | 6
01/9/2012 | | b | | 7
01/10/2012 | a | | 8 |
I know this suppose to be simple , but the main anchor of join here is the date.
The problem I've encountered is when I read line 1 , i search in the table all rows with the same date...fine. - its ok.
But when I read the second line , I do it also , and it yields the first row - which already was counted...
any help ?
here is the sql fiddle :
https://data.stackexchange.com/stackoverflow/query/edit/82605
I think you want a pivot
select
[date],
case when [a] IS null then null else 'a' end typea,
case when [b] IS null then null else 'b' end typeb,
a as aid,
b as bid
from yourtable src
pivot (max(id) for type in ([a],[b]))p
If you want to do it with joins..
select ISNULL(a.date, b.date), a.type,b.type, a.id,b.id
from
(select * from yourtable where type='a') a
full outer join
(select * from yourtable where type='b') b
on a.date = b.date

Resources