Update based on hierarchy table - sql

I have a table of employees- Active or Inactive . The structure is as below
create table employee
(
id int,
supervisorid int,
active bit
)
Another table Component has items like ..
create table component
(
cid int,
empid int
)
Have some sample data loaded HERE
How can i update the component table , such that the empid field holds the IMMEDIATE ACTIVE SUPERVISOR ID for Inactive Employees. In the sample case, the result of update should look like
Component
---------
Cid | empid
--------------
1 1235
2 1246
3 1246
I am able to generate hierarchy table for specific employees using the query mentioned in the fiddle page.

You can use a recursive CTE to make a table for update with first active supervisorID for each non active ID. On the first iteration you select all ACTIVE ID's and set their BaseID to the same. On each next iteration you select next level of their NON-ACTIVE childs add to the recursive table and save the root BaseID in each record. So when you add all records the BaseID field for each ID is the first ACTIVE parent. Then we leave only ID's we need to update (where Id<>BaseID) in the ForUpd table. Then use this table Id->BaseID to update the original table.
;With OrgChart as
(
SELECT id,id as BaseID
FROM employee WHERE Active=1
UNION ALL
SELECT e.ID, g.BaseID as BaseID
FROM employee e
JOIN OrgChart g on e.supervisorid=g.id
WHERE e.Active=0
), ForUpd as
(SELECT ID,BASEID FROM OrgChart WHERE ID<>BAseID)
UPDATE A SET empid = B.baseID
FROM component A
JOIN ForUpd B ON A.empid = B.id
SQLFiddle demo

Related

Own id for every unique name in the table?

Is it possible to make a table that has like auto-incrementing id's for every unique name that I make in the table?
For example:
ID NAME_ID NAME
----------------------
1 1 John
2 1 John
3 1 John
4 2 Mary
5 2 Mary
6 3 Sarah
7 4 Lucas
and so on.
Use the window function rank() to get a unique id per name. Or dense_rank() to get the same without gaps:
SELECT id, dense_rank() OVER (ORDER BY name) AS name_id, name
FROM tbl;
I would advise not to write that redundant information to your table. You can generate that number on the fly. Or you shouldn't store name redundantly in that table, name would typically live in another table, with name_id as PRIMARY KEY.
Then you have a "names" table and run "SELECT or INSERT" there to get a unique name_id for every new entry in the main table. See:
Is SELECT or INSERT in a function prone to race conditions?
First add the column to the table.
ALTER TABLE yourtable
ADD [UID] INT NULL;
``
ALTER TABLE yourtable
ADD constraint fk_yourtable_uid_id foreign key ([UID]) references yourtable([Serial]);
Then you can update the UID with the minimum Serial ID per Name.
UPDATE t
SET [UID] = q.[UID]
FROM yourtable t
JOIN
(
SELECT Name, MIN([Serial]) AS [UID]
FROM yourtable
GROUP BY Name
) q ON q.Name = t.Name
WHERE (t.[UID] IS NULL OR t.[UID] != q.[UID]) -- Repeatability

How to delete values from first table by using name of the second sql

