How to get parents up to 5 level hierarchy from child id - sql

I created a table with name "content_folder" and inserted values just like this.
cf_id cf_parent_id cf_name
--------------------------------------
1 0 root
2 1 US Blenders
3 2 US Blenders Chil11
4 1 Australian Blenderss
5 1 US Blenders Chil11 -2
40003 1 Child
40206 1 Child 111
40211 2 New
I want to display the folder hierarchy for 5 levels sepereated by '>' in drop drown so I wanted the result in following way
cf_id path
--------------------------------------
1 root
2 US Blenders
3 US Blenders > US Blenders Chil11
4 Australian Blenderss
5 US Blenders Chil11 -2
40003 Child
40206 Child 111
40211 US Blenders > New
I tried by writing following query but output result is not proper.
SELECT t.cf_id,
Group_concat(anc.cf_name ORDER BY anc.cf_name SEPARATOR ' > ') AS path
FROM content_folder AS t
JOIN content_folder AS anc
ON t.cf_name LIKE Concat(anc.cf_name, '%')
GROUP BY t.cf_id;
Can you please suggest me better solution. I am working on MySQL 5.7 so I think CTEs are not supported in this version.

I had done something like this with my database. Here is how i did it, i changed a few things and added the two update statements at the end to get your desired output, but i think you will get the gist of whats going on:
DECLARE #temp TABLE
(
id INT,
parent_id INT,
name NVARCHAR(100),
path NVARCHAR(MAX)
)
INSERT INTO #temp (id,parent_id,name) VALUES
(1,0,'root'),
(2,1,'US Blenders'),
(3,2,'US Blenders Chil11'),
(4,1,'Australian Blenderss'),
(5,1,'US Blenders Chil11 -2'),
(40003,1,'Child'),
(40206,1,'Child 111'),
(40211,2,'New')
DECLARE #counter TABLE ( id INT )
DECLARE #current_id INT;
DECLARE #loc_id INT;
INSERT INTO #counter SELECT id FROM #temp
SELECT * FROM #temp
WHILE (SELECT COUNT(*) FROM #counter) > 0
BEGIN
SET #current_id = (SELECT TOP 1 id FROM #counter)
SET #loc_id = (SELECT parent_id FROM #temp WHERE id = #current_id)
WHILE #loc_id IS NOT NULL AND #loc_id != 0
BEGIN
UPDATE #temp
SET path = ' > ' + (SELECT name FROM #temp WHERE id = #loc_id) + CASE WHEN path IS NULL THEN '' ELSE path END
WHERE id = #current_id
SET #loc_id = (SELECT parent_id FROM #temp WHERE id = #loc_id)
END
DELETE FROM #counter
WHERE id = #current_id
END
UPDATE #temp
SET path = REPLACE(REPLACE(path,' > root',''),' > ','')
UPDATE #temp
SET path = CASE WHEN LEN(path) > 0 THEN path + ' > ' + name ELSE name END
SELECT * FROM #temp
OUTPUT:
id parent_id name path
1 0 root root
2 1 US Blenders US Blenders
3 2 US Blenders Chil11 US Blenders > US Blenders Chil11
4 1 Australian Blenderss Australian Blenderss
5 1 US Blenders Chil11 -2 US Blenders Chil11 -2
40003 1 Child Child
40206 1 Child 111 Child 111
40211 2 New US Blenders > New

Related

How to perform calculation in a specific column with multiple conditions

I am looking for a clean solution to perform calculation from a single column with a few conditions and insert it in the same table. My existing solution is to use a while loop with many variable declarations, writing simple query to store value, perform calculation and finally insert it as a new row to the table. However it looks messy and complicated. I am wondering if there is a better solution to it?
Original Table
Week | Indicator | Value
1 A 2
1 B 3
1 D 10
1 E 5
1 X 12
1 Y 6
2 A 4
2 B 5
2 D 7
2 E 3
2 X 4
2 Y 2
...
53
Updated Table
Week | Indicator | Value
1 A 2
1 B 3
1 C 5
1 D 10
1 E 5
1 F 5
1 X 12
1 Y 6
1 Z 2
2 A 4
2 B 5
2 C 9
2 D 7
2 E 3
2 F 4
2 X 4
2 Y 2
2 Z 2
In this example in the updated table, every 3rd row involves different calculation for the same week such that the 3rd row is an addition, 6th row is a subtraction and the 9th row is a division.
The calculation does not restrict to only addition and could include other forms of calculation formulas. I am just using addition as a simple illustration.
Here is an example of my SQL solution:
DECLARE #total_rows int;
SET #total_rows = (SELECT COUNT(*) FROM original_table);
DECLARE #wk varchar(5);
DECLARE #indicator1 char(1);
DECLARE #indicator2 char(1);
SET #indicator1 = 'A';
SET #indicator2 = 'B';
DECLARE #a_value int;
DECLARE #b_value int;
DECLARE #cal_value int;
DECLARE #iteration int
SET #iteration = 1
WHILE #iteration <= #total_rows
BEGIN
IF #iteration <= 53
SET #wk = concat('W',#iteration)
SET #a_value = (SELECT value
FROM original_table
WHERE indicator = #indicator1 and week = #wk);
SET #b_value = (SELECT value
FROM original_table
WHERE indicator = #indicator2 and week = #wk);
SET #cal_value = (#a_value/ NULLIF(#b_value,0)) *1000000;
....
SET #iteration = #iteration + 1
END
Not going to post the entire SQL script as it is quite lengthy but I hope you get the gist of it.
Is this not as simple as INSERT and SUM..?
INSERT INTO dbo.YourTable ([Week],Indicator,[Value])
SELECT YT.[Week],
'C' AS Indicator,
SUM(YT.[Value]) AS [Value]
FROM dbo.YourTable YT
GROUP BY YT.[Week];
DB<>Fiddle

How can I create cumulative string concatenation across rows with ordering of the string?

I am looking to add a column to my database which performs row-wise cumulative concatenations of the values of another column for each ID, and orders the resulting string by a different hierarchy. The dataset is very large and the result I designed an smaller test data isn't workable on a larger scale so I need help redesigning it.
I have written the query so far using a combination of a recursive CTE to perform the cumulative concatenation (step 1 output below), and then a slightly clunky function (step 2 output below) to order the strings according to a separate hierarchy which also removes the '1' value. These work on a small subset of my data (n=60), but when I try to run an a larger subset (n=500,000) the CTE table runs forever (stopped without completing at 2hours). The real dataset will be in an order of magnitude of hundred of millions of rows, so solution isn't workable for that scale.
ID Start_Date End_Date Seg step1 step2
1 01/04/1946 31/12/1990 1 1 1
1 01/01/1991 08/01/2007 4 4 4
1 09/01/2007 04/02/2007 1 1 1
1 05/02/2007 18/10/2017 4 14 4
1 01/04/2013 18/10/2017 8 148 48
1 11/11/2014 18/10/2017 7 1487 487
2 01/05/1931 31/12/1997 1 1 1
2 01/01/1998 20/01/2014 4 4 4
2 31/01/2011 20/01/2014 6 146 46
2 21/02/2013 20/01/2014 5 1465 456
2 01/04/2013 20/01/2014 8 14658 4586
2 29/04/2013 20/01/2014 7 146587 45876
There are additional complicated logic elements, such as only starting the cumulation when the start date is earlier than the previous row end date, so a solution which allows flexibility by adding where or case when statements is key.
Example of the recursive CTE and ordering function I have used (not adapted for the simplified table shown, but indicative of the structure I have used) are below.
Recursive CTE (output step 1 column)
with t (ID, Segment,start_date, start_comb,updated_end_date ,rn) as (
select ID, Segment, start_date, case when Segment_end_date <> resolved_date OR Segment_end_date is null then 1 else 0 end as start_comb
,updated_end_date
,row_number() over (partition by ID order by start_date) as rn
from #test_IDs
)
,r (ID, orig_seg, Segment, rn, start_comb, start_date, updated_end_date) as (
select ID, cast(Segment as varchar(max)), cast(Segment as varchar(max)),rn, start_comb, start_date, updated_end_date
from t
where start_comb=0
union all
select r.ID, cast(t.segment as varchar(max)) as orig_seg
, Segment = cast( (concat(r.Segment,t.Segment)) as varchar(max))
, t.rn, t.start_comb, t.start_date, t.updated_end_date
from r
join t on t.ID = r.ID and t.rn = r.rn + 1 and t.start_comb <> 0
)
Ordering function (output step 2 column)
if object_id ('reformat') is not null
drop function reformat
create function dbo.reformat
(
#unordered_Segs varchar(max)
)
returns varchar(255)
as
begin
declare #healthy int, #first int, #second int, #third int, #fourth int, #fifth int, #outtext int
if Charindex('4',#unordered_segs) > 0
set #first = 4
else set #first = ''
if Charindex('5',#unordered_segs) > 0
set #second = 5
else set #second = ''
if Charindex('8',#unordered_segs) > 0
set #third = 8
else set #third = ''
if Charindex('7',#unordered_segs) > 0
set #fourth = 7
else set #fourth = ''
if Charindex('6',#unordered_segs) > 0
set #fifth = 6
else set #fifth = ''
if Charindex('1',#unordered_segs) > 0 and len(#unordered_segs) = 1
set #outtext = 1
else
set #outtext = Replace((concat(#first,#second,#third,#fourth,#fifth)),'0','')
return #outtext
end
Thanks!

SQL query to get multihierarchy items

in my SQL Table i have following data
ID Level Description Code MasterID
1 1 Cars AD0 NULL
2 1 Trucks JF1 NULL
3 1 Items YU2 NULL
4 2 New Cars AS3 1
5 2 Used Cars TG4 1
6 2 Car parts UJ5 1
7 2 New trucks OL6 2
8 2 Used trucks PL7 2
9 2 Truck parts KJL8 2
10 2 Factory stuff UY9 3
11 2 Custom stuff RT10 3
12 3 Toyota 6YH11 4
13 3 BMW 9OKH12 4
14 3 VW 13 5
15 3 Tiers Type I J14 6
16 3 Tiers Type II J15 6
17 3 Tiers Type III ADS16 9
18 3 Seats SA17 6
19 3 Doors UU18 6
20 3 Lights 9OL19 6
21 4 Left light GH20 20
22 4 Right light H21 20
23 4 Left door JHJ22 19
24 4 Michelin UY23 16
25 4 Vredestein GTF24 17
26 4 Dunlop 25 15
My achievement is to get all hierarchy data for each single item. For Exmaple, the outpu should look like as following
ID Level Description Code MasterId1 Description1 MasterId2 Description2 MasterId3 Description3
24 4 Michelin UY23 16 Tiers Type II 6 Car Parts 1 Cars
.
.
19 3 Doors UU18 6 Car Parts 1 Cars NULL NULL
.
.
10 2 Factory Stuff UY9 3 Items NULL NULL NULL NULL
.
.
3 1 Items NULL NULL NULL NULL NULL NULL NULL
.
.
If somebody can help or give an advise how to achieve this?
This is not dynamic but it could be pretty easily.
Using a recursive cte you can get the hierarchy for the entire table and self join a few times to get the table structure you want.
;WITH cte AS
(
SELECT *, ID AS [RootID], 1 AS [MasterLevel] FROM Table1
UNION ALL
SELECT t1.*, cte.[RootID], cte.[MasterLevel] + 1 FROM Table1 t1
JOIN cte ON t1.ID = cte.MasterID
)
SELECT r.ID, r.[Level], r.[Description], r.[Code],
m1.ID AS MasterId1, m1.[Description] AS Description1,
m2.ID AS MasterId2, m1.[Description] AS Description2,
m3.ID AS MasterId3, m1.[Description] AS Description3
FROM cte r
LEFT JOIN cte m1 ON m1.[RootID] = r.[RootID] AND m1.MasterLevel = 2
LEFT JOIN cte m2 ON m2.[RootID] = r.[RootID] AND m2.MasterLevel = 3
LEFT JOIN cte m3 ON m3.[RootID] = r.[RootID] AND m3.MasterLevel = 4
WHERE r.MasterLevel = 1
ORDER BY r.RootID DESC, r.MasterLevel
This would build a dynamic sql to get master and desciption fields based on the maximum Level value. or you could define how many levels you want to see by changing the #MaxLevel
DECLARE #Sql VARCHAR(MAX) = '',
#SelectSql VARCHAR(MAX) = '',
#JoinSql VARCHAR(MAX) = '',
#MaxLevel INT,
#idx INT = 1
SET #MaxLevel = (SELECT MAX([Level]) FROM Table1)
WHILE #idx < #MaxLevel
BEGIN
SET #SelectSql = #SelectSql + REPLACE(', m<index>.ID AS MasterId<index>, m<index>.[Description] AS Description<index> ', '<index>', #idx)
SET #JoinSql = #JoinSql + REPLACE(' LEFT JOIN cte m<index> ON m<index>.[RootID] = r.[RootID] AND m<index>.MasterLevel = <index> ', '<index>', #idx)
SET #idx = #idx + 1
END
SET #Sql = '
;WITH cte AS
(
SELECT *, ID AS [RootID], 0 AS [MasterLevel] FROM Table1
UNION ALL
SELECT t1.*, cte.[RootID], cte.[MasterLevel] + 1 FROM Table1 t1
JOIN cte ON t1.ID = cte.MasterID
)
SELECT r.ID, r.[Level], r.[Description], r.[Code]' + #SelectSql
+ 'FROM cte r ' + #JoinSql
+ 'WHERE r.MasterLevel = 0
ORDER BY r.RootID DESC, r.MasterLevel'
EXEC(#Sql)

SQL - How to select a column alias from another table?

I have a table in SQL with data and another table that holds the alias for that column. It is used for translation purposes.
I was wondering how can I do a select on those columns but retrieve the alias from another table?
This is the table that holds the real column names:
ID PageID ColName Order Type Width IsDeleted
1 7 CustType 2 NULL NULL 0
2 7 Description 3 NULL NULL 0
3 7 ApplyVAT 4 NULL NULL 0
4 7 ProduceInvoices 5 NULL NULL 0
5 7 PurchaseSale 6 NULL NULL 0
6 7 TermsDays 7 NULL NULL 0
7 7 DateTimeLastUpdated 8 NULL NULL 0
This is the table that holds the alias (text):
ID ColID UserID Text Order Enabled?
50 22 1 id 1 1
51 1 1 CustTypes 2 1
52 2 1 Description 3 1
53 3 1 ApplyVAT NULL 0
54 4 1 ProduceInvoices NULL 0
55 5 1 PurchaseSale NULL 0
56 6 1 TermsDays NULL 0
57 7 1 DateTimeLastUpdated NULL 0
I believe you will need to use dynamic sql to do this, e.g.:
DECLARE #Sql NVARCHAR(MAX);
SELECT TOP 1 #Sql = 'SELECT dt.ID as ' + at.IDAlias + ', dt.Town as ' + at.TownAlias
+ ' FROM DataTable dt'
FROM AliasTable at
WHERE at.LanguageID = 2;
EXEC(#Sql)
Given the example of Data Table
CREATE TABLE DataTable
(
ID INT,
Town NVARCHAR(50)
);
And a table holding language - dependent aliases for the columns in the above:
CREATE TABLE AliasTable
(
LanguageId INT,
IDAlias NVARCHAR(100),
TownAlias NVARCHAR(100)
);
SqlFiddle here
One of the (many) caveats with dynamic Sql is you will need to ensure that the alias data is validated against Sql Injectin attacks.

Implementing this join in SQL Stored procedure

I have 4 tables Position, Employee, Training and Trmatrix.
Table Position
PositionId PosName TrainingId
1 developer 1,2,3
2 Designer 4,5
3 BDA 2,3,6
Table Employee
Employeeid Ename Posid Courseid
1 Alex 1 4
2 Shaun 2 1,2,3
3 Hales 3
Table Training
Trainingid Trainingname
1 JAVA
2 Dot Net
3 PHP
4 Photoshop
5 JQUERY
6 Client Handling
TrMatrix
TrmatId TrID empID
1 1 1
2 2 1
3 3 1
4 4 1
5 4 2
6 5 2
7 1 2
8 2 2
9 2 3
10 3 3
foreign Key relation
trmatrix trId corresponds to the trainingID of the trainingtable.
Employee posid corresponds to the PositionId of the Positiontable.
Employee courseId corresponds to the trainingId of the trianingtable.
BY basic Aim is to get that course/trainingname which is no present in the
EMployee.Courseid column in correspondance to the trmatrix table,
which defines that I have to get the all entries from the trmatrix table for which there is no entry in the employee table Courseid column.
Suppose in case of Alex I have to fetch all the data from the trmatrix table except for course 4 since it is present in the courseid column of the Employee table, so it would return course no 1,2,3 not the no 4.
I am Newbie to the SQL so please help me out with this problem.
Thanks in advance
To start with, you should make PositionTraining and EmployeeCourse tables as well:
PositionTraining
PositionId TrainingId
1 1
1 2
1 3
2 4
2 5
3 2
3 3
3 6
EmployeeCourse
Employeeid Courseid
1 4
2 1
2 2
3 3
and then remove Position.TrainingId and Employee.Courseid.
By doing this you make the data much easier to query.
To get things which are not present in one table from another you can use
WHERE NOT EXISTS (SELECT value FROM OtherTable)
or
WHERE NOT IN (SELECT value FROM OtherTable)
However there is a class of queries called subqueries and these are very useful in this circumstance and a very good article on them is here
http://allenbrowne.com/subquery-01.html
(its written for ms access but the synstax and MS SQL rules are exactly the same so dont be put off)
UDF for spliting out entries
Create function [dbo].[atf_BarListToTable]
(#list ntext)
RETURNS #tbl TABLE (ListPosn int IDENTITY(1, 1) NOT NULL,
SString VARCHAR(1028) NOT NULL) AS
BEGIN
DECLARE #pos int
DECLARE #textpos int
DECLARE #ChunkLength smallint
DECLARE #str nvarchar(4000)
DECLARE #tmpstr nvarchar(4000)
DECLARE #leftover nvarchar(4000)
SET #textpos = 1
SET #leftover = ''
WHILE #textpos <= datalength(#list) / 2
BEGIN
SET #ChunkLength = 4000 - datalength(#leftover) / 2
SET #tmpstr = ltrim(#leftover + substring(#list, #textpos, #ChunkLength))
SET #textpos = #textpos + #ChunkLength
SET #pos = charindex('|', #tmpstr)
WHILE #pos > 0
BEGIN
SET #str = substring(#tmpstr, 1, #pos - 1)
INSERT #tbl (SString) VALUES( #str)
SET #tmpstr = ltrim(substring(#tmpstr, #pos + 1, len(#tmpstr)))
SET #pos = charindex('|', #tmpstr)
END
SET #leftover = #tmpstr
END
IF ltrim(rtrim(#leftover)) <> ''
INSERT #tbl (SString) VALUES(#leftover)
RETURN
END