referencing a table variable - sql

I have a problem referencing a table variable in a update statement. Seems I can't use the #a.id column (the compiler says it's not declared).
The following example was written only to illustrate the problem, meaning that I know that I can solve the problem in the current example renaming the column id and avoiding the #a.id reference, but it's not a option, I really can't do that. I saw some solutions using the from statement to alias the table being updated, but in this example I'm using the from statement for something else. Is there another way to solve it?
declare #a table
(
id int not null,
name varchar(100) null
)
insert into #a (id, name) values (1, null)
insert into #a (id, name) values (2, null)
insert into #a (id, name) values (3, null)
declare #b table
(
id int not null,
name varchar(100) null
)
insert into #b (id, name) values (1, 'one')
insert into #b (id, name) values (2, 'two')
update #a
set
name = f.name
from
(
select
id,
name
from #b
where
id = #a.id
) f
where
#a.id = f.id

Try something like this:
declare #a table
(
id int not null,
name varchar(100) null
)
insert into #a (id, name) values (1, null)
insert into #a (id, name) values (2, null)
insert into #a (id, name) values (3, null)
declare #b table
(
id int not null,
name varchar(100) null
)
insert into #b (id, name) values (1, 'one')
insert into #b (id, name) values (2, 'two')
update upd
set
name = [#b].name
from #a AS upd
INNER JOIN #b
ON upd.id = [#b].id
And BTW following will work too:
update #a
set
name = f.name
from
(
select
id,
name
from #b
) f
where
[#a].id = f.id

The reference to #a.id is illegal because the table is out of scope at that point. Here's what you might try:
Update #a
set name = f.name
from #b g
join #a f
on g.id=f.id

Related

Sql Server While Loop with Changing Condition

I have a User Table in my database that contains two fields
user_id
manager_id
I am trying to construct a query to list all of the manager_ids that are associated with a user_id in a hierarchical structure.
So if i give a user_id, i will get that users manager, followed by that persons manager all the way to the very top.
So far i have tried but it doesnt give what i need:
WITH cte(user_id, manager_id) as (
SELECT user_id, manager_id
FROM user
WHERE manager_id=#userid
UNION ALL
SELECT u.user_id, u.manager_id,
FROM user u
INNER JOIN cte c on e.manager_id = c.employee_id
)
INSERT INTO #tbl (manager_id)
select user_id, manager_id from cte;
If anyone can point me in the right direction that would be great.
I thought about a While loop but this may not be very efficient and im not too sure how to implement that.
OP asked for a while loop, and while (ha, pun) this may not be the best way... Ask and you shall receive. (:
Here is sample data I created (in the future, please provide this):
CREATE TABLE #temp (userID int, managerID int)
INSERT INTO #temp VALUES (1, 3)
INSERT INTO #temp VALUES (2, 3)
INSERT INTO #temp VALUES (3, 7)
INSERT INTO #temp VALUES (4, 6)
INSERT INTO #temp VALUES (5, 7)
INSERT INTO #temp VALUES (6, 9)
INSERT INTO #temp VALUES (7, 10)
INSERT INTO #temp VALUES (8, 10)
INSERT INTO #temp VALUES (9, 10)
INSERT INTO #temp VALUES (10, 12)
INSERT INTO #temp VALUES (11, 12)
INSERT INTO #temp VALUES (12, NULL)
While Loop:
CREATE TABLE #results (userID INT, managerID INT)
DECLARE #currentUser INT = 1 -- Would be your parameter!
DECLARE #maxUser INT
DECLARE #userManager INT
SELECT #maxUser = MAX(userID) FROM #temp
WHILE #currentUser <= #maxUser
BEGIN
SELECT #userManager = managerID FROM #temp WHERE userID = #currentUser
INSERT INTO #results VALUES (#currentUser, #userManager)
SET #currentUser = #userManager
END
SELECT * FROM #results
DROP TABLE #temp
DROP TABLE #results
Get rid of this column list in your CTE declaration that has nothing to do with the columns you are actually selecting in the CTE:
WITH cte(employee_id, name, reports_to_emp_no, job_number) as (
Just make it this:
WITH cte as (
I recommend recursive solution:
WITH Parent AS
(
SELECT * FROM user WHERE user_id=#userId
UNION ALL
SELECT T.* FROM user T
JOIN Parent P ON P.manager_id=T.user_id
)
SELECT * FROM Parent
To see demo, run following:
SELECT * INTO #t FROM (VALUES (1,NULL),(2,1),(3,2),(4,1)) T(user_id,manager_id);
DECLARE #userId int = 3;
WITH Parent AS
(
SELECT * FROM #t WHERE user_id=#userId
UNION ALL
SELECT T.* FROM #t T
JOIN Parent P ON P.manager_id=T.user_id
)
SELECT * FROM Parent

Find Groups that don't contain all records

I feel like I should be able to get this and I'm just having a brain fart. I've simplified the problem to the following example:
DECLARE #A TABLE (ID int);
DECLARE #B TABLE (GroupID char(1), ID int);
INSERT #A VALUES (1);
INSERT #A VALUES (2);
INSERT #A VALUES (3);
INSERT #B VALUES ('X', 1);
INSERT #B VALUES ('X', 2);
INSERT #B VALUES ('X', 3);
INSERT #B VALUES ('Y', 1);
INSERT #B VALUES ('Y', 2);
INSERT #B VALUES ('Z', 1);
INSERT #B VALUES ('Z', 2);
INSERT #B VALUES ('Z', 3);
INSERT #B VALUES ('Z', 4);
So table A contains a set of some records. Table B contains multiple copies of the set contained in A with Group IDs. But some of those groups may be missing one or more records of the set. I want to find the groups that are missing records. So in the above example, my results should be:
GroupID
-------
Y
But for some reason I just can't wrap my head around this, today. Any help would be appreciated.
Awesome use-case for relational division! (Here's a must-read blog post about it)
SELECT DISTINCT b1.GroupID
FROM #B b1
WHERE EXISTS (
SELECT 1
FROM #A a
WHERE NOT EXISTS (
SELECT 1
FROM #B b2
WHERE b1.GroupID = b2.GroupID
AND b2.ID = a.ID
)
);
How to read this?
I want all distinct GroupIDs in #B for which there is a record in #A for which there isn't a record in #B with the same #A.ID
In fact, this is the "remainder" of the relational division.
try this
SELECT GroupID ,COUNT(GroupID )
FROM #a INNER JOIN #b
ON #a.id=#b.id
GROUP BY GroupID
HAVING COUNT(GroupID )<(SELECT count(*) FROM #a)
This will give you all the combinations that are missing.
select FullList.*
from (select distinct a.ID,
b.GroupId
from #A a
cross join #B b) FullList
left join #B b
on FullList.ID = b.ID
and FullList.GroupID = b.GroupID
where b.ID is null
The answer to your question would just be the same but with the first line:
select distinct FullList.GroupID
This will give you all the combinations that are missing.
select FullList.*
from (select distinct a.ID,
b.GroupId
from #A a
cross join (select distinct db.GroupId from #B db) b
) as FullList
left join #B b
on FullList.ID = b.ID
and FullList.GroupID = b.GroupID
where b.ID is null
The answer to your question would just be the same but with the first line:
select distinct FullList.GroupID

CTE to build hierarchy from source table

I am trying to build a CTE or just some query that takes hierarchial data from one table and insert it into another table that might have a different index. I thought this would be simple, but for some reason I'm getting stuck. I can't get the output to read correctly on the 'RequiredID' with the newly seeded index. Btw I'm working in SQL Server 2012.
Below I've provided proof of concept code to demonstrate what I'm going for. The actual SQL is more complex, but this illustrates the point.
This is what I've got so far:
DECLARE #Table TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
INSERT INTO #Table (ID, Code, RequiredID) VALUES
(1, 'Physics', NULL),
(2, 'Advanced Physics', 1),
(3, 'Nuke', 2),
(4, 'Health', NULL);
DECLARE #DefaultSeed TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
WITH hierarchy
AS (
--anchor
SELECT t.ID , t.Code , t.RequiredID
FROM #Table AS t
WHERE t.RequiredID IS NULL
UNION ALL
--recursive
SELECT t.ID
, t.Code
, h.ID
FROM hierarchy AS h
JOIN #Table AS t
ON t.RequiredID = h.ID
)
INSERT INTO #DefaultSeed (ID, Code, RequiredID)
SELECT ID
, Code
, RequiredID
FROM hierarchy
OPTION (MAXRECURSION 10)
DECLARE #NewSeed TABLE (ID INT IDENTITY(10, 1), Code NVARCHAR(50), RequiredID INT)
--this is where I get stuck - I can't get the requiredID to read like below
INSERT INTO #NewSeed (Code, RequiredID)
SELECT Code, RequiredID
FROM #DefaultSeed
--I'm trying to get #NewSeed should read like the following...
[ID] [Code] [RequiredID]
10....Physics..........NULL
11....Health...........NULL
12....AdvancedPhysics..10
13....Nuke.............12
SELECT *
FROM #NewSeed
Any help would be greatly appreciated!
You can use OUTPUT in combination with Merge to get a Mapping from ID's to new ID's.
The essential part:
--this is where you got stuck
Declare #MapIds Table (aOldID int,aNewID int)
;MERGE INTO #NewSeed AS TargetTable
Using #DefaultSeed as Source on 1=0
WHEN NOT MATCHED then
Insert (Code,RequiredID)
Values
(Source.Code,Source.RequiredID)
OUTPUT Source.ID ,inserted.ID into #MapIds;
Update #NewSeed Set RequiredID=aNewID
from #MapIds
Where RequiredID=aOldID
and the whole example:
DECLARE #Table TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
INSERT INTO #Table (ID, Code, RequiredID) VALUES
(1, 'Physics', NULL),
(2, 'Advanced Physics', 1),
(3, 'Nuke', 2),
(4, 'Health', NULL);
DECLARE #DefaultSeed TABLE (ID INT, Code NVARCHAR(50), RequiredID INT);
WITH hierarchy
AS (
--anchor
SELECT t.ID , t.Code , t.RequiredID
FROM #Table AS t
WHERE t.RequiredID IS NULL
UNION ALL
--recursive
SELECT t.ID
, t.Code
, h.ID
FROM hierarchy AS h
JOIN #Table AS t
ON t.RequiredID = h.ID
)
INSERT INTO #DefaultSeed (ID, Code, RequiredID)
SELECT ID
, Code
, RequiredID
FROM hierarchy
OPTION (MAXRECURSION 10)
DECLARE #NewSeed TABLE (ID INT IDENTITY(10, 1), Code NVARCHAR(50), RequiredID INT)
Declare #MapIds Table (aOldID int,aNewID int)
;MERGE INTO #NewSeed AS TargetTable
Using #DefaultSeed as Source on 1=0
WHEN NOT MATCHED then
Insert (Code,RequiredID)
Values
(Source.Code,Source.RequiredID)
OUTPUT Source.ID ,inserted.ID into #MapIds;
Update #NewSeed Set RequiredID=aNewID
from #MapIds
Where RequiredID=aOldID
/*
--#NewSeed should read like the following...
[ID] [Code] [RequiredID]
10....Physics..........NULL
11....Health...........NULL
12....AdvancedPhysics..10
13....Nuke.............12
*/
SELECT *
FROM #NewSeed

project a sparse result at some level

I don't really know what to call this but it's not that hard to explain
Basically what I have is a result like this
Similarity ColumnA ColumnB ColumnC
1 SomeValue NULL SomeValue
2 NULL SomeB NULL
3 SomeValue NULL SomeC
4 SomeA NULL NULL
This result is created by matching a set of strings against another table. Each string also contains some values for these ColumnA..C which are the values I wan't to aggregate in some way.
Something like min/max works very well but I can't figure out how to get it to account for the highest similarity not just the min/max value. I don't really want the min/max, I want the first non-null value with the highest similarity.
Ideally the result would look like this
ColumnA ColumnB ColumnC
SomeA SomeB SomeC
I'd like be able to efficiently join in the temporary result to compute the rest and I've been exploring different options. Something which I've been considering is creating a SQL Server CLR aggregate the yields the "first" non-null value but I'm unsure if there's even such a thing as a first or last when running an aggregate on a result.
Okay, so I figured it out, I originally had trouble with the UPDATE FROM and JOIN not playing well together. I was counting on that the UPDATE would just occur multiple times and that would give me the correct results, however, there's no such guarantee from SQL Server (it's actually undefined behavior and alltough it appeared to work we'll have none of that) but since you can run UPDATE against a CTE I combined that with the OUTER APPLY to select the exactly 1 row to complement a missing value if possible.
Here's the whole thing with test data as well.
DECLARE #cost TABLE (
make nvarchar(100) not null,
model nvarchar(100),
a numeric(18,2),
b numeric(18,2)
);
INSERT #cost VALUES ('a%', null, 100, 2);
INSERT #cost VALUES ('a%', 'a%', 149, null);
INSERT #cost VALUES ('a%', 'ab', 349, null);
INSERT #cost VALUES ('b', null, null, 2.5);
INSERT #cost VALUES ('b', 'b%', 249, null);
INSERT #cost VALUES ('b', 'b', null, 3);
DECLARE #unit TABLE (
id int,
make nvarchar(100) not null,
model nvarchar(100)
);
INSERT #unit VALUES (1, 'a', null);
INSERT #unit VALUES (2, 'a', 'a');
INSERT #unit VALUES (3, 'a', 'ab');
INSERT #unit VALUES (4, 'b', null);
INSERT #unit VALUES (5, 'b', 'b');
DECLARE #tmp TABLE (
id int,
specificity int,
a numeric(18,2),
b numeric(18,2),
primary key(id, specificity)
);
INSERT #tmp
OUTPUT inserted.* --FOR DEBUGGING
SELECT
unit.id
, ROW_NUMBER() OVER (
PARTITION BY unit.id
ORDER BY cost.make DESC, cost.model DESC
) AS specificity
, cost.a
, cost.b
FROM #unit unit
INNER JOIN #cost cost ON unit.make LIKE cost.make
AND (cost.model IS NULL OR unit.model LIKE cost.model)
;
--fix the holes
WITH tmp AS (
SELECT *
FROM #tmp
WHERE specificity = 1
AND (a IS NULL OR b IS NULL) --where necessary
)
UPDATE tmp
SET
tmp.a = COALESCE(tmp.a, a.a)
, tmp.b = COALESCE(tmp.b, b.b)
OUTPUT inserted.* --FOR DEBUGGING
FROM tmp
OUTER APPLY (
SELECT TOP 1 a
FROM #tmp a
WHERE a.id = tmp.id
AND a.specificity > 1
AND a.a IS NOT NULL
ORDER BY a.specificity
) a
OUTER APPLY (
SELECT TOP 1 b
FROM #tmp b
WHERE b.id = tmp.id
AND b.specificity > 1
AND b.b IS NOT NULL
ORDER BY b.specificity
) b
;

Find lowest common parent in recursive SQL table

Suppose I have a recursive table (e.g. employees with managers) and a list of size 0..n of ids. How can I find the lowest common parent for these ids?
For example, if my table looks like this:
Id | ParentId
---|---------
1 | NULL
2 | 1
3 | 1
4 | 2
5 | 2
6 | 3
7 | 3
8 | 7
Then the following sets of ids lead to the following results (the first one is a corner case):
[] => 1 (or NULL, doesn't really matter)
[1] => 1
[2] => 2
[1,8] => 1
[4,5] => 2
[4,6] => 1
[6,7,8] => 3
How to do this?
EDIT: Note that parent isn't the correct term in all cases. It's the lowest common node in all paths up the tree. The lowest common node can also be a node itself (for example in the case [1,8] => 1, node 1 is not a parent of node 1 but node 1 itself).
Kind regards,
Ronald
Here's one way of doing it; it uses a recursive CTE to find the ancestry of a node, and uses "CROSS APPLY" over the input values to get the common ancestry; you just change the values in #ids (table variable):
----------------------------------------- SETUP
CREATE TABLE MyData (
Id int NOT NULL,
ParentId int NULL)
INSERT MyData VALUES (1,NULL)
INSERT MyData VALUES (2,1)
INSERT MyData VALUES (3,1)
INSERT MyData VALUES (4,2)
INSERT MyData VALUES (5,2)
INSERT MyData VALUES (6,3)
INSERT MyData VALUES (7,3)
INSERT MyData VALUES (8,7)
GO
CREATE FUNCTION AncestorsUdf (#Id int)
RETURNS TABLE
AS
RETURN (
WITH Ancestors (Id, ParentId)
AS (
SELECT Id, ParentId
FROM MyData
WHERE Id = #Id
UNION ALL
SELECT md.Id, md.ParentId
FROM MyData md
INNER JOIN Ancestors a
ON md.Id = a.ParentId
)
SELECT Id FROM Ancestors
);
GO
----------------------------------------- ACTUAL QUERY
DECLARE #ids TABLE (Id int NOT NULL)
DECLARE #Count int
-- your data (perhaps via a "split" udf)
INSERT #ids VALUES (6)
INSERT #ids VALUES (7)
INSERT #ids VALUES (8)
SELECT #Count = COUNT(1) FROM #ids
;
SELECT TOP 1 a.Id
FROM #ids
CROSS APPLY AncestorsUdf(Id) AS a
GROUP BY a.Id
HAVING COUNT(1) = #Count
ORDER BY a.ID DESC
Update if the nodes aren't strictly ascending:
CREATE FUNCTION AncestorsUdf (#Id int)
RETURNS #result TABLE (Id int, [Level] int)
AS
BEGIN
WITH Ancestors (Id, ParentId, RelLevel)
AS (
SELECT Id, ParentId, 0
FROM MyData
WHERE Id = #Id
UNION ALL
SELECT md.Id, md.ParentId, a.RelLevel - 1
FROM MyData md
INNER JOIN Ancestors a
ON md.Id = a.ParentId
)
INSERT #result
SELECT Id, RelLevel FROM Ancestors
DECLARE #Min int
SELECT #Min = MIN([Level]) FROM #result
UPDATE #result SET [Level] = [Level] - #Min
RETURN
END
GO
and
SELECT TOP 1 a.Id
FROM #ids
CROSS APPLY AncestorsUdf(Id) AS a
GROUP BY a.Id, a.[Level]
HAVING COUNT(1) = #Count
ORDER BY a.[Level] DESC
After doing some thinking and some hints in the right direction from Marc's answer (thanks), I came up with another solution myself:
DECLARE #parentChild TABLE (Id INT NOT NULL, ParentId INT NULL);
INSERT INTO #parentChild VALUES (1, NULL);
INSERT INTO #parentChild VALUES (2, 1);
INSERT INTO #parentChild VALUES (3, 1);
INSERT INTO #parentChild VALUES (4, 2);
INSERT INTO #parentChild VALUES (5, 2);
INSERT INTO #parentChild VALUES (6, 3);
INSERT INTO #parentChild VALUES (7, 3);
INSERT INTO #parentChild VALUES (8, 7);
DECLARE #ids TABLE (Id INT NOT NULL);
INSERT INTO #ids VALUES (6);
INSERT INTO #ids VALUES (7);
INSERT INTO #ids VALUES (8);
DECLARE #count INT;
SELECT #count = COUNT(1) FROM #ids;
WITH Nodes(Id, ParentId, Depth) AS
(
-- Start from every node in the #ids collection.
SELECT pc.Id , pc.ParentId , 0 AS DEPTH
FROM #parentChild pc
JOIN #ids i ON pc.Id = i.Id
UNION ALL
-- Recursively find parent nodes for each starting node.
SELECT pc.Id , pc.ParentId , n.Depth - 1
FROM #parentChild pc
JOIN Nodes n ON pc.Id = n.ParentId
)
SELECT n.Id
FROM Nodes n
GROUP BY n.Id
HAVING COUNT(n.Id) = #count
ORDER BY MIN(n.Depth) DESC
It now returns the entire path from the lowest common parent to the root node but that is a matter of adding a TOP 1 to the select.