TSQL Update statement with Join - sql

I have two tables with a parent/child relationship. I want to update the parent with data from the child. However assuming there are 2 children I would like to be able to pick which child is used for the update depending on some other column from the child.
Here is what I have so far:
Parent: #test
Child: #exdat
Expected outcome, result of parent after update should contain only Capital letters. I want to update the parent with some data from the child, but if more than one exists I'd rather choose a child with a dimp of p1 over p2, p2 over p3 and p3 over p4
DECLARE #test TABLE
(
id int,
val char(1)
);
DECLARE #exdat TABLE
(
id int,
dval char(1),
dimp char(2)
);
INSERT INTO #test (id,val)
SELECT 1,'a'
UNION ALL SELECT 2,'b'
UNION ALL SELECT 3,'c'
UNION ALL SELECT 4,'d'
UNION ALL SELECT 5,'e'
UNION ALL SELECT 6,'f'
UNION ALL SELECT 7,'g'
;
INSERT INTO #exdat (id,dval,dimp)
SELECT 1,'A','p1'
UNION ALL SELECT 2,'B','p3'
UNION ALL SELECT 3,'C','p1'
UNION ALL SELECT 4,'D','p2'
UNION ALL SELECT 5,'E','p2'
UNION ALL SELECT 6,'F','p3'
UNION ALL SELECT 7,'w','p2'
UNION ALL SELECT 7,'g','p3'
UNION ALL SELECT 7,'G','p1'
UNION ALL SELECT 7,'z','p4'
;
UPDATE #test SET
val = e.dval
FROM
#test t
INNER JOIN #exdat e ON t.id = e.id
;
SELECT * FROM #test;
Result:
1 A
2 B
3 C
4 D
5 E
6 F
7 w <-- problem illustrated here
This "w" could have been any of the values w,g,G,z. So I am asking how can I prioritize the child selection based on some other column ?

What you want to do instead of a join is a subquery. Something like this:
UPDATE
a
SET
a.val = ISNULL((
SELECT TOP 1 x.dval
FROM #exdat x
WHERE x.id = a.id
ORDER BY x.magic_field -- <- here's how you specify precedence
), 'ReasonableDefault')
FROM
#test a

Trying using a CROSS APPLY with your update. The example below orders by the #extdat.dimp value:
DECLARE #test TABLE
(
id int,
val char(1)
);
DECLARE #exdat TABLE
(
id int,
dval char(1),
dimp char(2)
);
INSERT INTO #test (id,val)
SELECT 1,'a'
UNION ALL SELECT 2,'b'
UNION ALL SELECT 3,'c'
UNION ALL SELECT 4,'d'
UNION ALL SELECT 5,'e'
UNION ALL SELECT 6,'f'
UNION ALL SELECT 7,'g'
;
INSERT INTO #exdat (id,dval,dimp)
SELECT 1,'A','p1'
UNION ALL SELECT 2,'B','p3'
UNION ALL SELECT 3,'C','p1'
UNION ALL SELECT 4,'D','p2'
UNION ALL SELECT 5,'E','p2'
UNION ALL SELECT 6,'F','p3'
UNION ALL SELECT 7,'w','p2'
UNION ALL SELECT 7,'g','p3'
UNION ALL SELECT 7,'G','p1'
UNION ALL SELECT 7,'z','p4'
;
UPDATE #test
SET
t.val = e.dval
FROM #test as t
CROSS APPLY
(
SELECT TOP(1) * FROM #exdat as cae
WHERE t.id = cae.id
ORDER BY cae.dimp
) as e
;
SELECT * FROM #test;
If you use this method your results would look like this:
Result:
1 A
2 B
3 C
4 D
5 E
6 F
7 G
You can modify this by changing the order by in your CROSS APPLY

UPDATE #test
SET t.val = e.dval
FROM #test t
JOIN #exdat e
ON t.id = e.id
JOIN
( SELECT id
, MIN(dimp) AS dimp --orders by dimp ascending
FROM #exdat
WHERE dval = UPPER(dval) --keeps only rows with capital letters in dval
GROUP BY id
) AS g
ON e.id = g.id
AND e.dimp = g.dimp

