T-SQL Recursion; Multiple Recursions? - sql

I'm trying to conceptualize a solution to a problem I have with recursion and I can't quite wrap my mind around it. I have three tables. We'll call them DocGroup, GroupGroup, and GroupUser. In the DocGroup table there is a hirerachial structure of one record, which specifies another record as it's parent and so on until a record is its own parent.
Doc Group
1 1
2 2
3 2
4 3
5 2
6 4
GroupGroup contains Group nesting:
Group MemberGroup
4 2
4 1
GroupUser maps a group to a number of users:
Group User Key
1 1 ABC
1 3 BCD
1 4 CDE
2 1 DEF
2 2 EFG
2 3 FGH
3 3 GHI
4 2 HIJ
4 3 IJK
4 4 JKL
So I would like to specify a user and a document and get a list of all the keys for that combination. For example if I choose user 2 and document 2 I need to return "EFG" but if I choose document 6 and user 3 I need to return "IJK", "GHI", "FGH", and "BCD" because group 2 and 1 are in group 4 and group 4 = document 4, which has a group of 3. In all cases I only need to get the record for the specified user.
I would imagine that I need multiple recursions to get this done, but I can't seem to mentally map out how that might be done in SQL and I don't want to degrade to loops and cursors to do this.

OK, here is the answer:
DECLARE #Key varchar(max)
;WITH DocBase AS (
SELECT Doc, Group
FROM DocGroup
WHERE Doc = #Doc
UNION ALL
SELECT DocGroup.Doc, DocGroup.Group
FROM DocGroup INNER JOIN DocBase
ON DocGroup.Doc = DocBase.Group
WHERE DocBase.Doc <> DocBase.Group --to prevent infinite recursion
), GroupNesting AS (
SELECT Doc
FROM DocBase
UNION ALL
SELECT MemberGroup
FROM GroupGroup.Group = GroupNesting.Doc
), GroupList AS (
SELECT DISTINCT Doc
FROM GroupNesting
), KeyList AS (
SELECT Key
FROM GroupList INNER JOIN GroupUser
ON GroupList.Doc = GroupUser.Group
WHERE User = #User
)
SELECT #Key = COALESE(#Key, '') + Key
FROM Key
SELECT #Key
I could have used any of the suggestions from http://www.simple-talk.com/sql/t-sql-programming/concatenating-row-values-in-transact-sql/ for doing the final concatenation, but I chose this method because it is the easiest and I'm putting this in a procedure anyway.

I have to post this because I'm a raging egomanic:
The Zen of Recursion
But seriously, that's how we did it back in the day. These days, you'd use a Common Table Expression.
HTH.

Related

How to update table by of the records in the table

I have a table in PostgerSQL and I need to make N entries in the table twice and for the first half I need to fill in the partner_id field with the value 1 and the second half with the value partner_id = 2.
i try to `
update USERS_TABLE set user_rule_id = 1;
update USERS_TABLE set user_rule_id = 2 where USERS_TABLE.id > count(*)/2;
`
I depends a lot how precise the number of users have to be that are updated with 1 or 2.
The following would be quite unprecise,a s it doesn't take the exact number of user that already exist8after deleting some rows the numbers doesn't fit anymore.
SELECT * FROM USERS_TABLE
id
user_rule_id
1
1
2
1
3
2
4
2
5
2
SELECT 5
If you have a lot of deleted rows and want still the half of the users, you can choose following approach, which does rely on the id, but at teh actual row number
UPDATE USERS_TABLE1
set user_rule_id = CASE WHEN rn <= (SELECT count(*) FROM USERS_TABLE1)/ 2 then 1
ELSE 2 END
FROM (SELECT id, ROW_NUMBER() OVER( ORDER BY id) rn FROM USERS_TABLE1) t
WHERE USERS_TABLE1.id = t.id;
UPDATE 5
SELECT * FROM USERS_TABLE1
id
user_rule_id
1
1
2
1
3
2
4
2
5
2
SELECT 5
fiddle
In the sample case it it the same result, but when you have a lot of rows and a bunch of the deleted users, the senind will give you quite a good result

SQL To delete number of items is less than required item number

