FK on same table - sql

I have table with ID who is PK and I have in same table column ParentID who is in some cases ID's.(I say some cases because for example ParentID can be 0 but this ID not have in ID's column).
I try to create Query:
ID ParentID Value
1 0 Test
2 0 Test01
3 2 Test02
4 3 Test03
And when I send ID=4 I need this result:
ID ParentID Value
2 0 Test01
3 2 Test02
4 3 Test03
Any help...

This looks like a tree traversal problem to me, which doesn't lend itself particularly well to set-based operations. The following works, but it's not too elegant:
-- turn this into stored proc?
declare #inputId int
set #inputId = 4
declare #Ids table (ID int)
insert into #Ids values (#inputId)
declare #reccount int
set #reccount = 1
declare #lastreccount int
set #lastreccount = 0
while #reccount <> #lastreccount
begin
set #lastreccount = #reccount
insert into #Ids
select ParentID from recursiveTest
where ID in (select ID from #Ids)
and ParentID not in (select ID from #Ids)
set #reccount = (select COUNT(*) from #Ids)
end
select * from recursiveTest where ID in (select ID from #Ids);

Don't use 0 (zero) but null to signal "no parent present".

try this:
Declare #Id Integer Set #Id = 4
With Child(Id, ParentId, Value, Level) As
( Select Id, ParentId, Value, 0 as Level
From table
Where id = #Id
Union All
Select t.Id, t.ParentId, t.Value, level + 1
From Child c Join table t
On t.Id = c.ParentId
)
Select id. ParentId, Value, Level
From Child
Order By Id

Related

Insert into table using a select statement and a while loop

I need to insert values into a table from another table. I also want to use a while loop to update a row in my table at the same time. Below you can see my query.
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from SAMPLES
select #id = #id + 1
end
This works but not as i was expecting. Instead of giving me three rows affected, it gives me three rows affected, three times. So i end up with nine new rows instead of the desired three i want.
ID_PRODUCT PRODUCTID PRODUCTDESC COUNT
35746 136559 Desc1 1
35747 276732 Desc2 1
35748 259910 Desc3 1
35746 136559 Desc1 2
35747 276732 Desc2 2
35748 259910 Desc3 2
35746 136559 Desc1 3
35747 276732 Desc2 3
35748 259910 Desc3 3
What i want to acheive is this :
ID_PRODUCT PRODUCTID PRODUCTDESC COUNT
35746 136559 Desc1 1
35747 276732 Desc2 2
35748 259910 Desc3 3
Can anyone see what im doing wrong?
The SELECT part of Insert statement always returns same records
#id does not change the selected rows, only inserts same data set with different #id values
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from SAMPLES
You are inserting three times same list of rows with different #id.
I guess you actually mean this:
;with DistinctSampleValues as
(
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC
from SAMPLES
)
insert into [dbo].[TEST] ([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT])
select
ID_PRODUCT,PRODUCTID,PRODUCTDESC,
ROW_NUMBER() OVER(ORDER BY ID_PRODUCT) RN
from DistinctSampleValues
one time insert all distinct values with additional "row number".
You are close. Add top 1 and where clause like ID_PRODUCT not in Test table
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
SELECT DISTINCT TOP 1
ID_PRODUCT,
PRODUCTID,
PRODUCTDESC,
#id
from
SAMPLES S
WHERE
S.ID_PRODUCT NOT IN
(
SELECT T.ID_PRODUCT FROM [TEST] T
)
select #id = #id + 1
end
Eralper and Ivan Starostin both answered correctly and it is right solution for you.
To run your code correctly, you need to add WHERE clause.
declare #id int
select #id = 1
while #id >=1 and #id <= 3
begin
INSERT INTO [dbo].[TEST]
([ID_PRODUCT],[PRODUCTID],[PRODUCTDESC],[COUNT]
select distinct
ID_PRODUCT,PRODUCTID,PRODUCTDESC,#id
from
SAMPLES s
WHERE Count = #id
AND NOT EXISTS(SELECT 1 FROM Test t WHERE t.ID_PRODUCT = s.ID_PRODUCT
AND t.PRODUCTID = s.PRODUCTID)
select #id = #id + 1
end

How to traverse a path in a table with id & parentId?

Suppose I have a table like:
id | parentId | name
1 NULL A
2 1 B
3 2 C
4 1 E
5 3 E
I am trying to write a scalar function I can call as:
SELECT dbo.GetId('A/B/C/E') which would produce "5" if we use the above reference table. The function would do the following steps:
Find the ID of 'A' which is 1
Find the ID of 'B' whose parent is 'A' (id:1) which would be id:2
Find the ID of 'C' whose parent is 'B' (id:2) which would be id:3
Find the ID of 'E' whose parent is 'C' (id:3) which would be id:5
I was trying to do it with a WHILE loop but it was getting very complicated very fast... Just thinking there must be a simple way to do this.
CTE version is not optimized way to get the hierarchical data. (Refer MSDN Blog)
You should do something like as mentioned below. It's tested for 10 millions of records and is 300 times faster than CTE version :)
Declare #table table(Id int, ParentId int, Name varchar(10))
insert into #table values(1,NULL,'A')
insert into #table values(2,1,'B')
insert into #table values(3,2,'C')
insert into #table values(4,1,'E')
insert into #table values(5,3,'E')
DECLARE #Counter tinyint = 0;
IF OBJECT_ID('TEMPDB..#ITEM') IS NOT NULL
DROP TABLE #ITEM
CREATE TABLE #ITEM
(
ID int not null
,ParentID int
,Name VARCHAR(MAX)
,lvl int not null
,RootID int not null
)
INSERT INTO #ITEM
(ID,lvl,ParentID,Name,RootID)
SELECT Id
,0 AS LVL
,ParentId
,Name
,Id AS RootID
FROM
#table
WHERE
ISNULL(ParentId,-1) = -1
WHILE ##ROWCOUNT > 0
BEGIN
SET #Counter += 1
insert into #ITEM(ID,ParentId,Name,lvl,RootID)
SELECT ci.ID
,ci.ParentId
,ci.Name
,#Counter as cntr
,ch.RootID
FROM
#table AS ci
INNER JOIN
#ITEM AS pr
ON
CI.ParentId=PR.ID
LEFT OUTER JOIN
#ITEM AS ch
ON ch.ID=pr.ID
WHERE
ISNULL(ci.ParentId, -1) > 0
AND PR.lvl = #Counter - 1
END
select * from #ITEM
Here is an example of functional rcte based on your sample data and requirements as I understand them.
if OBJECT_ID('tempdb..#Something') is not null
drop table #Something
create table #Something
(
id int
, parentId int
, name char(1)
)
insert #Something
select 1, NULL, 'A' union all
select 2, 1, 'B' union all
select 3, 2, 'C' union all
select 4, 1, 'E' union all
select 5, 3, 'E'
declare #Root char(1) = 'A';
with MyData as
(
select *
from #Something
where name = #Root
union all
select s.*
from #Something s
join MyData d on d.id = s.parentId
)
select *
from MyData
Note that if you change the value of your variable the output will adjust. I would make this an inline table valued function.
I think I have it based on #SeanLange's recommendation to use a recursive CTE (above in the comments):
CREATE FUNCTION GetID
(
#path VARCHAR(MAX)
)
/* TEST:
SELECT dbo.GetID('A/B/C/E')
*/
RETURNS INT
AS
BEGIN
DECLARE #ID INT;
WITH cte AS (
SELECT p.id ,
p.parentId ,
CAST(p.name AS VARCHAR(MAX)) AS name
FROM tblT p
WHERE parentId IS NULL
UNION ALL
SELECT p.id ,
p.parentId ,
CAST(pcte.name + '/' + p.name AS VARCHAR(MAX)) AS name
FROM dbo.tblT p
INNER JOIN cte pcte ON
pcte.id = p.parentId
)
SELECT #ID = id
FROM cte
WHERE name = #path
RETURN #ID
END

How to create loop based on value of row?

I have problem when I use my query bellow to have a looping inside the cursor.
data in table1 will be like this:
id | data
----|---------
A | 4
B | 2
C | 5
the result in table2 should be like this:
id | data
----|---------
A | 1
A | 1
A | 1
A | 1
B | 1
B | 1
C | 1
C | 1
C | 1
C | 1
C | 1
I have SQL query with cursor like this:
DECLARE #table2 table ( id VARCHAR(500), data INTEGER)
DECLARE Cur CURSOR FOR
SELECT id, data FROM table1
OPEN Cur
WHILE ( ##FETCH_STATUS = 0 )
BEGIN
DECLARE #LoopNum INTEGER
DECLARE #tempID VARCHAR(255)
DECLARE #tempDATA INTEGER
FETCH NEXT FROM Cur INTO #tempID, #tempDATA
set #LoopNum = 0
WHILE #LoopNum < #tempDATA
BEGIN
INSERT INTO table2 (id, data)
VALUES( #tempID, 1)
SET #LoopNum = #LoopNum + 1
END
END
CLOSE Cur
DEALLOCATE Cur
SELECT * FROM table2
but the query didn't work. is there something wrong with my query?
Thank you.
Use this query to the expected result.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test VALUES ('A',4)
INSERT #test VALUES('B',2)
INSERT #test VALUES('C',5);
SELECT s.id, 1 AS data
FROM #test s
INNER JOIN
master.dbo.spt_values t ON t.type='P'
AND t.number BETWEEN 1 AND s.data
Note: Refer this Why (and how) to split column using master..spt_values?
You actually don't need a loop
IF OBJECT_ID('TEMPDB..#TEMP') IS NOT NULL
DROP TABLE #TEMP
SELECT 'A' AS ID, 4 AS DATA
INTO #TEMP UNION
SELECT 'B', 2 UNION
SELECT 'C', 5
;WITH CTE AS
(
SELECT 1 AS NUMBER
UNION ALL
SELECT NUMBER + 1
FROM CTE
WHERE NUMBER < 100
)
SELECT T.ID, 1
FROM CTE C
INNER JOIN #TEMP T
ON C.NUMBER <= T.DATA
ORDER BY T.ID
Carefull that if you want ot generate a large set of numbers in the CTE it may become slower.
Use a Recursive CTE which will help you to loop through the records.
CREATE TABLE #test
(id CHAR(1),data INT)
INSERT #test
VALUES ('A',4),('B',2),('C',5);
WITH cte
AS (SELECT 1 AS da,id,data
FROM #test a
UNION ALL
SELECT da + 1,id,data
FROM cte a
WHERE da < (SELECT data
FROM #test b
WHERE a.id = b.id))
SELECT id,
1 AS data
FROM cte
ORDER BY id
i used two loops
1. for each row
2. for number for duplicate insert
SET NOCOUNT on;
DECLARE #t table(row int IDENTITY(1,1),id varchar(10),data int)
INSERT INTO #t
SELECT * from xyz
DECLARE #x table(id varchar(10),data int) --table to hold the new data
DECLARE #i int=(SELECT count (*) from xyz) --number of rows main table
DECLARE #y int --number of duplicate
DECLARE #p int=1 --number of rows
WHILE #i!=0 --loop until last row of main table
BEGIN
SET #y=(SELECT data FROM #t WHERE row=#p) --set #y for number of 'row duplicate'
WHILE #y!=0
BEGIN
INSERT INTO #x
SELECT id,1
FROM #t
WHERE row=#p
SET #y=#y-1
END
SET #p=#p+1
SET #i=#i-1
END
SELECT * FROM #x

SQL Server: Sum of childs column (sum only until no negative value left for childs)

again i have got a small (i hope small) problem.
I have got a parent child hirarchy where one parent can have multiple childs and a child can have again multiple childs and so on.
every parent and child has a amount (value) and a parent can compensate for any missing amount of the childrens.
Here my Table:
CREATE TABLE #Test
(
ID INTEGER NOT NULL
,ParentID INTEGER
,NAME VARCHAR(20)
,value INTEGER
)
Testdata:
INSERT INTO #Test
( ID, ParentID, NAME, value )
VALUES ( 1, NULL, 'MainStore', 1 )
, ( 2, 1, 'Substore1', 3 )
, ( 3, 1, 'Substore2', 10 )
, ( 4, 2, 'Sub1Substore1', -1 )
, ( 5, 2, 'Sub1Substore2', 1 )
, ( 6, 3, 'Sub2Substore1', 10 )
To get the parentchild realationship displayed ive tried it with an CTE:
;WITH CTE
AS ( SELECT ID
,ParentID
,Name
,Value
,0 AS LEVEL
,CAST('' AS INTEGER) AS ID_Parent
FROM #Test
WHERE ParentID IS NULL
UNION ALL
SELECT child.ID
,child.ParentID
,child.Name
,child.Value
,parent.Level + 1
,parent.ID
FROM CTE parent
JOIN #Test child ON child.ParentID = parent.ID
)
As you can see Substore1 has 2 childrens (Sub1Substore1 and Sub1Substore2) Substore1 hast a value of 3, Sub1Substore1 -1 and Sub1Substore2 has 1.
Sub1Substore1 is a child of Substore1 and the parent can compensate for missing values of the childs.
My desired output should look like this:
ID ParentID Name Value LEVEL ID_Parent FreeValues
----------- ----------- -------------------- ----------- ----------- ----------- -----------
1 NULL MainStore 1 0 0 1
2 1 Substore1 3 1 1 2
3 1 Substore2 10 1 1 8
4 2 Sub1Substore1 -1 2 2 0
5 2 Sub1Substore2 1 2 2 1
6 3 Sub2Substore1 -2 2 3 0
Sadly the SQL Fiddle Website is not working for me at the moment but i will provide this sample later on SQL Fiddle.
EDIT: Rewrote whole answer due to misunderstandment of the task.
This might be solvable elegantly with common table expression, but because CTE lacks support for multiple recursive references, this task seemed to became way too complex for me to handle.
However, here's a bit less elegant solution that should do the trick for you. Note that I made an assumption that parent's ID is always smaller than it's direct childrens'. This might become an issue if you should be able to change already inserted row's parent "on-the-fly". Anyway, here you go:
--Declare temp table.
DECLARE #Temp TABLE
(
ID INTEGER NOT NULL
,ParentID INTEGER
,NAME VARCHAR(20)
,value INTEGER
,FreeValues INTEGER
,NeedFromParent INTEGER
,ChildrenNeed INTEGER
);
--Other variables
DECLARE #ID INTEGER
DECLARE #ParentID INTEGER
DECLARE #Name VARCHAR(20)
DECLARE #value INTEGER
DECLARE #FreeValues INTEGER
DECLARE #NeedFromParent INTEGER
DECLARE #ChildrenNeed INTEGER
--Loop with cursor to calculate FreeValues
DECLARE cur CURSOR FOR SELECT id, parentId, Name, Value FROM #test
ORDER BY ID DESC -- NOTE! Assumed that Parent's ID < Child's ID.
OPEN cur
FETCH NEXT FROM cur INTO #ID, #ParentID, #Name, #value
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #ChildrenNeed = CASE
WHEN SUM(temp.NeedFromParent) IS NULL THEN 0
ELSE SUM(temp.NeedFromParent) END
FROM #temp temp WHERE temp.ParentID=#ID AND temp.NeedFromParent > 0
IF #ChildrenNeed IS NULL SET #ChildrenNeed = 0
IF #Value - #ChildrenNeed < 0
SET #NeedFromParent = #Value - #ChildrenNeed
ELSE
SET #NeedFromParent = 0
SET #NeedFromParent = -#NeedFromParent
IF #NeedFromParent = 0
SET #FreeValues = #value - #ChildrenNeed
ELSE
SET #FreeValues = 0
INSERT INTO #Temp
VALUES(#ID, #ParentID, #Name, #value, #FreeValues, #NeedFromParent, #ChildrenNeed)
FETCH NEXT FROM cur INTO #ID, #ParentID, #Name, #value
END
CLOSE cur;
DEALLOCATE cur;
-- Join with recursively calculated Level.
;WITH CTE
AS ( SELECT ID ,ParentID,0 AS [Level]
FROM #Test WHERE ParentID IS NULL
UNION ALL
SELECT child.ID,child.ParentID,parent.Level + 1
FROM CTE parent INNER JOIN #Test child ON child.ParentID = parent.ID
)
SELECT t1.ID, t1.ParentID, t1.Name, t1.Value, cte.[Level], t1.FreeValues
FROM CTE cte LEFT JOIN #temp t1 ON t1.ID = cte.ID
ORDER BY ID

Writing a recursive SQL query on a self-referencing table

I have a database with a table called Items, that contains these columns:
ID - primary key, uniqueidentifier
Name - nvarchar(256)
ParentID - uniqueidentifier
The name field can be used to build out a path to the item, by iterating through each ParentId until it equals '11111111-1111-1111-1111-111111111111', which is a root item.
So if you had a table that had rows like
ID Name ParentID
-------------------------------------------------------------------------------------
11111111-1111-1111-1111-111111111112 grandparent 11111111-1111-1111-1111-111111111111
22222222-2222-2222-2222-222222222222 parent 11111111-1111-1111-1111-111111111112
33333333-3333-3333-3333-333333333333 widget 22222222-2222-2222-2222-222222222222
So if I looked up an item with id '33333333-3333-3333-3333-333333333333' in the example above, i'd want the path
/grandparent/parent/widget
returned. i've attempted to write a CTE, as it looks like that's how you'd normally accomplish something like this - but as I don't do very much SQL, I can't quite figure out where i'm going wrong. I've looked at some examples, and this is as close as I seem to be able to get - which only returns the child row.
declare #id uniqueidentifier
set #id = '10071886-A354-4BE6-B55C-E5DBCF633FE6'
;with ItemPath as (
select a.[Id], a.[Name], a.ParentID
from Items a
where Id = #id
union all
select parent.[Id], parent.[Name], parent.ParentID
from Items parent
inner join ItemPath as a
on a.Id = parent.id
where parent.ParentId = a.[Id]
)
select * from ItemPath
I have no idea how i'd declare a local variable for the path and keep appending to it in the recursive query. i was going to try to at least get all the rows to the parent before going after that. if anyone could help with that as well - i'd appreciate it.
well here's working solution
SQL FIDDLE EXAMPLE
declare #id uniqueidentifier
set #id = '33333333-3333-3333-3333-333333333333'
;with ItemPath as
(
select a.[Id], a.[Name], a.ParentID
from Items a
where Id = #id
union all
select parent.[Id], parent.[Name] + '/' + a.[Name], parent.ParentID
from ItemPath as a
inner join Items as parent on parent.id = a.parentID
)
select *
from ItemPath
where ID = '11111111-1111-1111-1111-111111111112'
I don't like it much, I think better solution will be to do it other way around. Wait a minute and I try to write another query :)
UPDATE here it is
SQL FIDDLE EXAMPLE
create view vw_Names
as
with ItemPath as
(
select a.[Id], cast(a.[Name] as nvarchar(max)) as Name, a.ParentID
from Items a
where Id = '11111111-1111-1111-1111-111111111112'
union all
select a.[Id], parent.[Name] + '/' + a.[Name], a.ParentID
from Items as a
inner join ItemPath as parent on parent.id = a.parentID
)
select *
from ItemPath
and now you can use this view
declare #id uniqueidentifier
set #id = '33333333-3333-3333-3333-333333333333'
select *
from vw_Names where Id = #id
I needed a slightly different version of this answer as I wanted to produce a list of all lineages in the tree. I also wanted to know the depth of each node. I added a temporary table of top level parents that I could loop through and a temporary table to build the result set.
use Items
Select *
Into #Temp
From Items
where ParentID=0
Declare #Id int
create table #Results
(
Id int,
Name nvarchar(max),
ParentId int,
Depth int
)
While (Select Count(*) From #Temp) > 0
Begin
Select Top 1 #Id = Id From #Temp
begin
with ItemPath as
(
select a.[Id], cast(a.[Name] as nvarchar(max))as Name, a.ParentID ,1 as
Depth
from Items a
where a.ID = #id
union all
select a.[Id], parent.[Name] + '/' + a.[Name], a.ParentID, 1 + Depth
from Items as a
inner join ItemPath as parent on parent.id = a.parentID
)
insert into #Results
select *
from ItemPath
end
Delete #Temp Where Id = #Id
End
drop table #Temp
select * from #Results
drop table #Results
If we start from the following table...
Id Name ParentID
1 Fred 0
2 Mary 0
3 Baker 1
4 Candle 2
5 Stick 4
6 Maker 5
We would get this result table.
Id Name ParentID Depth
1 Fred 0 1
2 Mary 0 1
3 Fred/Baker 1 2
4 Mary/Candle 2 2
5 Mary/Candle/Stick 4 3
6 Mary/Candle/Stick/Maker 5 4