I have a table groups
group_id | name_group
1 ISI
2 IZI
And a table students
id | first_name | last_name | group_id
6 Bob Surname1 1
17 John Surname2 2
How can I delete all information from student table by using groups.name?
i.e. I need query which select all students with the same group_id which is equivalent to name.
group_id 1 = 'ISI'
group_id 2 = 'IZI'
And a query must delete exactly by name.
You can use this query
Delete from Students where group_id=(Select group_id from groups where name_group='ISI');
This all the records with the group_id of 1 (via group_name='ISi').
There are different ways. A simple one, could be selecting the Id of the group and deleting from there. Example:
DECLARE
#name as nvarchar(20) = 'myName'
-- we display the data just for check
SELECT s.*, g.group_id
FROM students s ON g.group_id = s.group_id
WHERE g.name_group = #name
--we look the group id and delete the matches with students
DELETE
FROM students
WHERE group_id in (SELECT group_id FROM groups WHERE name_group = #name)
PD: This basic approach could work on both: MySQL and MSSQL.

Given a parent / child key table, how can we recursively insert a copy of the structure into another table?

I have a recursive CTE which gives me a listing of a set of parent child keys as follows, lets say its in a temp table called [#relationtree]:
Parent | Child
--------------
1 | 3
3 | 5
5 | 6
5 | 9
I want to create a copy of these relationships into a table with, lets say, the following stucture:
CREATE TABLE [dbo].[Relations]
(
[Id] int identity(1,1)
[ParentId] int
)
How can I insert the above records but recursively obtain the previously inserted identity value to be able to insert that value as the ParentId column for each copy of a child I insert?
I would expect to have at the end of this in [dbo].[Relations] (given our current seed value is, say 50)
Id | ParentId
-------------
... other rows present before this query ...
50 | NULL
51 | 50
52 | 51
53 | 51
I'm not sure that scope_identity can work in this situation, or that creating a new temp table with a list of new IDs and inserting identity columns manually is the correct approach?
I could write a cursor / loop to do this, but there must be a nice way of doing some recursive select magic!
Since you're trying to put the tree into a segment of the table it looks like you're going to need to use SET IDENTITY_INSERT ON for the table anyway. You're going to need to make sure that there is room for the new tree. In this case, I'll assume that 49 is the current maximum id in your table so that we don't need to be concerned with overrunning a tree that's later in the table.
You'll need to be able to map the IDs from the old tree to the new tree. Unless there's some rule around the ids, the exact mapping should be irrelevant as long as it's accurate, so in that case, I'd just do something like this:
SET IDENTITY_INSERT dbo.Relations ON
;WITH CTE_MappedIDs AS
(
SELECT
old_id,
ROW_NUMBER() OVER(ORDER BY old_id) + 49 AS new_id
FROM
(
SELECT DISTINCT parent AS old_id FROM #relationtree
UNION
SELECT DISTINCT child AS old_id FROM #relationtree
) SQ
)
INSERT INTO dbo.Relations (Id, ParentId)
SELECT
CID.new_id,
PID.new_id
FROM
#relationtree RT
INNER JOIN CTE_MappedIDs PID ON PID.old_id = RT.parent
INNER JOIN CTE_MappedIDs CID ON CID.old_id = RT.parent
-- We need to also add the root node
UNION ALL
SELECT
NID.new_id,
NULL
FROM
#relationtree RT2
INNER JOIN CTE_MappedIDs NID ON NID.old_id = RT2.parent
WHERE
RT2.parent NOT IN (SELECT DISTINCT child FROM #relationtree)
SET IDENTITY_INSERT dbo.Relations OFF
I haven't tested that, but if it doesn't work as expected then hopefully it will point you in the right direction.
I know you already have a working answer, but I think you can accomplish the same thing a little more simply (not that there is anything at all wrong with Tom H's answer) using the LAG function to inspect the previous row, assuming you have SQL Server 2012 or later.
Setup:
CREATE TABLE #relationtree (
Parent INT,
Child INT
)
CREATE TABLE #relations (
Id INT IDENTITY(1,1),
ParentId INT
)
INSERT INTO #relationtree (Parent, Child) VALUES(1,3), (3,5), (5,6), (5,9)
INSERT INTO #relations (ParentId) values(1), (3), (5)
Solution:
DECLARE #offset INT = IDENT_CURRENT('#relations')
;WITH relationtreeids AS (
SELECT *,
ROW_NUMBER() OVER(ORDER BY Parent, Child) - 2 AS UnmodifiedParentId -- Simulate an identity field
FROM #relationtree
)
INSERT INTO #relations
-- The LAG window function allows you to inspect the previous row
SELECT CASE WHEN LAG(Parent) OVER(ORDER BY Parent) IS NULL
THEN NULL
WHEN LAG(Parent) OVER(ORDER BY Parent) = Parent
THEN UnmodifiedParentId + #offset ELSE UnmodifiedParentId + #offset + 1
END AS ParentId
FROM relationtreeids
Output:
Id ParentId
1 1
2 3
3 5
4 NULL
5 4
6 5
7 5

One SQL statement for counting the records in the master table based on matching records in the detail table?

I have the following master table called Master and sample data
ID---------------Date
1 2014-09-07
2 2014-09-07
3 2014-09-08
The following details table called Details
masterId-------------Name
1 John Walsh
1 John Jones
2 John Carney
1 Peter Lewis
3 John Wilson
Now I want to find out the count of Master records (grouped on the Date column) whose corresponding details record with Name having the value "John".
I cannot figure how to write a single SQL statement for this job.
**Please note that join is needed in order to find master records for count. However, such join creates duplicate master records for count. I need to remove such duplicate records from being counted when grouping on the Date column in the Master table.
The correct results should be:
count: grouped on Date column
2 2014-09-07
1 2014-09-08
**
Thanks and regards!
This answer assumes the following
The Name field is always FirstName LastName
You are looking once and only once for the John firstname. The search criteria would be different, pending what you need
SELECT Date, Count(*)
FROM tblmaster
INNER JOIN tbldetails ON tblmaster.ID=tbldetails.masterId
WHERE NAME LIKE 'John%'
GROUP BY Date, tbldetails.masterId
What we're doing here is using a wilcard character in our string search to say "Look for John where any characters of any length follows".
Also, here is a way to create table variables based on what we're working with
DECLARE #tblmaster as table(
ID int,
[date] datetime
)
DECLARE #tbldetails as table(
masterID int,
name varchar(50)
)
INSERT INTO #tblmaster (ID,[date])
VALUES
(1,'2014-09-07'),(2,'2014-09-07'),(3,'2014-09-08')
INSERT INTO #tbldetails(masterID, name) VALUES
(1,'John Walsh'),
(1,'John Jones'),
(2,'John Carney'),
(1,'Peter Lewis'),
(3,'John Wilson')
Based on all comments below, this SQL statement in it's clunky glory should do the trick.
SELECT date,count(t1.ID) FROM #tblmaster mainTable INNER JOIN
(
SELECT ID, COUNT(*) as countOfAll
FROM #tblmaster t1
INNER JOIN #tbldetails t2 ON t1.ID=t2.masterId
WHERE NAME LIKE 'John%'
GROUP BY id)
as t1 on t1.ID = mainTable.id
GROUP BY mainTable.date
Is this what you want?
select date, count(distinct m.id)
from master m join
details d
on d.masterid = m.id
where name like '%John%'
group by date;