I have two tables - StepModels (support plan) and FeedbackStepModels (feedback), StepModels keeps how many steps each support plan requires.
SELECT [SupportPlanID],COUNT(*)AS Steps
FROM [StepModels]
GROUP BY SupportPlanID
SupportPlanID (Steps)
-------------------------------
1 4
2 9
3 3
4 10
FeedbackStepModels keeps how many steps employee entered the system
SELECT [FeedbackID],SupportPlanID,Count(*)AS StepsNumber
FROM [FeedbackStepModels]
GROUP BY FeedbackID,SupportPlanID
FeedbackID SupportPlanID
---------------------------------------------
1 1 3 --> this suppose to be 4
2 2 9 --> Correct
3 3 0 --> this suppose to be 3
4 4 10 --> Correct
If submitted Feedback steps total is less then required total amount I want to delete this wrong entry from the database. Basically i need to delete FeedbackID 1 and 3.
I can load the data into List and compare and delete it, but want to know if we can we do this in SQL rather than C# code.
You can use the query below to remove your unwanted data by SQL Script
DELETE f
FROM FeedbackStepModels f
INNER JOIN (
SELECT [FeedbackID],SupportPlanID, Count(*) AS StepsNumber
FROM [FeedbackStepModels]
GROUP BY FeedbackID,SupportPlanID
) f_derived on f_derived_FeedbackID=f.FeedBackID and f_derived.SupportPlanID = f.SupportPlanID
INNER JOIN (
SELECT [SupportPlanID],COUNT(*)AS Steps
FROM [StepModels]
GROUP BY SupportPlanID
) s_derived on s_derived.SupportPlanID = f.SupportPlanID
WHERE f_derived.StepsNumber < s_derived.Steps
I think you want something like this.
DELETE FROM [FeedbackStepModels]
WHERE FeedbackID IN
(
SELECT a.FeedbackID
FROM
(
SELECT [FeedbackID],
SupportPlanID,
COUNT(*) AS StepsNumber
FROM [FeedbackStepModels]
GROUP BY FeedbackID,
SupportPlanID
) AS a
INNER JOIN
(
SELECT [SupportPlanID],
COUNT(*) AS Steps
FROM [StepModels]
GROUP BY SupportPlanID
) AS b ON a.SupportPlanID = b.[SupportPlanID]
WHERE a.StepsNumber < b.Steps
);

Select query to reference tables based on table Id or Name

Consider I have a table which list all the tables in database along with the referenced tables. In my case, no foreign key reference are used in the tables. Tables references are maintained as below
TableId ReferedInTableId
1 2
1 3
1 4
2 5
2 6
3 7
4 8
4 9
5 -
6 -
7 10
8 -
9 11
10 -
11 -
In this case, I need a query to find the referenced tables based on the input TableId.
For Eg, for TableId 1, the referencedTableId are 2,3,4. But I need to recurse again like these 2,3,4 are again referred in some tables and I need that list too.
At the end,
If the input is TableId 1, It should return 2,3,4,5,6,7,8,9,10,11
If the input is tableId 4, It should return 8,9,11
If the input is tableId 3, it should return 7,10
Please help me in building a SQL select query.
Assuming you have no cycles in the links, you can use a relatively simple recursive CTE:
with cte as (
select t.tableid, t.referencedtableid, 1 as lev
from t
where t.id = 1
union all
select cte.tableid, t.referencedtableid, lev + 1
from cte join
t
on cte.referencedtableid = t.id
)
select referencedtableid
from cte;

SQL Query to find which group does not have a given value

I am using T-SQL.
Say if I have the following
Value Nbr
----- ---
one 6
one 7
one 8
two 6
two 7
three 5
three 3
three 2
In the above table, I need to find which group does not have 6 in it.
In this case, it is three as it does not have 6 in it.
What would be the best approach to do this?
I tried:
select Value from tbl1
where nbr <> 6
group by Value
but did not get the intended result.
select distinct value
from tbl1
where value not in
(
select distinct value
from tbl1
where nbr = 6
)

Access SQL query to mailmerge

How can I transform this table from this
id name
1 sam
2 nick
3 ali
4 farah
5 josef
6 fadi
to
id1 name1 id2 name2 id3 name3 id4 name4
1 sam 2 nick 3 ali 4 farah
5 josef 6 fadi
the reason i need this is i have a database and i need to do a mail merge using word and I want to print every 4 rows on one page, MS word can only print one row per page, so using an SQL query I want one row to represent 4 rows
thanks in advance
Ali
You don't need to create a query for this in Access. Word has a merge field called <<Next Record>> which forces moving to the next record. If you look at how label documents are created using the Mail Merge Wizard, you'll see that's how it's done.
Updated - Doing this in SQL
The columns in simple SELECT statements are derived from the columns from the underlying table/query (or from expressions). If you want to define columns based on the data, you need to use a crosstab query.
First create a query with a running count for each person (say your table is called People), and calculate the row and column position from the running count:
SELECT People.id, Count(*)-1 AS RunningCount, int(RunningCount/4) AS RowNumber, RunningCount Mod 4 AS ColumnNumber
FROM People
LEFT JOIN People AS People_1 ON People.id >= People_1.id
GROUP BY People.id;
(You won't be able to view this in the Query Designer, because the JOIN isn't comparing with = but with >=.)
This query returns the following results:
id Rank RowNumber ColumnNumber
1 0 0 0
2 1 0 1
3 2 0 2
4 3 0 3
5 4 1 0
6 5 1 1
Assuming this query is saved as Positions, the following query will return the results:
TRANSFORM First(Item) AS FirstOfItem
SELECT RowNumber
FROM (
SELECT ID AS Item, RowNumber, "id" &( ColumnNumber + 1) AS ColumnHeading
FROM Positions
UNION ALL SELECT Name, RowNumber, "name" & (ColumnNumber +1)
FROM Positions
INNER JOIN People ON Positions.id = People.id
) AS AllValues
GROUP BY AllValues.RowNumber
PIVOT AllValues.ColumnHeading In ("id1","name1","id2","name2","id3","name3","id4","name4");
The UNION is there so each record in the People table will have two columns - one with the id, and one with the name.
The PIVOT clause forces the columns to the specified order, and not in alphabetical order (e.g. id1, id2 ... name1, name2...)