Search code examples
sqlsql-servert-sqlcommon-table-expression

Recursively adding data from scratch


Suppose I have a table that looks like this (using C# for example):

public class Table {
    public Guid Id {get; set;}
    public string Name {get; set;}
    public Guid ParentId {get; set;}
}

This is a recursive table where a row can have a linked parent in the same table.

Assuming I am starting from scratch (i.e. there is no data to select from originally from other tables), what is the best way to insert new rows of up to depth X via TSQL?

Suppose I want the data to look something like this:

Intended data model

The purpose of this script is for load testing - thus I don't have any data I can select from. Data is continually going to be added, deleted, and added again. Most of what I've found are either SELECT CTE's or INSERT after SELECT.

I'm sure an alternative is to write a console app to do this for me (admittedly my SQL game is not that great), but I'd like to see if there's a way to do this in SQL.


Solution

  • There is nothing special (necessarily) about this data. You can script inserts as a sql insert statement. For example, using int instead of guid ID values for simplicity here:

    create table Scratch (
        id int,
        [name] nvarchar(100),
        parentId int, 
        primary key (id))
    
    alter table Scratch add constraint fk_temp_id 
        foreign key (parentId) references Scratch(id)
    
    insert into Scratch (id, [Name], ParentId)
    values 
        (1, 'a', null),
        (2, 'b', 1),
        (3, 'c', 1),
        (4, 'd', 2),
        (5, 'e', 2),
        (6, 'f', 3),
        (7, 'g', 3)
    

    That being said, here is how you could create the data with a loop up to N levels deep - but more than 63 levels will overflow the bigint sequence values (and this could start to take a bit of time as number of levels increases - just 20 levels will be a million rows).

    create table dbo.Scratch (
        id uniqueidentifier,
        [name] nvarchar(100),
        parentId uniqueidentifier, 
        seq int,
        parentSeq int,
        primary key (id));
    
    alter table dbo.Scratch add constraint fk_temp_id 
        foreign key (parentId) references Scratch(id);
    
    -- seed first row
    insert into dbo.Scratch (id, [name], parentId, seq, parentSeq) values (newid(), '1', null, 1, null);
    
    -- build binary tree with int ids
    declare @I int = 0;
    declare @J int = 0;
    declare @K int;
    declare @LEVELS int = 3; --> To be set as needed, using zero-based counting
    declare @next_node bigint = 2;
    while @I < @LEVELS-1
    begin
        set @I += 1;
        set @J = 0;
        set @K = cast(power(2, @I) as bigint)
        while @J < @K / 2
        begin
            set @J += 1
            insert into dbo.Scratch(id, [name], ParentId, seq, parentSeq)
                values 
                    (newid(), cast(@next_node as nvarchar(100)), null, @next_node, @next_node / 2),
                    (newid(), cast(@next_node+1 as nvarchar(100)), null, @next_node+1, @next_node / 2);
            set @next_node += 2;
        end
    end     
    
    -- update the parent guids
    update b
    set b.parentId = a.id
    from dbo.Scratch a
    inner join dbo.Scratch b
    on a.seq = b.ParentSeq
    
    -- drop the temporary columns needed for creating the data
    --alter table dbo.Scratch drop column parentSeq;
    --alter table dbo.Scratch drop column seq;
    
    --select * from dbo.scratch order by seq;
    

    Result:

    id name parentId seq parentSeq
    46b16f14-20b9-4388-a7f2-832e2c56215d 1 1
    a613b692-138d-4207-8e84-43cccd0d2476 2 46b16f14-20b9-4388-a7f2-832e2c56215d 2 1
    0ff8b67a-63fe-4125-b2b9-bb962d67f526 3 46b16f14-20b9-4388-a7f2-832e2c56215d 3 1
    f27f82b6-d1c1-4cb2-810d-4fa21ffef5bc 4 a613b692-138d-4207-8e84-43cccd0d2476 4 2
    346fd62a-6727-4414-975b-2b1659ee40c9 5 a613b692-138d-4207-8e84-43cccd0d2476 5 2
    a8d3928a-6357-47ef-b1d0-0be76fa9233f 6 0ff8b67a-63fe-4125-b2b9-bb962d67f526 6 3
    1c669bdf-63e1-44af-858b-752003d9a980 7 0ff8b67a-63fe-4125-b2b9-bb962d67f526 7 3
    6e7ac3b1-921e-4ddc-a0c7-ad9499992689 8 f27f82b6-d1c1-4cb2-810d-4fa21ffef5bc 8 4
    6786d550-7df8-4ecd-b7ca-6b7658b91b68 9 f27f82b6-d1c1-4cb2-810d-4fa21ffef5bc 9 4
    d3113177-cc37-4d55-8b2d-b603345837a8 10 346fd62a-6727-4414-975b-2b1659ee40c9 10 5
    bb32c1c2-9f75-455e-9aa1-fc38ddd25a6c 11 346fd62a-6727-4414-975b-2b1659ee40c9 11 5
    1a218245-b6d4-41a7-8117-1b3d0bd2fdd9 12 a8d3928a-6357-47ef-b1d0-0be76fa9233f 12 6
    fe8f1882-a89a-4201-83f4-b72e528ba9da 13 a8d3928a-6357-47ef-b1d0-0be76fa9233f 13 6
    edab3434-c038-406e-bcf0-b6139feff6c0 14 1c669bdf-63e1-44af-858b-752003d9a980 14 7
    19007b10-e86f-4fe4-9c0f-8e739f4a5c89 15 1c669bdf-63e1-44af-858b-752003d9a980 15 7