CTE for Count the Binary Tree nodes - sql

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

Related

How to get last record from Master-Details tables

I have a table that has 3 columns.
create table myTable
(
ID int Primary key,
Detail_ID int references myTable(ID) null, -- reference to self
Master_Value varchar(50) -- references to master table
)
this table has the follow records:
insert into myTable select 100,null,'aaaa'
insert into myTable select 101,100,'aaaa'
insert into myTable select 102,101,'aaaa'
insert into myTable select 103,102,'aaaa' ---> last record
insert into myTable select 200,null,'bbbb'
insert into myTable select 201,200,'bbbb'
insert into myTable select 202,201,'bbbb' ---> last record
the records is saved In the form of relational with ID and Detail_ID columns.
I need to select the last record each Master_Value column. follow output:
lastRecordID Master_Value Path
202 bbbb 200=>201=>202
103 aaaa 100=>101=>102=>103
tips:
The records are not listed in order in the table.
I can not use the max(ID) keyword. beacuse data is not sorted.(may
be the id column updated manually.)
attempts:
I was able to Prepare follow query and is working well:
with Q as
(
select ID ,Detail_ID, Master_Value , 1 RowOrder, CAST(id as varchar(max)) [Path] from myTable where Detail_ID is null
union all
select R.id,R.Detail_ID , r.Master_Value , (q.RowOrder + 1) RowOrder , (q.[Path]+'=>'+CAST(r.id as varchar(max))) [Path] from myTable R inner join Q ON Q.ID=R.Detail_ID --where r.Dom_ID_RowType=1010
)
select * into #q from Q
select Master_Value, MAX(RowOrder) lastRecord into #temp from #Q group by Master_Value
select
q.ID lastRecordID,
q.Master_Value,
q.[Path]
from #temp t
join #q q on q.RowOrder = t.lastRecord
where
q.Master_Value = t.Master_Value
but I need to simple way (one select) and optimal method.
Can anyone help me?
One method uses a correlated subquery to get the last value (which is how I interpreted your question):
select t.*
from mytable t
where not exists (select 1
from mytable t2
where t2.master_value = t.master_value and
t2.id = t.detail_id
);
This returns rows that are not referred to by another row.
For the path, you need a recursive CTE:
with cte as (
select master_value, id as first_id, id as child_id, convert(varchar(max), id) as path, 1 as lev
from mytable t
where detail_id is null
union all
select cte.master_value, cte.first_id, t.id, concat(path, '->', t.id), lev + 1
from cte join
mytable t
on t.detail_id = cte.child_id and t.master_value = cte.master_value
)
select cte.*
from (select cte.*, max(lev) over (partition by master_value) as max_lev
from cte
) cte
where max_lev = lev
Here is a db<>fiddle.

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

missing number or max number from the list