Recursively grab all data based on a parent id

We have a table where rows recursively link to another row. I want to pull data associated with a given parentId and all it's children. Where parentId is one from the root row.
I thought I have seen or done something like that before, but I am unable to find it now. Can this be done in SQL or is it better to do this in code?
I want the list to look like this when I'm done:
Parent
Child
Grandchild
This can be done in SQL Server 2005 and above using Common Table Expressions (CTEs). Here is a great link from MSDN describing recursive queries: Recursive Queries Using Common Table Expressions
Here is an example:
If you imagine a hierarchical line of people, this query will let you see the complete line of any person AND calculates their place in the hierarchy. It can be modified to find any child relationship.
Instead of the ID of the person, you swap in the ID of the row you are using as your parent.
--Create table of dummy data
create table #person (
personID integer IDENTITY(1,1) NOT NULL,
name varchar(255) not null,
dob date,
father integer
);
INSERT INTO #person(name,dob,father)Values('Pops','1900/1/1',NULL);
INSERT INTO #person(name,dob,father)Values('Grandma','1903/2/4',null);
INSERT INTO #person(name,dob,father)Values('Dad','1925/4/2',1);
INSERT INTO #person(name,dob,father)Values('Uncle Kev','1927/3/3',1);
INSERT INTO #person(name,dob,father)Values('Cuz Dave','1953/7/8',4);
INSERT INTO #person(name,dob,father)Values('Billy','1954/8/1',3);
DECLARE #OldestPerson INT;
SET #OldestPerson = 1; -- Set this value to the ID of the oldest person in the family
WITH PersonHierarchy (personID,Name,dob,father, HierarchyLevel) AS
(
SELECT
personID
,Name
,dob
,father,
1 as HierarchyLevel
FROM #person
WHERE personID = #OldestPerson
UNION ALL
SELECT
e.personID,
e.Name,
e.dob,
e.father,
eh.HierarchyLevel + 1 AS HierarchyLevel
FROM #person e
INNER JOIN PersonHierarchy eh ON
e.father = eh.personID
)
SELECT *
FROM PersonHierarchy
ORDER BY HierarchyLevel, father;
DROP TABLE #person;