Delete the same reference multiple columns rows safely? - sql

I need to find all the parent-children relationships, which are all linked to my primary column ID
How I can delete the same reference columns in the table? Let say for example,if I want to delete "Google", I have to delete "HP" and Intel first also the child of HP as well.
I have tried the below thus far, but that works with only one column.
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE parent1 IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = e.parent1
)
SELECT Name, Level, Path, Parent FROM tb

is this use full?
declare #tmp table (id int, Name varchar(10),Parent1 int,Parent2 int,Parent3 int,Parent4 int,Parent5 int)
insert into #tmp
SELECT 1,'Microsoft',NULL,NULL,NULL,NULL,NULL
union
SELECT 2,'Google',1,NULL,NULL,NULL,NULL
union
SELECT 3,'HP',NULL,2,NULL,NULL,NULL
union
SELECT 4,'Amazone',NULL,NULL,3,NULL,NULL
union
SELECT 5,'FB',NULL,NULL,NULL,4,NULL
union
SELECT 6,'Yahoo',NULL,NULL,NULL,4,NULL
union
SELECT 7,'Intel',NULL,NULL,2,NULL,NULL
union
SELECT 8,'Apple',7,5,NULL,NULL,NULL
select * from #tmp
;with name_tree as (
select *
from #tmp
where id = 2
union all
select c.*
from #tmp c
join name_tree p on (p.id = c.parent1 or p.id = c.parent2 or p.id = c.parent3 or p.id = c.parent4 or p.id = c.parent5)
)
delete from t
from #tmp t
JOIN name_tree c on t.id=c.id
select * from #tmp

I haven't provided an actual solution to your issue here. But I would recommend investigating recursive Common Table Expressions. That should allow you to find all the parent records, then you can run a delete on them.
https://technet.microsoft.com/en-us/library/ms186243(v=sql.105).aspx

You can simply modify the recursive CTE's where clause like in below query to get all rows that need to be deleted.
See live demo
create table krishtest (id int, name varchar(100), parent1 int, parent2 int)
insert into krishtest values
(1,'Microsoft', NULL, NULL),
(2,'Google',1,NULL),
(3,'HP',NULL,2),
(4,'amazon',3,NULL),
(5,'FB',NULL,4),
(6,'yahoo',3,NULL),
(7,'cisco',6,NULL)
;
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE -- COALESCE(parent1,parent2) IS NULL
name ='HP'
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = COALESCE(e.parent1,e.parent2)
)
--delete FROM krishtest where id in( select id from tb)
--select * from krishtest
SELECT Name, Level, Path, Parent FROM tb

Related

How can I create a trigger to unpack json data?

