I have a tree of strings stored in a sql table, this is the definition for the table.
CREATE TABLE [FileTree] (
[ID] INT NOT NULL,
[Name] VARCHAR (MAX) NOT NULL,
[ParentID] INT NULL,
[UserID] VARCHAR (MAX) NOT NULL
);
And I have a recursive delete procedure for deleting a node and it's children, it uses a cursor and it works perfectly well after a user on SO in my previous question regarding this matter pointed out the problems with the syntax. (Deleting Tree-Nodes in a SQL table)
CREATE PROCEDURE DeleteFile
#FileID INTEGER,
#UserID VARCHAR(MAX)
AS
BEGIN
DELETE FROM [FileTree] WHERE [ID] = #FileID AND [UserID]=#UserID;
IF EXISTS(SELECT * FROM [FileTree] WHERE [ParentID] = #FileID AND [UserID]=#UserID)
BEGIN
DECLARE FileCursor CURSOR LOCAL FOR
SELECT [ID],[UserID] FROM [FileTree] WHERE [ParentID] = #FileID AND [UserID]=#UserID;
OPEN FileCursor
FETCH NEXT FROM FileCursor INTO #FileID , #UserID
WHILE ##FETCH_STATUS =0
BEGIN
EXEC DeleteFile #FileID,#UserID;
FETCH NEXT FROM FileCursor INTO #FileID , #UserID ;
END
END
END
However , another reply to the question suggested using a Common Table Expression, I googled and I don't think I really understand how can a CTE replace the cursor in this procedure. Any suggestions ?
You can use a recursive CTE to get all children of the ID that should be deleted and then use that in a DELETE statement:
with all_ids as (
select id, ParentID
from FileTree
where id = 4 -- this is the root ID that should be deleted
union all
select c.id, c.ParentID
from FileTree c
join all_ids p on p.id = c.ParentID
)
delete from file_tree
where id in (select id from all_ids);
SQLFiddle example: http://sqlfiddle.com/#!3/ef474f/1
Related
I'm using SQL SERVER 2017 (Developper Edition 64 Bit) on a Windows 10 Machine. I'm trying to execute in a dynamic sql a batch multiples times through GO and it won't work.
But the sql Statement will work if it is not execute dynamically. The goal is to do it dynamically and I still don't figure out what I am doing wrong.
Here is how the definition tables look like:
ParentTable
(
Id uniqueidentifier DEFAULT (newsequentialid()) not null,
Created datetime not null,
Creator uniqueidentifier not null,
Modifier uniqueidentifier null,
Modified datetime null
)
ChildTable
(
Id uniqueidentifier DEFAULT (newsequentialid()) not null,
ParentTable_Id not null,
Created datetime not null,
Creator uniqueidentifier not null,
Modifier uniqueidentifier null,
Modified datetime null
)
This is what I've tried so far:
create Procedure InsertIntoChildTable
AS
BEGIN
DECLARE #countDset int
DECLARE #todaysdate datetime
DECLARE #UserName uniqueidentifier
DECLARE #ParentTable_Id uniqueidentifier
DECLARE #insertIntoChildTable nvarchar(max)
DECLARE #ChildTableName nvarchar(35)
SET #ChildTableName = ChildTable
SET #countDset = 6
SET #todaysdate = GETDATE()
SET #UserName = 'e86aacf4-9887-e911-9724-4439c492b2a7'
BEGIN TRY
BEGIN TRANSACTION
SET #insertIntoChildTable = 'INSERT INTO ' + #ChildTableName + '
(ParentTable_Id, Created, Creator, Modified, Modifier)
VALUES ( (select max(Id) from ParentTable) , #todaysdate, #UserName ,
NULL, NULL) ' + ' GO ' + #countDset
EXECUTE sp_executesql #insertIntoChildTable,N'#ChildTableName
nvarchar(35), #todaysdate datetime, #UserName uniqueidentifier,
#countDset int', #ChildTableName = #ChildTableName, #todaysdate =
#todaysdate, #UserName = #UserName, #countDset = #countDset
COMMIT TRANSACTION
END TRY
BEGIN CATCH
PRINT 'Could not insert in the Child table'
ROLLBACK TRANSACTION
RETURN
END CATCH
END
After the Line with 'Go ' + #countDset it will automatically go into the Catch block and return.
Thank you
INSERT INTO ChildTable ( ParentTable_Id, Created, Creator, Modified, Modifier )
SELECT (select max(Id) from ParentTable) as ParentId , #todaysdate, #UserName ,
NULL, NULL
FROM
(
SELECT TOP #countDset '' As Dummy
FROM sys.Objects As A
CROSS JOIN sys.Objects As B
) As Multiplier
Guided through the example Zohar posted I could insert new Rows in the ChildTable using CROSS JOIN
I have a database (lets call it DB) containing 150+ tables, (eg: table1, table2 etc.)
Problem
I want to loop through all tables and get count of rows by groups, as below
Current Approach
As of now I was thinking of appending all tables or doing so manually!
Table structure
name code
A code1
A code2
A code6
A code98
B code1
Expected Output
table_name name code count
table1 A code1 100
table1 B code2 941
table2 A code1 98
Code for each table
SELECT name, code, count (*) AS count
FROM table1
GROUP BY name, code
As you want result a single result set, below will work:
CREATE TABLE #Temp
(
tableName VARCHAR(100)
,name VARCHAR(10)
,code VARCHAR(10)
, [Count] INT
)
EXEC sys.sp_MSforeachtable #command1=" insert into #Temp select '?' AS tableName,Name,Code,count(*) from ? group by Name, Code"
SELECT * FROM #Temp
DROP TABLE #temp
Just as an alternative, using dynamic sql:
CREATE TABLE #Temp
(
tableName VARCHAR(100)
,name VARCHAR(10)
,Code VARCHAR(10)
, [Count] INT
)
DECLARE #TableName VARCHAR(100)
DECLARE tableCursor CURSOR FAST_FORWARD FOR
SELECT name FROM sys.tables WHERE type ='U'
OPEN tableCursor
FETCH NEXT FROM tableCursor INTO #TableName
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #Query NVARCHAR(MAX)= N'select #table as TableName, Name, Code,Count(*) as [Count] from '+ #tablename + ' Group by Name, Code'
INSERT INTO #Temp
EXEC sp_executesql #Query, N'#table varchar(100)', #table = #TableName
FETCH NEXT FROM tableCursor INTO #TableName
END
CLOSE tableCursor
DEALLOCATE tableCursor
SELECT * FROM #temp
DROP TABLE #temp
You can use the SP ForEachTable that will execute the code against each table stored in your database. Try something like this:
sp_MSforeachtable #command1="select '?' AS TABLE_NAME,count(*) from ?"
I am writing a procedure in SQL Server to insert or update records.
The update part of the code is working fine but when I am executing it for inserting, duplicate entries are inserted into the table.
I created the primary key to avoid this error but after creating that I am not able to insert any single record.
Here is the code :
Alter Procedure test_case
#id int,
#name nvarchar(20)
AS
If exists (Select t_id from testing2 where t_id = #id)
begin
update testing2
set t_id = #id, t_name = #name
where t_id = #id
end
else
begin
insert into testing2 (t_id, t_name, last_date, hard)
select
#id, #name, convert(date, getdate()), 'null'
from test
end
On executing it is showing 2 rows affected
You do not require test table in the select query
insert into testing2 (t_id, t_name, last_date, hard)
select
#id as t_id, #name as t_name, convert(date, getdate()) as last_date, 'null' as hard
is enough
I like to break functionality into smaller parts because it helps me to manage code better.
Maybe this is not a good example since it is pretty simple but I will write it anyway.
Create Procedure Testing2_InsertData (
#id int,
#name nvarchar(20)
) As
Set NoCount On
Insert Into testing2
(t_id, t_name, last_date, hard)
Values
( #id, #name, GetDate(), null )
Go
Create Procedure Testing2_UpdateData (
#id int,
#name nvarchar(20)
) As
Set NoCount On
Update testing2 Set
t_name = #name --, maybe last_date = GetDate()
Where ( t_id = #id )
Go
Create Procedure Testing2_SaveData (
#id int,
#name nvarchar(20)
) As
Set NoCount On
If ( Exists( Select t_id From testing2 Where ( t_id = #id ) ) )
Exec Testing2_UpdateData #id, #name
Else
Exec Testing2_InsertData #id, #name
Go
I am trying to write a recursive procedure that would delete the node and all it's children if they are such in the table. I tried doing the following
CREATE PROCEDURE DeleteFile
#FileID INTEGER,
#UserID VARCHAR(MAX)
AS
DELETE FROM [FileTree] WHERE [ID] = #FileID AND [UserID]=#UserID;
IF EXISTS(SELECT * FROM [FileTree] WHERE [ParentID] = #FileID AND [UserID]=#UserID)
BEGIN
DECLARE FileCursor CURSOR LOCAL FOR
SELECT [ID],[UserID] FROM [FileTree] WHERE [ParentID] = #FileID AND [UserID]=#UserID;
OPEN FileCursor
FETCH NEXT FROM FileCursor INTO #FileID , #UserID
WHILE ##FETCH_STATUS =0
BEGIN
EXEC DeleteFile #FileID,#UserID;
FETCH NEXT FROM FileCursor INTO #FileID , #UserID ;
END
END
ELSE
return
But for some reason this is not working. It deletes the node but the kids remain.
Table ,,design" .
CREATE TABLE [FileTree] (
[ID] INT IDENTITY NOT NULL,
[Name] VARCHAR (MAX) NOT NULL,
[ParentID] INT NULL,
[UserID] VARCHAR (MAX) NOT NULL
);
Can you please indicate the errors I made and suggest a working procedure ?
UPD: I made the cursor LOCAL and I am fetching one time before going into the while loop, it still does not delete all the children.
I think you have syntax problem.i fixed it like this
CREATE PROCEDURE DeleteFile
#FileID INTEGER,
#UserID VARCHAR(MAX)
AS
BEGAIN
DELETE FROM [FileTree] WHERE [ID] = #FileID AND [UserID]=#UserID;
IF EXISTS(SELECT * FROM [FileTree] WHERE [ParentID] = #FileID AND [UserID]=#UserID)
BEGIN
DECLARE FileCursor CURSOR LOCAL FOR
SELECT [ID],[UserID] FROM [FileTree] WHERE [ParentID] = #FileID AND [UserID]=#UserID;
OPEN FileCursor
FETCH NEXT FROM FileCursor INTO #FileID , #UserID
WHILE ##FETCH_STATUS =0
BEGIN
EXEC DeleteFile #FileID,#UserID;
FETCH NEXT FROM FileCursor INTO #FileID , #UserID ;
END
END
END
You can use a CTE and a temporary table:
WITH CTE (ID)
AS
(
SELECT ID FROM FileTree WHERE Id=#ID
UNION ALL
SELECT t.ID FROM FileTree t
INNER JOIN CTE c ON t.ParentId=c.Id
)
SELECT ID INTO #temp FROM CTE;
DELETE FROM FileTree WHERE ID IN(SELECT ID FROM #temp)
DROP TABLE #temp
I have a table with certain records, let's call this the Priority table. For each record in this table I need to execute a stored procedure called GetProductsBasedOnPriorityRecord which returns records from a Product table.
All these results need to be returned as one UNION without duplicates.
I'm able to write the following C# pseodo code, but I have no idea on how to do this in SQL:
//DECLARE resultVariable
var products = new List<Product>();
//SELECT * FROM Priority and FOREACH over this
foreach(var prio in Priority) {
//EXEC GetProductsBasedOnPriorityRecord and UNION this on the resultVariable
products.Union(GetProductsBasedOnPriorityRecord(prio));
}
//RETURN resultVariable
return products;
You should not be writing SQL code in loops if you need it to perform even relatively well. But this is what I would do in this case.
-- build some variables
declare #prio table (prio varchar(100) not null)
declare #singlePrio varchar(50)
declare #sqlcmd varchar(50)
-- assuming that the output of the stored procedure is just a table of INTs
declare #output table (outputID int not null)
-- build the list to loop over
insert into #prio (prio)
select * from [Priority]
while exists (select 1 from #prio)
begin
select #singlePrio = (select top 1 prio from #prio)
--
insert into #output exec GetProductsBasedOnPriorityRecord #singlePrio
--
delete from #prio where prio = #singlePrio
end
-- using distinct because you want a 'union' but didnt say 'union all'
select distinct outputID from #output
Well you should be able to do it with a cursor. It's not always the best way. I assume that you are forced to use the existing stored procedure.
Example tables
CREATE TABLE [dbo].[Products](
[ProductID] [int] IDENTITY(1,1) NOT NULL,
[ProductName] [nchar](10) NULL,
[PriorityID] [int] NULL)
CREATE TABLE [dbo].[Priority](
[PriorityID] [int] IDENTITY(1,1) NOT NULL,
[PriorityCode] [nchar](10) NULL)
(with PKs etc).
Sample stored procedure:
ALTER PROCEDURE [dbo].[GetProductsBasedOnPriorityRecord]
-- Add the parameters for the stored procedure here
#PriorityID int = 0
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT * FROM Products WHERE PriorityID = #PriorityID
END
Then something like this:
DECLARE #PriorityID INT
DECLARE #table TABLE (ProductID INT, ProductName NCHAR(10), PriorityID INT)
DECLARE cur CURSOR FOR SELECT PriorityID FROM Priority
OPEN cur
FETCH NEXT FROM cur INTO #PriorityID
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #table EXEC GetProductsBasedOnPriorityRecord #PriorityID
FETCH NEXT FROM cur INTO #PriorityID
END
CLOSE cur
DEALLOCATE cur
SELECT DISTINCT * FROM #table
Of course this makes some assumptions about your tables. Also (of course) a synthetic example like this is actually showing a long-winded way of doing something very simple.
Try this one -
DECLARE #SQL NVARCHAR(MAX)
IF OBJECT_ID (N'tempdb.dbo.#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp (outputID INT NOT NULL)
SELECT #SQL = (
SELECT CHAR(13) + '
INSERT INTO #temp(outputID)
EXEC dbo.GetProductsBasedOnPriorityRecord '''
+ prio + ''''
FROM dbo.[Priority]
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
PRINT #SQL
EXEC sys.sp_executesql #SQL
SELECT DISTINCT outputID
FROM #temp