I have a table Test which contains
TEST
----
tablename|columnvalue|rankofcolumn
A|C1|1
A|C2|2
A|C3|3
A|C4|4
B|CX1|1
B|CX2|2
C|CY1|1
C|CY2|2
C|CY3|3
I want to generate the path along with other columns as follows
RESULT
----
tablename|columnvalue|rankofcolumn|path
A|C1|1|C1
A|C2|2|C1->C2
A|C3|3|C1->C2->C3
A|C4|4|C1->C2->C3->C4
B|CX1|1|CX1
B|CX2|2|CX1->CX2
C|CY1|1|CY1
C|CY2|2|CY1->CY2
C|CY3|3|CY1->CY2->CY3
As per this question, I can use recursive CTE to achieve this
WITH r ( tablename, columnvalue, rankofcolumn, PATH ) AS
(SELECT tablename,
columnvalue,
rankofcolumn,
columnvalue
FROM test
WHERE rankofcolumn = 1
UNION ALL
SELECT xx.tablename,
xx.columnvalue,
xx.rankofcolumn,
r.PATH || '->' || xx.columnvalue
FROM r
JOIN test xx
ON xx.tablename = r.tablename
AND xx.rankofcolumn = r.rankofcolumn + 1)
SELECT *
FROM r;
But I am using WX2 database which lacks this option at the moment. Is there a SQL alternative for this?
You could do the brute-force approach with a table that you gradually populate. Assuming your test table looks something like:
create table test (tablename varchar2(9), columnvalue varchar2(11), rankofcolumn number);
then the result table could be created with:
create table result (tablename varchar2(9), columnvalue varchar2(11), rankofcolumn number,
path varchar2(50));
Then create the result entries for the lowest rank:
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn, t.columnvalue
from test t
where t.rankofcolumn = 1;
3 rows inserted.
And repeatedly add rows building on the highest existing rank, getting the following values (if there are any for that tablename) from the test table:
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn,
concat(concat(r.path, '->'), t.columnvalue)
from test t
join result r
on r.tablename = t.tablename
and r.rankofcolumn = t.rankofcolumn - 1
where t.rankofcolumn = 2;
3 rows inserted.
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn,
concat(concat(r.path, '->'), t.columnvalue)
from test t
join result r
on r.tablename = t.tablename
and r.rankofcolumn = t.rankofcolumn - 1
where t.rankofcolumn = 3;
2 rows inserted.
insert into result (tablename, columnvalue, rankofcolumn, path)
select t.tablename, t.columnvalue, t.rankofcolumn,
concat(concat(r.path, '->'), t.columnvalue)
from test t
join result r
on r.tablename = t.tablename
and r.rankofcolumn = t.rankofcolumn - 1
where t.rankofcolumn = 4;
1 row inserted.
And keep going for the maximum possible number of columns (i.e. highest rankofcolumn for any table). You may be able to do that procedurally in WX2, iterating until zero rows are inserted; but you've made it sound pretty limited.
After all those iterations the table now contains:
select * from result
order by tablename, rankofcolumn;
TABLENAME COLUMNVALUE RANKOFCOLUMN PATH
--------- ----------- ------------ --------------------------------------------------
A C1 1 C1
A C2 2 C1->C2
A C3 3 C1->C2->C3
A C4 4 C1->C2->C3->C4
B CX1 1 CX1
B CX2 2 CX1->CX2
C CY1 1 CY1
C CY2 2 CY1->CY2
C CY3 3 CY1->CY2->CY3
Tested in Oracle but trying to avoid anything Oracle-specific; might need tweaking for WX2 of course.
Related
I need to generate 500 records using CTE, my output table should be something like below
TEST
DESCRIPTION
ID
TEST1
DESC1
1
TEST2
DESC2
2
TEST3
DESC3
3
TEST4
DESC4
4
I struggling with how to create it with cte as it has to be wrapped inside view
i created temp table its working fine but i wasnt able to wrap it inside view
i tried the following code
with cte1 as (
CREATE TABLE #code1(TEST,DESCRIPTION)
declare #TEST int
set #TEST = 1
While #TEST <= 500
Begin
Insert Into #code1(TEST,DESCRIPTION)
values ('TEST' + CAST(#TEST as varchar(100)),'DESC' + CAST(#TEST as varchar(100)))
Print #TEST
Set #TEST = #TEST + 1
End
)
I think I am doing it in the wrong way, can anyone give me any suggestions here.
Your INSERT...SELECT syntax is all off. Start by creating the table normally, then INSERT the rows.
The best way to generate a lot of rows is to use a tally function. Itzik Ben-Gan's cross-join is great, here is a simplified version of it, good for up to 65,536 rows
WITH
L0 AS ( SELECT 1 AS c
FROM (VALUES(1),(1),(1),(1),(1),(1),(1),(1),
(1),(1),(1),(1),(1),(1),(1),(1)) AS D(c) ),
L1 AS ( SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B ),
L2 AS ( SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B ),
Nums AS ( SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L2 )
INSERT #code1 (TEST, DESCRIPTION, ID)
SELECT TOP(500)
CONCAT('TEST', rownum) AS TEST,
CONCAT('DESC', rownum) AS DESCRIPTION,
rownum AS ID
FROM Nums;
You can create a stored procedure to achieve a similar result:
SP Code:
DELIMITER $$
CREATE PROCEDURE Proc00()
BEGIN
CREATE TABLE code1 (TEST VARCHAR(20),DESCRIPTION VARCHAR(30),ID INT);
SET #TEST = 1;
WHILE #TEST <= 500 DO
INSERT INTO code1
VALUES (CONCAT('TEST',#TEST),CONCAT('DESC',#TEST),#TEST);
SET #TEST = #TEST + 1;
END WHILE;
SELECT * FROM code1;
END $$
DELIMITER ;
Then let's execute the stored procedure by using CALL() statement:
CALL Proc00();
It returns:
...............
Hello dear Stackoverflow SQL gurus.
Using this simple data model:
create table test(Id INT, Field1 char(1), Field2 varchar(max));
insert into test (id, Field1) values (1, 'a');
insert into test (id, Field1) values (2, 'b');
insert into test (id, Field1) values (3, 'c');
insert into test (id, Field1) values (4, 'd');
I'm able to update Field2 with Field1 and Field2 concatenated previous value in a simple TSQL anonymous block like this :
BEGIN
DECLARE #CurrentId INT;
DECLARE #CurrentField1 char(1);
DECLARE #Field2 varchar(max) = NULL;
DECLARE cur CURSOR FOR
SELECT id, Field1
FROM test
ORDER BY id;
OPEN cur
FETCH NEXT FROM cur INTO #CurrentId, #CurrentField1;
WHILE ##FETCH_STATUS = 0
BEGIN
SET #Field2 = CONCAT(#Field2, #CurrentId, #CurrentField1);
UPDATE test
SET Field2 = #Field2
WHERE Id = #CurrentId;
FETCH NEXT FROM cur INTO #CurrentId, #CurrentField1;
END
CLOSE cur;
DEALLOCATE cur;
END
GO
Giving me the desired result:
select * from test;
Id Field1 Field2
1 a 1a
2 b 1a2b
3 c 1a2b3c
4 d 1a2b3c4d
I want to achieved the same result with a single UPDATE command to avoid CURSOR.
I thought it was possible with the LAG() function:
UPDATE test set Field2 = NULL; --reset data
UPDATE test
SET Field2 = NewValue.NewField2
FROM (
SELECT CONCAT(Field2, Id, ISNULL(LAG(Field2,1) OVER (ORDER BY Id), '')) AS NewField2,
Id
FROM test
) NewValue
WHERE test.Id = NewValue.Id;
But this give me this:
select * from test;
Id Field1 Field2
1 a 1
2 b 2
3 c 3
4 d 4
Field2 is not correctly updated with Id+Field1+(previous Field2).
The update result is logic to me because when the LAG() function re-select the value in the table this value is not yet updated.
Do you think their is a way to do this with a single SQL statement?
One method is with a recursive Common Table Expression (rCTE) to iterate through the data. This assumes that all values of Id are sequential:
WITH rCTE AS(
SELECT Id,
Field1,
CONVERT(varchar(MAX),CONCAT(ID,Field1)) AS Field2
FROM dbo.test
WHERE ID = 1
UNION ALL
SELECT t.Id,
t.Field1,
CONVERT(varchar(MAX),CONCAT(r.Field2,t.Id,t.Field1)) AS Field2
FROM dbo.test t
JOIN rCTe r ON t.id = r.Id + 1)
SELECT *
FROM rCTe;
If they aren't sequential, you can use a CTE to row number the rows first:
WITH RNs AS(
SELECT Id,
Field1,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM dbo.Test),
rCTE AS(
SELECT Id,
Field1,
CONVERT(varchar(MAX),CONCAT(ID,Field1)) AS Field2,
RN
FROM RNs
WHERE ID = 1
UNION ALL
SELECT RN.Id,
RN.Field1,
CONVERT(varchar(MAX),CONCAT(r.Field2,RN.Id,RN.Field1)) AS Field2,
RN.RN
FROM RNs RN
JOIN rCTe r ON RN.RN = r.RN + 1)
SELECT Id,
Field1,
Field2
FROM rCTe;
Unfortunately, SQL Server does not (yet) support string_agg() as a window function.
Instead, you can use cross apply to calculate the values:
select t.*, t2.new_field2
from test t cross apply
(select string_agg(concat(id, field1), '') within group (order by id) as new_field2
from test t2
where t2.id <= t.id
) t2;
For an update:
with toupdate as (
select t.*, t2.new_field2
from test t cross apply
(select string_agg(concat(id, field1), '') within group (order by id) as new_field2
from test t2
where t2.id <= t.id
) t2
)
update toupdate
set field2 = new_field2;
Here is a db<>fiddle.
Note: This works for small tables, but it would not be optimal on large tables. But then again, on large tables, the string would quickly become unwieldy.
I need to know how to return a default row if no rows exist in a table. What would be the best way to do this? I'm only returning a single column from this particular table to get its value.
Edit: This would be SQL Server.
One approach for Oracle:
SELECT val
FROM myTable
UNION ALL
SELECT 'DEFAULT'
FROM dual
WHERE NOT EXISTS (SELECT * FROM myTable)
Or alternatively in Oracle:
SELECT NVL(MIN(val), 'DEFAULT')
FROM myTable
Or alternatively in SqlServer:
SELECT ISNULL(MIN(val), 'DEFAULT')
FROM myTable
These use the fact that MIN() returns NULL when there are no rows.
If your base query is expected to return only one row, then you could use this trick:
select NVL( MIN(rate), 0 ) AS rate
from d_payment_index
where fy = 2007
and payment_year = 2008
and program_id = 18
(Oracle code, not sure if NVL is the right function for SQL Server.)
This would be eliminate the select query from running twice and be better for performance:
Declare #rate int
select
#rate = rate
from
d_payment_index
where
fy = 2007
and payment_year = 2008
and program_id = 18
IF ##rowcount = 0
Set #rate = 0
Select #rate 'rate'
How about this:
SELECT DEF.Rate, ACTUAL.Rate, COALESCE(ACTUAL.Rate, DEF.Rate) AS UseThisRate
FROM
(SELECT 0) DEF (Rate) -- This is your default rate
LEFT JOIN (
select rate
from d_payment_index
--WHERE 1=2 -- Uncomment this line to simulate a missing value
--...HERE IF YOUR ACTUAL WHERE CLAUSE. Removed for testing purposes...
--where fy = 2007
-- and payment_year = 2008
-- and program_id = 18
) ACTUAL (Rate) ON 1=1
Results
Valid Rate Exists
Rate Rate UseThisRate
----------- ----------- -----------
0 1 1
Default Rate Used
Rate Rate UseThisRate
----------- ----------- -----------
0 NULL 0
Test DDL
CREATE TABLE d_payment_index (rate int NOT NULL)
INSERT INTO d_payment_index VALUES (1)
This snippet uses Common Table Expressions to reduce redundant code and to improve readability. It is a variation of John Baughman's answer.
The syntax is for SQL Server.
WITH products AS (
SELECT prod_name,
price
FROM Products_Table
WHERE prod_name LIKE '%foo%'
),
defaults AS (
SELECT '-' AS prod_name,
0 AS price
)
SELECT * FROM products
UNION ALL
SELECT * FROM defaults
WHERE NOT EXISTS ( SELECT * FROM products );
*SQL solution
Suppose you have a review table which has primary key "id".
SELECT * FROM review WHERE id = 1555
UNION ALL
SELECT * FROM review WHERE NOT EXISTS ( SELECT * FROM review where id = 1555 ) AND id = 1
if table doesn't have review with 1555 id then this query will provide a review of id 1.
I figured it out, and it should also work for other systems too. It's a variation of WW's answer.
select rate
from d_payment_index
where fy = 2007
and payment_year = 2008
and program_id = 18
union
select 0 as rate
from d_payment_index
where not exists( select rate
from d_payment_index
where fy = 2007
and payment_year = 2008
and program_id = 18 )
One table scan method using a left join from defaults to actuals:
CREATE TABLE [stackoverflow-285666] (k int, val varchar(255))
INSERT INTO [stackoverflow-285666]
VALUES (1, '1-1')
INSERT INTO [stackoverflow-285666]
VALUES (1, '1-2')
INSERT INTO [stackoverflow-285666]
VALUES (1, '1-3')
INSERT INTO [stackoverflow-285666]
VALUES (2, '2-1')
INSERT INTO [stackoverflow-285666]
VALUES (2, '2-2')
DECLARE #k AS int
SET #k = 0
WHILE #k < 3
BEGIN
SELECT #k AS k
,COALESCE(ActualValue, DefaultValue) AS [Value]
FROM (
SELECT 'DefaultValue' AS DefaultValue
) AS Defaults
LEFT JOIN (
SELECT val AS ActualValue
FROM [stackoverflow-285666]
WHERE k = #k
) AS [Values]
ON 1 = 1
SET #k = #k + 1
END
DROP TABLE [stackoverflow-285666]
Gives output:
k Value
----------- ------------
0 DefaultValue
k Value
----------- ------------
1 1-1
1 1-2
1 1-3
k Value
----------- ------------
2 2-1
2 2-2
Assuming there is a table config with unique index on config_code column:
CONFIG_CODE PARAM1 PARAM2
--------------- -------- --------
default_config def 000
config1 abc 123
config2 def 456
This query returns line for config1 values, because it exists in the table:
SELECT *
FROM (SELECT *
FROM config
WHERE config_code = 'config1'
OR config_code = 'default_config'
ORDER BY CASE config_code WHEN 'default_config' THEN 999 ELSE 1 END)
WHERE rownum = 1;
CONFIG_CODE PARAM1 PARAM2
--------------- -------- --------
config1 abc 123
This one returns default record as config3 doesn't exist in the table:
SELECT *
FROM (SELECT *
FROM config
WHERE config_code = 'config3'
OR config_code = 'default_config'
ORDER BY CASE config_code WHEN 'default_config' THEN 999 ELSE 1 END)
WHERE rownum = 1;
CONFIG_CODE PARAM1 PARAM2
--------------- -------- --------
default_config def 000
In comparison with other solutions this one queries table config only once.
Do you want to return a full row? Does the default row need to have default values or can it be an empty row? Do you want the default row to have the same column structure as the table in question?
Depending on your requirements, you might do something like this:
1) run the query and put results in a temp table (or table variable)
2) check to see if the temp table has results
3) if not, return an empty row by performing a select statement similar to this (in SQL Server):
select '' as columnA, '' as columnB, '' as columnC from #tempTable
Where columnA, columnB and columnC are your actual column names.
Insert your default values into a table variable, then update this tableVar's single row with a match from your actual table. If a row is found, tableVar will be updated; if not, the default value remains. Return the table variable.
---=== The table & its data
CREATE TABLE dbo.Rates (
PkId int,
name varchar(10),
rate decimal(10,2)
)
INSERT INTO dbo.Rates(PkId, name, rate) VALUES (1, 'Schedule 1', 0.1)
INSERT INTO dbo.Rates(PkId, name, rate) VALUES (2, 'Schedule 2', 0.2)
Here's the solution:
---=== The solution
CREATE PROCEDURE dbo.GetRate
#PkId int
AS
BEGIN
DECLARE #tempTable TABLE (
PkId int,
name varchar(10),
rate decimal(10,2)
)
--- [1] Insert default values into #tempTable. PkId=0 is dummy value
INSERT INTO #tempTable(PkId, name, rate) VALUES (0, 'DEFAULT', 0.00)
--- [2] Update the single row in #tempTable with the actual value.
--- This only happens if a match is found
UPDATE #tempTable
SET t.PkId=x.PkId, t.name=x.name, t.rate = x.rate
FROM #tempTable t INNER JOIN dbo.Rates x
ON t.PkId = 0
WHERE x.PkId = #PkId
SELECT * FROM #tempTable
END
Test the code:
EXEC dbo.GetRate #PkId=1 --- returns values for PkId=1
EXEC dbo.GetRate #PkId=12314 --- returns default values
This is what I used for getting a default value if no values are present.
SELECT IF (
(SELECT COUNT(*) FROM tbs.replication_status) > 0,
(SELECT rs.last_replication_end_date FROM tbs.replication_status AS rs
WHERE rs.last_replication_start_date IS NOT NULL
AND rs.last_replication_end_date IS NOT NULL
AND rs.table = '%s' ORDER BY id DESC LIMIT 1),
(SELECT CAST(UNIX_TIMESTAMP (CURRENT_TIMESTAMP(6)) AS UNSIGNED))
) AS ts;
Is it possible to use SQL to return a list of values that are NOT in a particular dataset but are within the range of values?
For example, I have a column with the following....
Registration_no
1
5
6
9
I would like to return all the 'unused' integers between the min and the max in the range, which in this example would 2, 3, 4, 7 and 8
Is this possible by way of a SQL statement?
Many thanks,
You cannot get them as a list of values using generic SQL. But, you can get them as ranges.
select registration_no + 1 as StartMissingRange, registration_no - 1 as EndMissingRange
from (select r.*,
(select min(registration_no)
from registrations r2
where r2.registration_no > r.registration_no
) as next_ro
from registrations r
) t
where next_ro <> registration_no + 1;
This is generic SQL. There are other solutions depending on the database (such as using lead() function). If you want the list with separate numbers on each row, then a recursive CTE is one method supported by many databases.
CREATE TABLE [table]
(number varchar(250))
INSERT INTO [table] VALUES (1),(5),(6),(9)
SELECT * FROM [table]
DEClARE #a varchar(250)
SET #a = (SELECT MIN(number) FROM [table])
WHILE (SELECT MAX(number) FROM [table] ) > #a
BEGIN
IF #a NOT IN ( SELECT number FROM [table] )
PRINT #a
SET #a=#a+1
END
This will give you the missing numbers....2,3,4,7,8
Test Data
DECLARE #TABLE TABLE(registration_no INT)
INSERT INTO #TABLE VALUES
(1),(5),(6),(9)
Query
;WITH CTE AS
(
SELECT MIN(registration_no) MinVal
,MAX(registration_no) MaxVal
FROM #TABLE
)
,
RequiredVals
AS
(
SELECT DISTINCT [number]
FROM master..spt_values
WHERE number >= (SELECT MinVal FROM CTE)
AND number <= (SELECT MaxVal FROM CTE)
AND NOT EXISTS (SELECT 1
FROM #TABLE
WHERE registration_no = spt_values.number)
)
SELECT number AS MissingValues
FROM RequiredVals
Result Set
╔═══════════════╗
║ MissingValues ║
╠═══════════════╣
║ 2 ║
║ 3 ║
║ 4 ║
║ 7 ║
║ 8 ║
╚═══════════════╝
You can do this with a numbers table in regular SQL I believe. The idea is you have a table with a column that just lists all integers. Then you join to that table to find the missing numbers. Basically a poor mans sequence.
CREATE TABLE `Numbers` (
`NUM` int(11) NOT NULL,
PRIMARY KEY (`NUM`)
) ;
Fill the table with integers.
SELECT NUM
FROM
(SELECT MIN(Registration_no) AS MIN_Range, Max(Registration_no) AS Max_Range
FROM StackOverflow.Registration) r_temp
INNER JOIN
StackOverflow.Numbers n
ON
n.NUM >= r_temp.MIN_Range AND n.NUM <= r_temp.MAX_Range
LEFT OUTER JOIN
StackOverflow.Registration r
ON
r.Registration_no = n.NUM
WHERE r.Registration_no IS NULL
;
*http://www.sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
Simply do this:
DECLARE #MinVal int
DECLARE #MaxVal int
SELECT #MinVal=Min(registration_no),#MaxVal=Max(registration_no) FROM TableName
;WITH nums AS
(SELECT #MinVal AS value
UNION ALL
SELECT value + 1 AS value
FROM nums
WHERE nums.value <#MaxVal)
SELECT *
FROM nums
WHERE value NOT IN (SELECT registration_no FROM TableName)
Result:
VALUE
2
3
4
7
8
See result in SQL Fiddle.
Just Update the "Range" Below (note: code will only work up to the size of sys.columns table)
/* TABLE OF VALUES */
CREATE TABLE [#table](number varchar(250))
INSERT INTO [#table]
SELECT 1 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 9
select * from
(
select row=row_number() OVER (ORDER BY name) from sys.columns
)a
where row BETWEEN 1 and 20 /*CHANGE THIS RANGE*/
and row not in
(
SELECT number FROM [#table] /* TABLE OF VALUES */
)
DROP TABLE [#table]
I'm using a Split function (found on social.msdn.com) and when executing manually in a query window
SELECT * FROM dbo.Split('/ABC/DEF/GHI/JKL', '/')
I get the following
Id Name
-- ----
1
2 ABC
3 DEF
4 GHI
5 JKL
Where Id is just a sequential number indicating position within the original string and Name is the name of that node. No hierarchical information yet.
Now, the next step is to put this into a hierarchical data structure in the DB. I'm trying to do this in a stored proc and with my SQL skills being what they are, I've hit a wall. Here's what I'd like to have: (NOTE that the Id column above is not related to the Id or the ParentId column here.)
Id ParentId Name FullName
-- -------- ---- --------
1 NULL ABC /ABC
2 1 DEF /ABC/DEF
3 2 GHI /ABC/DEF/GHI
4 3 JKL /ABC/DEF/GHI/JKL
I've got this far with my SP (called GetId with param #FullName) - GetId should return the Id associated with this node. If the node doesn't exist, it should be created and the Id from that new row should be returned - in other words, the consumer of this SP shouldn't care or know if the node exists prior to its calling it:
DECLARE #count int
-- // is there already a row for this node?
SELECT #count = COUNT(CatId)
FROM Category
WHERE FullName = #FullName
-- // if no row for this node, create the row
-- // and perhaps create multiple rows in hierarchy up to root
IF (#count = 0)
BEGIN
SELECT * FROM Split(#FullName, '/')
-- // NOW WHAT ???
-- // need to insert row (and perhaps parents up to root)
END
-- // at this point, there should be a row for this node
-- // return the Id associated with this node
SELECT Id
FROM Category
WHERE FullName = #FullName
The Category table (adjacency list) where these items will eventually end up, through a series of inserts, has the following structure.
CREATE TABLE Category (
Id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
ParentId int NULL,
Name nvarchar(255) NOT NULL,
FullName nvarchar(255) NOT NULL)
As result, I do not want to generate a value for the Id column in the Category table and need to get the appropriate ParentId for each node.
After the paths '/ABC/DEF/GHI/JKL' and '/ABC/DEF/XYZ/LMN/OPQ' were processed and I did a SELECT * FROM Category, I would expect to see the following:
Id ParentId Name FullName
-- -------- ---- --------
1 NULL ABC /ABC
2 1 DEF /ABC/DEF
3 2 GHI /ABC/DEF/GHI
4 3 JKL /ABC/DEF/GHI
5 2 XYZ /ABC/DEF/XYZ
6 5 LMN /ABC/DEF/XYZ/LMN
7 6 OPQ /ABC/DEF/XYZ/LMN/OPQ
Q: would it be possible to call back into the SP recursively starting at the outer most node, until the node existed or we were at the ultimate parent? Something to the effect of:
GetId(#FullName)
{
If Category exists with #FullName
return CatId
Else // row doesn't exist for this node
Split #FullName, order by Id DESC so we get the leaf node first
Create Category row
#FullName,
#Name,
#ParentId = Id of next FullName (call GetId with FullName of next row from Split)
}
You can use CTE to achieve this, in combination with RowNumbering
With TMP AS (
SELECT Id, Data as Name, RN=ROW_NUMBER() over (Order by Id ASC)
FROM dbo.Split('/ABC/DEF/GHI/JKL', '/')
where Data > ''
), TMP2 AS (
SELECT TOP 1 RN, CONVERT(bigint, null) ParentId, Name, convert(nvarchar(max),'/' + Name) FullName
From TMP
Order by RN
union all
SELECT n.RN, t.RN, n.Name, t.FullName + '/' + n.Name
from TMP2 t
inner join TMP n on n.RN = t.RN+1)
select *
from tmp2
order by RN
Now for the 2nd part, this inserts the entire hierarchy, but starts with ID=1
IF (#count = 0)
BEGIN
With TMP AS (
SELECT Id, Data as Name, RN=ROW_NUMBER() over (Order by Id ASC)
FROM dbo.Split('/ABC/DEF/GHI/JKL', '/')
where Data > ''
), TMP2 AS (
SELECT TOP 1 RN, CONVERT(bigint, null) ParentId, Name, convert(nvarchar(max),'/' + Name) FullName
From TMP
Order by RN
union all
SELECT n.RN, t.RN, n.Name, t.FullName + '/' + n.Name
from TMP2 t
inner join TMP n on n.RN = t.RN+1)
insert Category(CatId, ParentId, Name, FullName) --<< list correct column names
select RN, ParentId, Name, FullName
from tmp2
order by RN
END