This is the simplest reproducible example I can come up with. I would like to create a materialized view which is managed by triggers. This MV extracts some data from a json column, which could have an unbounded number of values in an array. Since we don't yet have json_table I am extracting these in a recursive CTE.
create or replace table base_data(
id int auto_increment not null primary key,
record_data longtext not null check (json_valid(record_data))
);
insert into base_data(record_data)
values
('{"x":1, "y":[2,3,4]}')
;
create or replace table mv_integers as
with recursive array_values as (
select
json_extract(b.record_data, '$.y[*]') val
, b.id
, json_length(json_extract(b.record_data, '$.y[*]')) n
from base_data b
), rec_elems as (
select av.id, 0 as i, json_extract(av.val, '$[0]') elem
from array_values av
where av.n > 0
union all
select av.id, e.i + 1, json_extract(av.val, concat('$[', e.i + 1, ']'))
from array_values av
inner join rec_elems e on av.id = e.id
where (i + 1) < av.n
)
select r.id record_id, r.elem extracted_integer, 'y' as 'comes_from'
from rec_elems r
union all
select b.id, json_value(b.record_data, '$.x'), 'x'
from base_data b
;
create or replace trigger maintain_mv_integers
after insert on base_data for each row
insert mv_integers(record_id, extracted_integer, comes_from)
with recursive array_values as (
select
json_extract(new.record_data, '$.y[*]') val
, new.id
, json_length(json_extract(new.record_data, '$.y[*]')) n
), rec_elems as (
select av.id, 0 as i, json_extract(av.val, '$[0]') elem
from array_values av
where av.n > 0
union all
select av.id, e.i + 1, json_extract(av.val, concat('$[', e.i + 1, ']'))
from array_values av
inner join rec_elems e on av.id = e.id
where (i + 1) < av.n
)
select r.id record_id, r.elem extracted_integer, 'y' as 'comes_from'
from rec_elems r
union all
select new.id, json_value(new.record_data, '$.x'), 'x'
;
insert into base_data(record_data)
values
('{"x":5, "y":[6,7,4]}')
;
What the code does:
Create a table
Populate that table with some data
Create a materialized view from that data
Create a trigger to maintain the materialized view going forward
Insert some new data to see if the trigger works
The code fails on the last step. The error is Unknown column 'record_data' in 'NEW'. If I comment out the bit before the union all and just leave the last bit which doesn't involve a CTE, then the code works. Does this mean that CTEs are not supported in triggers? I hope not and I can't find any documentation that supports this conclusion.
My version is 10.5.8-MariaDB-1:10.5.8+maria~bionic running on WSL2 Ubuntu.
So how to get this to work?
I have come up with a workaround. All I did was put the logic of the trigger into a stored procedure and call it from the trigger. That fact that this works and the original does not, suggests that this is either a bug or undocumented behavior.
UPDATE: this is now a confirmed bug.
create or replace table base_data(
id int auto_increment not null primary key,
record_data longtext not null check (json_valid(record_data))
);
insert into base_data(record_data)
values
('{"x":1, "y":[2,3,4]}')
;
create or replace table mv_integers as
with recursive array_values as (
select
json_extract(b.record_data, '$.y[*]') val
, b.id
, json_length(json_extract(b.record_data, '$.y[*]')) n
from base_data b
), rec_elems as (
select av.id, 0 as i, json_extract(av.val, '$[0]') elem
from array_values av
where av.n > 0
union all
select av.id, e.i + 1, json_extract(av.val, concat('$[', e.i + 1, ']'))
from array_values av
inner join rec_elems e on av.id = e.id
where (i + 1) < av.n
)
select r.id record_id, r.elem extracted_integer, 'y' as 'comes_from'
from rec_elems r
union all
select b.id, json_value(b.record_data, '$.x'), 'x'
from base_data b
;
create or replace procedure sp_maintain_mv_integers(
in record_id int unsigned,
in json_data longtext
)
insert mv_integers(record_id, extracted_integer, comes_from)
with recursive array_values as (
select
json_extract(json_data, '$.y[*]') val
, record_id id
, json_length(json_extract(json_data, '$.y[*]')) n
), rec_elems as (
select av.id, 0 as i, json_extract(av.val, '$[0]') elem
from array_values av
where av.n > 0
union all
select av.id, e.i + 1, json_extract(av.val, concat('$[', e.i + 1, ']'))
from array_values av
inner join rec_elems e on av.id = e.id
where (i + 1) < av.n
)
select r.id record_id, r.elem extracted_integer, 'y' as 'comes_from'
from rec_elems r
union all
select record_id, json_value(json_data, '$.x'), 'x';
create or replace trigger tg_maintain_mv_integers
after insert on base_data for each row
call sp_maintain_mv_integers(new.id, new.record_data);
insert into base_data(record_data)
values
('{"x":5, "y":[6,7,4]}')
;

Get all children and grandchildren from either of the two parent columns

I need to find all the parent-children relationships, which are all linked to my primary column ID
I have tried the below thus far, but that works with only one column.
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE parent1 IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = e.parent1
)
SELECT Name, Level, Path, Parent FROM tb
As i understand the parent can be in either parent1 or parent 2 column; then in such case your query should be as below
The only change is use of keyword COALSECE which takes the first non NULL value from the list.
bug assumption is that both parent1 and parent2 are not (Non-Null) together.
See live demo
;
WITH tb (id,Name, Level, Path, Parent)
AS
(
SELECT
id,Name, 1 AS Level,
CAST('/'+Name as nvarchar(max)) as Path,
CAST(NULL as nvarchar(max)) as Parent
FROM krishtest
WHERE COALESCE(parent1,parent2) IS NULL
UNION All
SELECT
e.id,e.Name, x.Level + 1 AS Level, x.Path + '/' + e.Name as Path,
REVERSE(SUBSTRING( REVERSE(x.[Path]) ,0 , CHARINDEX( '/', REVERSE(x.[Path])) )) as [Parent]
FROM krishtest e
JOIN tb x ON x.id = COALESCE(e.parent1,e.parent2)
)
SELECT Name, Level, Path, Parent FROM tb

SQL Server 2014 with clause inside function/procedure