I think using the ranking functions can be put to good use here.
In this sample I used DENSE_RANK to pick the highest dimp value by ORDER BY dimp and then looking for the one had the rank of one.
WITH cte
AS (SELECT Dense_rank() OVER (PARTITION BY id ORDER BY dimp) AS foo,
*
FROM #exdat)
UPDATE #test
SET val = e.dval
FROM #test t
INNER JOIN cte e
ON t.id = e.id
WHERE foo = 1;

Related

Merge three tables in Select query by rule 3, 2, 1 records from each table

Merge three tables in a Select query by rule 3, 2, 1 records from each table as follows:
TableA: ID, FieldA, FieldB, FieldC,....
TableB: ID, FieldA, FieldB, FieldC,....
TableC: ID, FieldA, FieldB, FieldC,....
ID : auto number in each table
FieldA will be unique in all three tables.
I am looking for a Select query to merge three tables as follows:
TOP three records from TableA sorted by ID
TOP two records from TableB sorted by ID
TOP 1 record from TableC sorted by ID
Repeat this until select all records from all three tables.
If some table has fewer records or does not meet the criteria, ignore that and continue with others.
My attempt:
I did it totally through programming way, like cursors and If conditions inside a SQL Server stored procedure.
It makes delay.
This requires a formula that takes row numbers from each table and transforms it into a series of integers that skips the desired values.
In the query below, I am adding some CTE for the sake of shortening the formula. The real magic is in the UNION. Also, I am adding an additional field for your control. Feel free to get rid of it.
WITH A_Aux as (
SELECT 'A' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableA.*
FROM TableA
), B_Aux AS (
SELECT 'B' As FromTable, ROW_NUMBER() OVER (ORDER BY ID) AS RowNum, TableB.*
FROM TableB
), C_Aux AS (
SELECT 'C' As FromTable, ROW_NUMBER() OVER (Order BY ID) AS RowNum, TableC.*
FROM TableC
)
SELECT *
FROM (
SELECT RowNum+3*FLOOR((RowNum-1)/3) As ColumnForOrder, A_Aux.* FROM A_Aux
UNION ALL
SELECT 3+RowNum+4*FLOOR((RowNum-1)/2), B_Aux.* FROM B_Aux
UNION ALL
SELECT 6*RowNum, C_Aux.* FROM C_Aux
) T
ORDER BY ColumnForOrder
PS: note the pattern Offset + RowNum + (6-N) * Floor((RowNum-1)/N) to group N records together (it of course simplifies a lot for TableC).
PPS: I don't have a SQL server at hand to test it. Let me know if there is a syntax error.
You may try this..
GO
select * into #temp1 from (select * from table1) as t1
select * into #temp2 from (select * from table2) as t2
select * into #temp3 from (select * from table3) as t3
select * into #final from (select col1, col2, col3 from #temp1 where 1=0) as tb
declare #i int
set #i=1
while( (select COUNT(*) from #temp1)>#i)
Begin
;with ct1 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp1
),ct2 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp2
),ct3 as (
select ROW_NUMBER() over (order by id) as Slno, * from #temp3
),cfinal as (
select top 3 * from #temp1
union all
select top 2 * from #temp2
union all
select top 1 * from #temp3
)
insert into #final ( col1 , col2, col3 )
select col1, col2, col3 from cfinal
delete from #temp1 where id in (select top 3 ID from #temp1)
delete from #temp2 where id in (select top 2 ID from #temp2)
delete from #temp3 where id in (select top 1 ID from #temp3)
set #i = #i+1
End
Select * from #final
Drop table #temp1
Drop table #temp2
Drop table #temp3
GO
First create temp table for all 3 tables with each insert delete the inserted record and this will result you the desired result, if nothing is missing from my side.
Please see to this if this works.
There is not a lot of information to go with here, but I assume you can use UNION to combine multiple statements.
SELECT * TableA ORDER BY ID DESC OFFSET 3 ROWS
UNION
SELECT * TableB ORDER BY ID DESC OFFSET 2 ROWS
UNION
SELECT * TableC ORDER BY ID DESC OFFSET 1 ROWS
Execute and see if this works.
/AF
From my understanding, I create three temp tables as ta, tb, tc.
select * into #ta from (
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
union all
select 'A' a
) a
select * into #tb from (
select 'B' b
union all
select 'B'
union all
select 'B'
union all
select 'B'
union all
select 'B'
) b
select * into #tc from (
select 'C' c
union all
select 'C'
union all
select 'C'
union all
select 'C'
union all
select 'C'
) c
If tables match you tables, then the output looks like A,A,A,B,B,C,A,A,A,B,B,C,A,B,C,C,C
T-SQL
declare #TAC int = (select count (*) from #ta) -- Table A Count = 7
declare #TBC int = (select count (*) from #tb) -- Table B Count = 5
declare #TAR int = #TAC % 3 -- Table A Reminder = 1
declare #TBR int = #TBC % 2 -- Table B Reminder = 1
declare #TAQ int = (#TAC - #TAR) / 3 -- Table A Quotient = (7 - 1) / 3 = 2, is will passed on NTILE
-- So we gonna split as two group (111), (222)
declare #TBQ int = (#TBC - #TBR) / 2 -- Table B Quotient = (5 - 1) / 2 = 2, is will passed on NTILE
-- So we gonna split as two group (11), (22)
select * from (
select *, NTILE (#TAQ) over ( order by a) FirstOrder, 1 SecondOrder from (
select top (#TAC - #TAR) * from #ta order by a
) ta -- 6 rows are obtained out of 7.
union all
select *, #TAQ + 1, 1 from (
select top (#TAR) * from #ta order by a desc
) ta -- Remaining one row is obtained. Order by desc is must
-- Here FirstOrder is next value of previous value.
union all
select *, NTILE (#TBQ) over ( order by b), 2 from (
select top (#TBC - #TBR) * from #tb order by b
) tb
union all
select *, #TBQ + 1, 2 from (
select top (#TBR) * from #tb order by b desc
) tb
union all
select *, ROW_NUMBER () over (order by c), 3 from #tc
) abc order by FirstOrder, SecondOrder
Let me explain the T-SQL:
Before that, FYR: NTILE and Row Number
Get the count.
Find the Quotient which will pass to NTILE function.
Order by the NTILE value and static.
Note:
I am using SQL Server 2017.
If T-SQL works fine, then you need to change the column in order by <yourcolumn>.

Can I delete multiple of nth rows in a single query from a table in SQL?

I want to delete multiple of 4 from my table which have thousands of record. How can I do it?
Ex:-
Table1
1 a
2 b
3 c
4 d
5 e
6 f
7 g
8 h
9 i
I want to delete every 4th row.
I don't want to use a loop or cursor.
Delete A from
(
Select *,Row_Number() Over(Order By Id) as RN from TableA
) A
where RN%4=0
SQL Fiddle Link
Try this...
delete from table_name where (col1 % 4) = 0
Use a CTE.
WITH cte AS (
SELECT t.*, ROW_NUMBER() OVER (ORDER BY t.rowfield) AS rank
FROM Table1 t)
SELECT rowfield, fielda
FROM cte
WHERE rank%4 != 0
Output
rowfield fielda
1 a
2 b
3 c
5 e
6 f
7 g
9 i
SQL Fiddle: http://sqlfiddle.com/#!6/c9540b/13/0
Once you are happy with the output use DELETE FROM.
This can use an index if one exists and uses numbers table
;with cte
as
(select n from numbers
where n%4=0
)
delete t
from table1 t
join
cte c
on c.n=t.id
Try this
DECLARE #nvalToDelete varchar(100)='4,7,3,8,9'-- just give values to delete from table
DECLARE #temp TABLE
(
valtodelete VARCHAR(100)
)
DECLARE #Deletetemp TABLE
(
valtodelete INT
)
INSERT INTO #temp
SELECT #nvalToDelete
INSERT INTO #Deletetemp
SELECT split.a.value('.', 'VARCHAR(1000)') AS valToDelete
FROM (SELECT Cast('<S>' + Replace(valtodelete, ',', '</S><S>')
+ '</S>' AS XML) AS valToDelete
FROM #temp) AS A
CROSS apply valtodelete.nodes('/S') AS Split(a)
DECLARE #Table1 TABLE
(
ID INT,
val varchar(10)
)
INSERT INTO #Table1
SELECT 1,'a' UNION ALL
SELECT 2,'b' UNION ALL
SELECT 3,'c' UNION ALL
SELECT 4,'d' UNION ALL
SELECT 5,'e' UNION ALL
SELECT 6,'f' UNION ALL
SELECT 7,'g' UNION ALL
SELECT 8,'h' UNION ALL
SELECT 9,'i'
SELECT *
FROM #Table1;
WITH cte
AS (SELECT *,
RN = Row_number()
OVER (
ORDER BY id )
FROM #Table1)
DELETE FROM #Table1
WHERE id IN(SELECT id FROM cte
WHERE rn IN (SELECT CASt(valToDelete AS INT) FROM #Deletetemp)
)
SELECT *
FROM #Table1

get child count and list of records under that

I have two tables which are like below.
declare #Table1 table ( ID int, ColName varchar(20), ParentID int)
insert into #Table1
select 1,'Rec1',-1
union
select 2,'Rec2',1
union
select 3,'Rec3',1
union
select 4,'Rec4',2
declare #Table2 table ( ColID int, Name varchar(10),ID int)
insert into #Table2
select 10,'asdf',1
union
select 20,'atsdf',2
union
select 30,'asurdf',3
union
select 40,'ahfsdf',4
union
select 50,'aesdf',2
union
select 60,'asdsfgf',1
union
select 70,'asdfdf',3
I want to show top level parent count and under that related records like Parent and Child count.
The second table data should come under first table parent id, next Child from First table, count of child and second table records, third child, count of Child and related records from second table ..
ID Name Count
1 Rec1 6
10 asdf NULL
20 atsdf NULL
2 Rec2 3
50 aesdf NULL
60 asdsfgf NULL
SELECT id,colname,cnt FROM (
SELECT tab1.id,
tab1.colname,
COUNT (*) cnt,
tab1.id sortorder
FROM #Table1 tab1 INNER JOIN #Table2 tab2 ON tab1.id = tab2.id
GROUP BY tab1.id, tab1.colname
UNION ALL
SELECT colid,
name,
NULL,
id
FROM #Table2
) sub
ORDER BY sortorder, id

CTE for Count the Binary Tree nodes

i have the table structure like this :
Create table #table(advId int identity(1,1),name nvarchar(100),Mode nvarchar(5),ReferId int )
insert into #table(name,Mode,ReferId)values('King','L',0)
insert into #table(name,Mode,ReferId)values('Fisher','L',1)
insert into #table(name,Mode,ReferId)values('Manasa','R',1)
insert into #table(name,Mode,ReferId)values('Deekshit','L',2)
insert into #table(name,Mode,ReferId)values('Sujai','R',2)
insert into #table(name,Mode,ReferId)values('Fedric','L',3)
insert into #table(name,Mode,ReferId)values('Bruce','R',3)
insert into #table(name,Mode,ReferId)values('paul','L',4)
insert into #table(name,Mode,ReferId)values('walker','R',4)
insert into #table(name,Mode,ReferId)values('Diesel','L',5)
insert into #table(name,Mode,ReferId)values('Jas','R',5)
insert into #table(name,Mode,ReferId)values('Edward','L',6)
insert into #table(name,Mode,ReferId)values('Lara','R',6)
select *from #table
How do to write the CTE for count the Binary tree nodes on level basis.
Here is the example,
now, what I want to do is if I'm going to calculate the Count of the downline nodes. Which means I want to calculate for '1' so the resultset which I'm expecting
count level mode
1 1 L
1 1 R
2 2 L
2 2 R
4 3 L
2 3 R
How do I achieve this, I have tried this
with cte (advId,ReferId,mode,Level)
as
(
select advId,ReferId,mode,0 as Level from #table where advid=1
union all
select a.advId,a.ReferId,a.mode ,Level+1 from #table as a inner join cte as b on b.advId=a.referId
)
select *From cte order by Level
You should use aggregate SQL as a last statement (Group by Lebvel,mode):
with cte (advId,mode,Level)
as
(
select advId,mode,0 as Level from table1 where advid=1
union all
select a.advId,a.mode ,b.Level+1 from table1 as a
inner join cte as b on b.advId=a.referId
)
select count(*),Level,mode From cte
where Level<>0
group by level,mode
order by level,mode
SQLFiddle demo
Finally i have created stored procedure
declare #advId int,#L_advId int,#R_advId int,#level int,#L_count int ,#R_count int, #startId int, #EndId int;
set #advId=1;
with cte (advId,ReferId,mode,Level)
as
(
select advId,ReferId,mode,0 as Level from #table where advId=#advId
union all
select a.advId,a.ReferId,a.mode ,Level+1 from #table as a inner join cte as b on b.advId=a.ReferId
)
select distinct top 1 #EndId= Level from cte order by Level desc;
set #startId =0;
while (#startId<#EndId)
begin
with cte (advId,mode,Level)
as
(
select advId,mode,0 as Level from #table where advid=#advId
union all
select a.advId,a.mode ,b.Level+1 from #table as a
inner join cte as b on b.advId=a.referId
)
select #L_advId=[L],#R_advId=[R],#level=[Level] from(select advId,mode,[level] from cte )up pivot (sum(advId) for mode in([L],[R])) cte where level=1;
with cte (advId,mode,Level)
as
(
select advId,mode,0 as Level from #table where advid=#L_advId
union all
select a.advId,a.mode ,b.Level+1 from #table as a
inner join cte as b on b.advId=a.referId
)
select #L_count=COUNT(*) From cte where level=#startId ;
with cte (advId,mode,Level)
as
(
select advId,mode,0 as Level from #table where advid=#R_advId
union all
select a.advId,a.mode ,b.Level+1 from #table as a
inner join cte as b on b.advId=a.referId
)
select #R_count=COUNT(*) From cte where level=#startId ;
select #L_count as LCount ,#R_count as Rcount ,#startId as level
set #startId=#startId+1;
end
i have stored that data into dummy table and i solved .
Thank you

SQL Select Condition Question

I have a quick question about a select statement condition.
I have the following table with the following items. What I need to get is the object id that matches both type id's.
TypeId ObjectId
1 10
2 10
1 11
So I need to get both object 10 because it matches type id 1 and 2.
SELECT ObjectId
FROM Table
WHERE TypeId = 1
AND TypeId = 2
Obviously this doesn't work because it won't match both conditions for the same row. How do I perform this query?
Also note that I may pass in 2 or more type id's to narrow down the results.
Self-join:
SELECT t1.ObjectId
FROM Table AS t1
INNER JOIN Table AS t2
ON t1.ObjectId = t2.ObjectId
AND t1.TypeId = 1
AND t2.TypeId = 2
Note sure how you want the behavior to work when passing in values, but that's a start.
I upvoted the answer from #Cade Roux, and that's how I would do it.
But FWIW, here's an alternative solution:
SELECT ObjectId
FROM Table
WHERE TypeId IN (1, 2)
GROUP BY ObjectId
HAVING COUNT(*) = 2;
Assuming uniqueness over TypeId, ObjectId.
Re the comment from #Josh that he may need to search for three or more TypeId values:
The solution using JOIN requires a join per value you're searching for. The solution above using GROUP BY may be easier if you find yourself searching for an increasing number of values.
This code is written with Oracle in mind. It should be general enough for other flavors of SQL
select t1.ObjectId from Table t1
join Table t2 on t2.TypeId = 2 and t1.ObjectId = t2.ObjectId
where t1.TypeId = 1;
To add additional TypeIds, you just have to add another join:
select t1.ObjectId from Table t1
join Table t2 on t2.TypeId = 2 and t1.ObjectId = t2.ObjectId
join Table t3 on t3.TypeId = 3 and t1.ObjectId = t3.ObjectId
join Table t4 on t4.TypeId = 4 and t1.ObjectId = t4.ObjectId
where t1.TypeId = 1;
Important note: as you add more joins, performance will suffer a LOT.
In regards to Bill's answer you can change it to the following to get rid of the need to assume uniqueness:
SELECT ObjectId
FROM (SELECT distinct ObjectId, TypeId from Table)
WHERE TypeId IN (1, 2)
GROUP BY ObjectId
HAVING COUNT(*) = 2;
His way of doing it scales better as the number of types gets larger.
Try this
Sample Input:(Case 1)
declare #t table(Typeid int,ObjectId int)
insert into #t
select 1,10 union all select 2,10 union all
select 1,11
select * from #t
Sample Input:(Case 2)
declare #t table(Typeid int,ObjectId int)
insert into #t
select 1,10 union all select 2,10 union all
select 3,10 union all select 4,10 union all
select 5,10 union all select 6,10 union all
select 1,11 union all select 2,11 union all
select 3,11 union all select 4,11 union all
select 5,11 union all select 1,12 union all
select 2,12 union all select 3,12 union all
select 4,12 union all select 5,12 union all
select 6,12
select * from #t
Sample Input:(Case 3)[Duplicate entries are there]
declare #t table(Typeid int,ObjectId int)
insert into #t
select 1,10 union all select 2,10 union all
select 1,10 union all select 2,10 union all
select 3,10 union all select 4,10 union all
select 5,10 union all select 6,10 union all
select 1,11 union all select 2,11 union all
select 3,11 union all select 4,11 union all
select 5,11 union all select 1,12 union all
select 2,12 union all select 3,12 union all
select 4,12 union all select 5,12 union all
select 6,12 union all select 3,12
For case 1, the output should be 10
For case 2 & 3, the output should be 10 and 12
Query:
select X.ObjectId from
(
select
T.ObjectId
,count(ObjectId) cnt
from(select distinct ObjectId,Typeid from #t)T
where T.Typeid in(select Typeid from #t)
group by T.ObjectId )X
join (select max(Typeid) maxcnt from #t)Y
on X.cnt = Y.maxcnt