I have one problem that i can't find nice solution for.
I have relational table Groups_Members with columns GroupId and MemberId.
I have a stored procedure that creates a new group and receives an array of memberId as parameter (user defined type). What I want is to make sure that there is not a group with exactly the same members in the database already.
I'm trying to figure out how EXCEPT operator might help me but I can't. I need condition that would return the group that has exactly the same set of members as those in my memberId parameter (or null or 0 if such group doesn't exist).
Any help would be highly appreciated!
thanks!
Table Groups_Members
GroupId|MemberId
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
2 | 4
3 | 1
3 | 3
3 | 4
Declare #Members table(id int)
insert into #Members
values(1), (3), (4)
Declare #MemberCount int
Select #MemberCount = count(id) From #Members
Select GroupId from
(Select distinct groupId, memberid from Groups_Members) gm
Inner Join #Members On MemberId = id
group by GroupId
Having COUNT(MemberId) = #MemberCount
Result would be 3
Description can be provided on demand.
Declare #Members table(id int)
insert into #Members
values(1), (3), (4)
Declare #MemberCount int
Select #MemberCount = count(id) From #Members
--Select GroupId from
--(Select distinct groupId, memberid from Groups_Members) gm
--Inner Join #Members On MemberId = id
--group by GroupId
--Having COUNT(MemberId) = #MemberCount
When values(1), (3), (4) (it works correctly).
But when values(1), (2) (it does not work correctly).
Nice try but it does not provide the exact matching with group members.
Try this for better solution
Select Gm.GroupId from
(Select distinct GroupId, memberid from Groups_Members) gm
Inner Join #Members On MemberId = id
Inner join (Select COUNT(MemberId) as Totalmember,GroupId
from Groups_Members group by GroupId) tgm on tgm.GroupId = gm.GroupId
where Totalmember = #MemberCount
group by gm.GroupId
Having COUNT(MemberId) = #MemberCount
Description can be provided on demand.
Related
How to join 2 table variables which are not consists of a foreign key column.
DECLARE #InventoryIDList TABLE(ID INT)
DECLARE #ProductSupplierIDList TABLE(ID INT)
Excepted output
#InventoryList
--------------
123
456
789
111
#ProductSupplierIDList
--------------
999
888
777
666
#InventoryList ProductSupplierIDList
---------------------------------------
123 | 999
567 | 888
789 | 777
111 | 666
All are random data. I just want to combine the 2 table variable to look like above. I tried all the types of joins. But I need to have the upper mentioned output without having null values.
I tried the CROSS APPLY
SELECT *
FROM #InventoryIDList invList CROSS APPLY #ProductSupplierIDList prdList
But it gives me 5^2 number of elements as the result with duplicates.
Since the IDs are not in sequential order and can be random, I would recommend using an Identity on the table variables and joining on that:
DECLARE #InventoryIDList TABLE(JoiningID INT IDENTITY(1,1), ID INT)
DECLARE #ProductSupplierIDList TABLE(JoiningID INT IDENTITY(1,1), ID INT)
INSERT INTO #InventoryIDList
VALUES
(123),
(456),
(789),
(111)
INSERT INTO #productsupplierIDList
VALUES
(999),
(888),
(777),
(666)
SELECT i.id, p.id
FROM #inventoryIDList i
INNER JOIN #productsupplierIDList p
oN i.joiningid = p.JoiningID
I guess you need Row_Number and Full Outer Join, considering there is no relation between those 2 tables
SELECT I.ID,
P.ID
FROM (SELECT Rn = Row_number()OVER(ORDER BY ID),*
FROM #InventoryList) I
FULL JOIN (SELECT Rn = Row_number()OVER(ORDER BY ID),*
FROM #ProductSupplierIDList) p
ON I.RN = P.RN
Assuming that the JOIN criteria is the same "row number" in ascending ID order:
WITH invList AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM #InventoryIDList),
prdList AS (
SELECT *,
ROW_NUMBER() OVER (ORDER BY ID) AS RN
FROM #ProductSupplierIDList)
SELECT *
FROM invList IL
JOIN prdList PL ON IL.RN = PL.RN;
Query below works as planned, it shows exactly the way i joined it, and that is fine, but problem with it, is that if you have more "specialization" tables for users, something like "Mail type" or anything that user can have more then one data ... you would have to go two left joins for each and "give priority" via ISNULL (in this case)
I am wondering, how could I avoid using two joins and "give" priority to TypeId 2 over TypeId 1 in a single join, is that even possible?
if object_id('tempdb..#Tab1') is not null drop table #Tab1
create table #Tab1 (UserId int, TypeId int)
if object_id('tempdb..#Tab2') is not null drop table #Tab2
create table #Tab2 (TypeId int, TypeDescription nvarchar(50))
insert into #Tab1 (UserId, TypeId)
values
(1, 1),
(1, 2)
insert into #Tab2 (TypeId, TypeDescription)
values
(1, 'User'),
(2, 'Admin')
select *, ISNULL(t2.TypeDescription, t3.TypeDescription) [Role]
from #Tab1 t1
LEFT JOIN #Tab2 t2 on t1.TypeId = t2.TypeId and
t2.TypeId = 2
LEFT JOIN #Tab2 t3 on t1.TypeId = t3.TypeId and
t3.TypeId = 1
The first problem is determining priority. In this case, you could use the largest TypeId, but that does not seem like a great idea. You could add another column to serve as a priority ordinal instead.
From there, it is a top 1 per group query:
using top with ties and row_number():
select top 1 with ties
t1.UserId, t1.TypeId, t2.TypeDescription
from #Tab1 t1
left join #Tab2 t2
on t1.TypeId = t2.TypeId
order by row_number() over (
partition by t1.UserId
order by t2.Ordinal
--order by t1.TypeId desc
)
using common table expression and row_number():
;with cte as (
select t1.UserId, t1.TypeId, t2.TypeDescription
, rn = row_number() over (
partition by t1.UserId
order by t2.Ordinal
--order by t1.TypeId desc
)
from #Tab1 t1
left join #Tab2 t2
on t1.TypeId = t2.TypeId
)
select UserId, TypeId, TypeDescription
from cte
where rn = 1
rextester demo for both: http://rextester.com/KQAV36173
both return:
+--------+--------+-----------------+
| UserId | TypeId | TypeDescription |
+--------+--------+-----------------+
| 1 | 2 | Admin |
+--------+--------+-----------------+
Actually I don't think you don't need a join at all. But you have to take the max TypeID without respect to the TypeDescription, since these differences can defeat a Group By. So a workaround is to take the Max without TypeDescription initially, then subquery the result to get the TypeDescription.
SELECT dT.*
,(SELECT TypeDescription FROM #Tab2 T2 WHERE T2.TypeId = dT.TypeId) [Role] --2. Subqueries TypeDescription using the Max TypeID
FROM (
select t1.UserId
,MAX(T1.TypeId) [TypeId]
--, T1.TypeDescription AS [Role] --1. differences will defeat group by. Subquery for value later in receiving query.
from #Tab1 t1
GROUP BY t1.UserId
) AS dT
Produces Output:
UserId TypeId Role
1 2 Admin
Considering following table:
SELECT [ItemID]
,[ParentID]
,[PolicyID]
,[PolicyRoot]
FROM [AdventureWorks2008R2].[dbo].[Example]
ItemID ParentID PolicyID PolicyRoot
----------- ----------- ---------- ----------
1 NULL default 1
2 1 b 1
3 1 c 0
4 NULL d 1
5 3 e 0
6 3 f 1
7 NULL g 0
I'm trying to select the PolicyID from each item where PolicyRoot = 1, in case PolicyRoot = 0 I need to use PolicyID from its ParentID. This is recursive...
Working with a function:
CREATE FUNCTION dbo.Policies(#ItemID INT) RETURNS VARCHAR(10)
AS
BEGIN
DECLARE #ParentID INT, #PolicyRoot BIT, #PolicyID VARCHAR(10)
SELECT #ParentID = ParentID
, #PolicyRoot = PolicyRoot
, #PolicyID = PolicyID
FROM [dbo].[Example]
WHERE ItemID = #ItemID
IF #PolicyRoot != 1
SELECT #PolicyID = dbo.Policies(#ParentID)
RETURN #PolicyID
END;
GO
SELECT ItemID
, dbo.Policies(ItemID) AS Policy
FROM [dbo].[Example];
ItemID Policy
----------- ----------
1 default
2 b
3 default
4 d
5 default
6 f
7 NULL
I'm trying to rewrite this function to a CTE, but I don't have any CTE knowledge yet. I've read into multiple CTE's but I don't have a single clue how to manage a conditional CTE. This is as far as I've gotten, I'm not familiar (enough) with the UNION ALL.
WITH Policies (ItemID, PolicyID) AS (
SELECT ItemID
, PolicyID
FROM dbo.Example
UNION ALL
...
)
SELECT ItemID
, PolicyID
FROM Policies;
Can someone explain me in plain steps how such a CTE works and push me in the right direction?
A recursive CTE works by joining to itself, using a UNION ALL to collate the results.
You start with yourtable to populate the initial dataset of the recursive query
select * from yourtable
and you add to that with the UNION ALL, further results
select c.ItemID, t2.ParentID, t2.PolicyID, t2.PolicyRoot
from yourtable t2
inner join c on c.ParentID = t2.ItemID
where c.PolicyRoot=0
and the recursion occurs in this - where the results of this query are fed through this query again and again, up to the MAXRECURSION limit, or when no more results are added.
;with c as
(
select * from yourtable
union all
select c.ItemID, t2.ParentID, t2.PolicyID, t2.PolicyRoot
from yourtable t2
inner join c on c.ParentID = t2.ItemID
where c.PolicyRoot=0
)
select t.ItemID, c.PolicyID
from yourtable t
left join c on t.ItemID = c.ItemID
and c.PolicyRoot=1
MSSQL
Table looks like so
ID 1 | 2 | 3 | 4 | 5
AA1 1 | 1 | 1 | 2 | 1
any clues on how I could make a query to return
ID | MaxNo
AA1 | 4
, usign the above table example? I know I could write a case blah when statement, but I have a feeling there's a much simpler way of doing this
You can use UNPIVOT to get these comparable items, correctly1, into the same column, and then use ROW_NUMBER() to find the highest valued row2:
declare #t table (ID char(3) not null,[1] int not null,[2] int not null,
[3] int not null,[4] int not null,[5] int not null)
insert into #t (ID,[1],[2],[3],[4],[5]) values
('AA1',1,1,1,2,1)
;With Unpivoted as (
select *,ROW_NUMBER() OVER (ORDER BY Value desc) rn
from #t t UNPIVOT (Value FOR Col in ([1],[2],[3],[4],[5])) u
)
select * from Unpivoted where rn = 1
Result:
ID Value Col rn
---- ----------- ------------------------- --------------------
AA1 2 4 1
1 If you have data from the same "domain" appearing in multiple columns in the same table (such that it even makes sense to compare such values), it's usually a sign of attribute splitting, where part of your data has, incorrectly, been used to form part of a column name.
2 In your question, you say "per row", and yet you've only given a one row sample. If we assume that ID values are unique for each row, and you want to find the maximum separately for each ID, you'd write the ROW_NUMBER() as ROW_NUMBER() OVER (PARTITION BY ID ORDER BY Value desc) rn, to get (I hope) the result you're looking for.
You can use a cross apply where you do max() over the columns for one row.
select T1.ID,
T2.Value
from YourTable as T1
cross apply
(
select max(T.Value) as Value
from (values (T1.[1]),
(T1.[2]),
(T1.[3]),
(T1.[4]),
(T1.[5])) as T(Value)
) as T2
If you are on SQL Server 2005 you can use union all in the derived table instead of values().
select T1.ID,
T2.Value
from YourTable as T1
cross apply
(
select max(T.Value) as Value
from (select T1.[1] union all
select T1.[2] union all
select T1.[3] union all
select T1.[4] union all
select T1.[5]) as T(Value)
) as T2
SQL Fiddle
I have a table which contains hierarchy data - something like:
childID | parentID
____________________
1 | 5
5 | 9
9 | 20
2 | 4
3 | 7
7 | 8
8 | 8
20 | 20
4 | 4
8 | 8
desired output:
I've created a recursive CTE which finds me the top fatherID.
Something like:
;WITH cte AS (
SELECT a.childID
,a.parentID
,1 AS lvl
FROM [Agent_Agents] a
WHERE a.childID = 214 //<==== value to begin with !! - thats part the problem
UNION ALL
SELECT tmp.childID
,tmp.parentID
,cte.lvl+1
FROM [Agent_Agents] tmp
INNER JOIN cte ON tmp.childID = cte.parentID
WHERE cte.childID<>cte.parentID
)
SELECT *
FROM cte
WHERE lvl = (
SELECT MAX(lvl)
FROM cte
)
The problem:
I executed the CTE with explicit childID value to begin with (214) !
So it gives me the value for 214 only.
the CTE do the recursive part and find topParent for childID.
but
I want ForEach row in the Table - to execute the CTE with the childID value !
I have tried to do it with CROSS APPLY:
Something like:
select * from myTable Cross Apply (
;WITH cte AS (....)
)
but IMHO (from my testing !!) - its impossible.
The other idea of putting the recursive CTE in a UDF has a performance penalty (udf's problem as we know).
How can I create this query so that it'll actually work? ( or some near solution )?
here is what I've tried
https://data.stackexchange.com/stackoverflow/query/edit/69458
Can't you do something like this?
;WITH cte AS (....)
SELECT
*
FROM
cte
CROSS APPLY
dbo.myTable tbl ON cte.XXX = tbl.XXX
Put the CROSS APPLY after the CTE definition - into the one SQL statement that refers back to the CTE. Wouldn't that work??
OR: - flip around your logic - do a "top-down" CTE, that picks the top-level nodes first, and then iterates through the hiearchy. This way, you can easily determine the "top-level father" in the first part of the recursive CTE - something like this:
;WITH ChildParent AS
(
SELECT
ID,
ParentID = ISNULL(ParentID, -1),
SomeName,
PLevel = 1, -- defines level, 1 = TOP, 2 = immediate child nodes etc.
TopLevelFather = ID -- define "top-level" parent node
FROM dbo.[Agent_Agents]
WHERE ParentID IS NULL
UNION ALL
SELECT
a.ID,
ParentID = ISNULL(a.ParentID, -1),
a.SomeName,
PLevel = cp.PLevel + 1,
cp.TopLevelFather -- keep selecting the same value for all child nodes
FROM dbo.[Agent_Agents] a
INNER JOIN ChildParent cp ON r.ParentID = cp.ID
)
SELECT
ID,
ParentID,
SomeName,
PLevel,
TopLevelFather
FROM ChildParent
This would give you nodes something like this (based on your sample data, slightly extended):
ID ParentID SomeName PLevel TopLevelFather
20 -1 Top#20 1 20
4 -1 TOP#4 1 4
8 -1 TOP#8 1 8
7 8 ChildID = 7 2 8
3 7 ChildID = 3 3 8
2 4 ChildID = 2 2 4
9 20 ChildID = 9 2 20
5 9 ChildID = 5 3 20
1 5 ChildID = 1 4 20
Now if you select a particular child node from this CTE output, you'll always get all the infos you need - including the "level" of the child, and its top-level parent node.
Not sure I understand what you are looking for but it could be this.
;WITH c
AS (SELECT childid,
parentid,
parentid AS topParentID
FROM #myTable
WHERE childid = parentid
UNION ALL
SELECT T.childid,
T.parentid,
c.topparentid
FROM #myTable AS T
INNER JOIN c
ON T.parentid = c.childid
WHERE T.childid <> T.parentid)
SELECT childid,
topparentid
FROM c
ORDER BY childid
SE-Data
It is the same as answer by marc_s with the difference that I use your table variable and the fact that you have childID = parentID for root nodes where the answer by marc_s has parent_ID = null for root nodes. In my opinion it is better to have parent_ID = null for root nodes.
I have not yet the time to look further into your question and am not sure whether or not i've understood your problem, but couldn't you use this svf to get the top father's id?
CREATE FUNCTION [dbo].[getTopParent] (
#ChildID INT
)
RETURNS int
AS
BEGIN
DECLARE #result int;
DECLARE #ParentID int;
SET #ParentID=(
SELECT ParentID FROM ChildParent
WHERE ChildID = #ChildID
)
IF(#ParentID IS NULL)
SET #result = #ChildID
ELSE
SET #result = [dbo].[getTopParent](#ParentID)
RETURN #result
END
Then you should be able to find each top parent in this way:
SELECT ChildID
, [dbo].[getTopParent](ChildID) AS TopParentID
FROM ChildParent
select distinct
a.ChildID,a.ParentID,
--isnull(nullif(c.parentID,b.parentID),a.parentID) as toppa,
B.parentID
--,c.parentID
,isnull(nullif(d.parentID,a.parentID),c.parentID) as toppa1,a.name
from myTable a
inner join myTable c
on a.parentID=c.parentID
inner join myTable b
on b.childID=a.parentID
inner join myTable d
on d.childID=b.parentID
I have using the without CTE expression and then using joins to get the step to step parent for child and then more important Common table expressions were introduced in SQL Server 2005 not in server 2000 so using joins to get values this is basic way for to get parentid for a child value
select dbo.[fn_getIMCatPath](8)
select Cat_id,Cat_name,dbo.[fn_getIMCatPath](cat_id) from im_category_master
Create FUNCTION [dbo].[fn_getIMCatPath] (#ID INT)
returns NVARCHAR(1000)
AS
BEGIN
DECLARE #Return NVARCHAR(1000),
#parentID INT,
#iCount INT
SET #iCount = 0
SELECT #Return = Cat_name,
#parentID = parent_id
FROM im_category_master
WHERE [cat_id] = #ID
WHILE #parentID IS NOT NULL
BEGIN
SELECT #Return = cat_name + '>' + #Return,
#parentID = parent_id
FROM im_category_master
WHERE [cat_id] = #parentID
SET #iCount = #iCount + 1
IF #parentID = -1
BEGIN
SET #parentID = NULL
END
IF #iCount > 10
BEGIN
SET #parentID = NULL
SET #Return = ''
END
END
RETURN #Return
END
Consider this sample data and respective SQL to access child records along with their top parent.
Sample DATA
SQL code:
;WITH c AS (
SELECT Id, Name, ParentId as CategoryId,
Id as MainCategoryId, Name AS MainCategory
FROM pmsItemCategory
WHERE ParentId is null
UNION ALL
SELECT T.Id, T.Name, T.ParentId, MainCategoryId, MainCategory
FROM pmsItemCategory AS T
INNER JOIN c ON T.ParentId = c.Id
WHERE T.ParentId is not null
)
SELECT Id, Name, CategoryId, MainCategoryId, MainCategory
FROM c
order by Id
select distinct
a.ChildID,a.ParentID,
--isnull(nullif(c.parentID,b.parentID),a.parentID) as toppa,
B.parentID
--,c.parentID
,isnull(nullif(d.parentID,a.parentID),c.parentID) as toppa1,a.name
from myTable a
inner join myTable c
on a.parentID=c.parentID
inner join myTable b
on b.childID=a.parentID
inner join myTable d
on d.childID=b.parentID
With cte as
(
Select ChileId,Name,ParentId from tblHerarchy
where ParentId is null
union ALL
Select h.ChileId,h.Name,h.ParentId from cte
inner join tblHerarchy h on h.ParentId=cte.ChileId
)
Select * from cte
With cteherarchy as
(
Select ChileId,Name,ParentId from tblHerarchy
where ParentId is null
union ALL
Select h.ChileId,h.Name,h.ParentId from cte
inner join tblHerarchy h on h.ParentId=cte.ChileId
)
Select * from cteherarchy