Is it possible to make a user defined function /user defined procedure with the "with" clause inside it?
CREATE FUNCTION udf_UsersComments (
#Id INT
)
RETURNS #UsersComments TABLE (
CommentTextFormatted NVARCHAR(MAX),
DateCommented NVARCHAR(MAX),
Username NVARCHAR(255),
ParentCommentId INT,
Id INT
)
AS
BEGIN
WITH UpperHierarchy (Id, QuestionId, CommentText, ParentCommentId, DateCommented, UserId, HierarchyOrder,
lineage)
AS (SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
0 AS HierarchyOrder,
Cast ('/' AS VARCHAR(255)) AS Lineage
FROM Comments AS com
WHERE com.ParentCommentId IS NULL AND IsDeleted=0
UNION ALL
(SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
HierarchyOrder + 1,
Cast(lineage + Ltrim(Str(com.ParentCommentId, 6, 0))
+ '/' AS VARCHAR(255))
FROM Comments AS com
INNER JOIN UpperHierarchy AS parent
ON com.ParentCommentId = parent.Id
WHERE com.IsDeleted=0))
SELECT CommentTextFormatted, DateCommented, U.Username, ParentCommentId, Com.id
FROM Questions AS Q
INNER JOIN
(SELECT Space(HierarchyOrder*5) + CommentText AS CommentTextFormatted, Id, QuestionId, ParentCommentId, DateCommented, UserId, lineage
FROM UpperHierarchy) AS Com
ON Com.QuestionId=Q.Id
INNER JOIN Users AS U
ON U.Id=Com.UserId
WHERE Q.Id=#Id
ORDER BY lineage + Ltrim(Str(Q.Id, 6, 0))
RETURN
END
GO
And I am getting this error
Msg 444, Level 16, State 2, Procedure udf_UsersComments, Line 13
Select statements included within a function cannot return data to a
client.
Make it as a Inline table valued function. Check this question to know why I chose inline instead of multi line table valued function
CREATE FUNCTION udf_UsersComments (
#Id INT
)
RETURNS TABLE
AS
Return(
WITH UpperHierarchy (Id, QuestionId, CommentText, ParentCommentId, DateCommented, UserId, HierarchyOrder,
lineage)
AS (SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
0 AS HierarchyOrder,
Cast ('/' AS VARCHAR(255)) AS Lineage
FROM Comments AS com
WHERE com.ParentCommentId IS NULL AND IsDeleted=0
UNION ALL
(SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
HierarchyOrder + 1,
Cast(lineage + Ltrim(Str(com.ParentCommentId, 6, 0))
+ '/' AS VARCHAR(255))
FROM Comments AS com
INNER JOIN UpperHierarchy AS parent
ON com.ParentCommentId = parent.Id
WHERE com.IsDeleted=0))
SELECT CommentTextFormatted, DateCommented, U.Username, ParentCommentId, Com.id,ordercol = lineage + Ltrim(Str(Q.Id, 6, 0))
FROM Questions AS Q
INNER JOIN
(SELECT Space(HierarchyOrder*5) + CommentText AS CommentTextFormatted, Id, QuestionId, ParentCommentId, DateCommented, UserId, lineage
FROM UpperHierarchy) AS Com
ON Com.QuestionId=Q.Id
INNER JOIN Users AS U
ON U.Id=Com.UserId
WHERE Q.Id=#Id)
Note, I have added another column in result to do the ordering while selecting the function. You cannot use Order by without TOP inside a function
select CommentTextFormatted, DateCommented, Username, ParentCommentId, id
from udf_UsersComments(1)--some id
order by ordercol
Regarding your original issue, you are missing insert into #UsersComments. CTE select should insert the records into #UsersComments

Using CTE to assign level code to item how to retrieve the last item that already in the trail string

I am using the following query to assign level code to item.
;with C ( Ingredient_Item_No,
Lvl,
Trail)
as (
select
Matl.Ingredient_Item_No,
2 as Lvl,
CAST(('/' + Matl.Ingredient_Item_No + '/') as varchar(max)) as Trail
from Materials as Matl
and Matl.Product_Item_No = Lvl.Item_No
where Lvl.Level_Code = 1
union all
select
Matl.Ingredient_Item_No,
C.Lvl + 1,
C.trail + CAST((Matl.Ingredient_Item_No +'/') as varchar(max))
from NVA_Work_Rollup_BOM_Materials as Matl
inner join C
on Matl.Product_Item_No = C.Ingredient_Item_No
where CHARINDEX(CAST((Matl.Ingredient_Item_No + '/') as varchar(max)), C.trail) = 0
)
select * from C
The "Material" table structure is like this:
Product Ingredient
A B
B C
D E
E F
C A
The Level_Code have a list of item that already been assigned with level 1.
I use the trial column to store the ingredient hierarchy. Whenever the ingredient is already in the trail for the item, I will not assign it with another level. But that also means there is bad record that not supposed to be in the material table. For example, if I have A (1) -> B (2) -> C (3) -> A, A could not be the ingredient of the product C since it is a low level product. it also means that the pair (c (product), A (ingredient)) is a wrong record that need to be take out from 'Material' table. My problem is that I could use the trail to keep track the right order. But how could I retrieve the last pair that with wrong order, such as C -> A?
Edit:
Here is what table 'Lvl' Looks like
item_no level_Code
A 1
B 1
Any help will be appreciated!
Updated answer: Sorry, that was rather buggy.
With some horribly named Level i stuff:
-- Sample data.
declare #Material as Table ( Product VarChar(10), Ingredient VarChar(10) );
insert into #Material ( Product, Ingredient ) values
( 'A', 'B' ), ( 'B', 'C' ), ( 'D', 'E' ), ( 'E', 'F' ), ( 'C', 'A' );
--select * from #Material;
declare #Lvl as Table ( ImALevel1Product VarChar(10) );
insert into #Lvl ( ImALevel1Product ) values ( 'A' ), ( 'B' );
select *
from #Material as M left outer join
#Lvl as L on L.ImALevel1Product = M.Product;
declare #False as Bit = 0, #True as Bit = 1;
-- Run through the hierarchy looking for loops.
with Children as (
select Product, Ingredient,
Convert( VarChar(4096), '|' + Convert( VarChar(10), Product ) + '|' ) as Path, #False as Loop
from #Material
where Product in ( select ImALevel1Product from #Lvl )
union all
select Child.Product, Child.Ingredient,
Convert( VarChar(4096), Path + Convert( VarChar(10), Child.Product ) + '|' ),
case when Path like '%|' + Convert( VarChar(10), Child.Ingredient ) + '|%' then #True else #False end
from #Material as Child inner join
Children as Parent on Parent.Ingredient = Child.Product
where Parent.Loop = 0 )
select *
from Children
option ( MaxRecursion 0 )

Comma Separated SQL Server Result Set 'JOINED' with other Columns

I have a table say ProjectMaster:
Id ProjectName
1 A
2 B
3 C
another table ProjectMeter
Id ProjectId MeterNumber
1 1 #0001
2 1 #0002
3 1 #0003
4 2 #0004
5 2 #0005
6 3 #0006
I wish to have following output
ProjectName MeterNumbers
A #0001, #0002, #0003
B #0004, #0005
C #0006
I tried this and this, but unable to solve my problem.
I cannot use a table variable.
I have a already written Stored Procedure and it brings data from many joined tables. ProjectMaster also happens to be joined in one of these tables. Now am required to fetch data from ProjectMeter, such that, each row has concatenated ProjectMeter.MeterNumber corresponding to the ProjectId in that column.
right now, I get concatenated list of all meternumbers in all the rows.
I cannot use CURSOR, TABLE variable , Temp TABLE
( I hope still something can be done to my cause)
please help.....
Try this:
SELECT projectname, STUFF((SELECT distinct ', ' + meternumber
from projectmeter m
where p.id = m.projectid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'') MeterNumbers
from projectmaster p
See SQL Fiddle with Demo
DECLARE #ProjectMaster AS TABLE
(
ID INT IDENTITY(1, 1) ,
ProjectName VARCHAR(2)
)
DECLARE #ProjectMeter AS TABLE
(
ID INT IDENTITY(1, 1) ,
ProjectID INT ,
MeterNumber VARCHAR(50)
)
INSERT INTO #ProjectMaster
( ProjectName )
VALUES ( 'A' )
INSERT INTO #ProjectMeter
( ProjectID, MeterNumber )
VALUES ( 1, '#0001' )
INSERT INTO #ProjectMeter
( ProjectID, MeterNumber )
VALUES ( 1, '#0002' )
SELECT pMaster.ID, STUFF(( SELECT ',' + MeterNumber
FROM #ProjectMeter
FOR
XML PATH('')
), 1, 1, '') AS 'Concat Result'
FROM #ProjectMeter pMeter
INNER JOIN #ProjectMaster pMaster ON pMaster.ID = pMeter.ProjectID
GROUP BY pMaster.ID
I have used table variables here but surely you just need to drop the #'s as I have used the same table names as you have specified? Not sure if this is okay? :)
Also in MS SQL you can do it using recursive query with CTE.
Here is a SQLFiddle demo
;with t1 as (
select t.*,
cast(meternumber as varchar(max)) as m2,
0 as level
from ProjectMeter t
where not exists
(select id
from ProjectMeter l
where l.id<t.id and l.ProjectId=t.ProjectID
)
union all
select b.*,
cast(c.m2+','+b.MeterNumber as varchar(max)) as m2,
c.level+1 as level
from ProjectMeter b
inner join t1 c
on (c.id < b.id) and (b.ProjectID=c.ProjectId)
)
select pm.ProjectName as ProjectName,
t1.m2 as MeterNumbers
from t1
inner join
(select ProjectId,max(level) ml
from t1
group by ProjectId
) t2
on (t1.ProjectId=t2.ProjectID) and (t1.level=t2.ml)
left join ProjectMaster pm
on (t1.ProjectId=pm.Id)
order by t1.ProjectID