I am looking for missing number in the list, it works perfectly fine but when it start from 2, would it be possible to get 1. in below insertion
it should provide 1 not 4. please help thanks
drop table #temp
create table #temp
(
Number INT
)
insert into #temp
(Number)
select 2 union all
select 3 union all
select 5
SELECT MIN(t1.Number) + 1 AS MissingNumber
FROM #temp t1
LEFT OUTER JOIN #temp t2 ON (t1.Number + 1 = t2.Number)
WHERE t2.Number IS NULL
I will suggest you to create a separate numbers table to do this.
There a many ways to create number table. Check this link for more info
SELECT TOP (1000) n = Row_number()OVER (ORDER BY number)
INTO #numbers
FROM [master]..spt_values
ORDER BY n;
CREATE TABLE #temp
(Number INT)
INSERT INTO #temp(Number)
SELECT 2
UNION ALL
SELECT 3
UNION ALL
SELECT 5
SELECT Min(t1.n) AS MissingNumber
FROM #numbers t1
LEFT OUTER JOIN #temp t2
ON ( t1.n = t2.Number )
WHERE t2.Number IS NULL
I think its not possible because join doesn't knows that number starts from 1. it will search for min value.
we can use while loop to solve problem
use this
IF OBJECT_ID('Tempdb..#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp ( Number INT )
INSERT INTO #temp
( Number )
VALUES ( 2 ),
( 3 ),
( 5 );
WITH cte
AS ( SELECT n = 1
UNION ALL
SELECT n + 1
FROM cte
WHERE n <= 100 --can be increased with OPTION ( MAXRECURSION {iteration value} ) at the end of the query
)
SELECT MIN(cte.n) AS MissingNumber
FROM cte
LEFT JOIN #temp t ON ( cte.n = t.Number )
WHERE t.Number IS NULL

SQL create table with integers

I want to have in a database a table with single column with integers (1,2,...) since it can be helpful for certain joins.
I came up with a solution using a loop. Is there a more efficient way of creating such a table?
My solution
CREATE TABLE #NUM
(
NUM int
)
DECLARE #i int=1
WHILE #i<10000
BEGIN
INSERT INTO #Temp
SELECT #i
SET #i = #i + 1
END
I will do this using a tally table
;WITH e1(n) AS
(
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL
SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
), -- 10
e2(n) AS (SELECT 1 FROM e1 CROSS JOIN e1 AS b), -- 10*10
e3(n) AS (SELECT 1 FROM e2 CROSS JOIN e2 AS b) -- 100*100
INSERT INTO #Temp
SELECT ROW_NUMBER() OVER (ORDER BY n) FROM e3 ORDER BY n;
check here for more info
SELECT
row_number() over (order by message_id) num
into #Table
from sys.messages
This is the same principle as the answer by NoDisplayName, but I think it is a little cleaner (my opinion):
;WITH TBL(ROW_NUM) AS (
SELECT 1 AS ROW_NUM
UNION ALL
SELECT ROW_NUM+1
FROM TBL
WHERE ROW_NUM < 100
)
SELECT ROW_NUMBER() OVER (ORDER BY T1.ROW_NUM) AS ROW_NUM
FROM TBL T1
JOIN TBL T2 ON 1=1
JOIN TBL T3 ON 1=1

Split a row on 2 or more rows depending on a column

I have a question
If I have one row that looks like this
|ordernumber|qty|articlenumber|
| 123125213| 3 |fffff111 |
How can I split this into three rows like this:
|ordernumber|qty|articlenumber|
| 123125213| 1 |fffff111 |
| 123125213| 1 |fffff111 |
| 123125213| 1 |fffff111 |
/J
You can use recursive CTE:
WITH RCTE AS
(
SELECT
ordernumber, qty, articlenumber, qty AS L
FROM Table1
UNION ALL
SELECT
ordernumber, 1, articlenumber, L - 1 AS L
FROM RCTE
WHERE L>0
)
SELECT ordernumber,qty, articlenumber
FROM RCTE WHERE qty = 1
SQLFiddleDEMO
EDIT:
Based on Marek Grzenkowicz's answer and MatBailie's comment, whole new idea:
WITH CTE_Nums AS
(
SELECT MAX(qty) n FROM dbo.Table1
UNION ALL
SELECT n-1 FROM CTE_Nums
WHERE n>1
)
SELECT ordernumber ,
1 AS qty,
articlenumber
FROM dbo.Table1 t1
INNER JOIN CTE_Nums n ON t1.qty >= n.n
Generating number from 1 to max(qty) and join table on it.
SQLFiddle DEMO
Here's a quick hack using an additional table populated with a number of rows suitable for the qty values you are expecting:
-- helper table
CREATE TABLE qty_splitter (qty int)
INSERT INTO qty_splitter VALUES (1)
INSERT INTO qty_splitter VALUES (2)
INSERT INTO qty_splitter VALUES (3)
INSERT INTO qty_splitter VALUES (4)
INSERT INTO qty_splitter VALUES (5)
....
-- query to produce split rows
SELECT t1.ordernumber, 1, t1.articlenumber
FROM table1 t1
INNER JOIN qty_splitter qs on t.qty >= qs.qty
You can do it using CTE
declare #t table (ordername varchar(50), qty int)
insert into #t values ('ord1',5),('ord2',3)
;with cte as
(
select ordername, qty, qty-1 n
from #t
union all
select ordername, qty, n-1
from cte
where n>0
)
select ordername,1
from cte
order by ordername
Also you can use option with master..spt_values system table.
SELECT t.ordernumber, o.qty, t.articlenumber
FROM dbo.SplitTable t CROSS APPLY (
SELECT 1 AS qty
FROM master..spt_values v
WHERE v.TYPE = 'P' AND v.number < t.qty
) o
However, for this purpose is preferable to use its own sequence table
See demo on SQLFiddle