对于这种情况,是否存在单个自联接?

时间:2009-09-04 18:07:41

标签: sql sql-server self-join

我有一个表ALPHA,包含2个字段GroupId,成员:

GroupId | Member;
A1----------A;
A1----------B; 
A1----------C;
A2----------A;
A2----------B;
A3----------A;
A3----------D;
A3----------E;

目标:鉴于-A,B,C的输入,我必须查询该表以查找是否存在这组精确成员的GroupId。所以,这就是我打算做的事情:

  1. 查询表中所有计数为3的GroupId(因为我的inpt是A,B,C ..我知道它的3)
  2. 这将给我A1,A3。现在,查询此集合以获得完全匹配的成员值...这将给我A1。
  3. 我打算写一个存储过程,并以某种方式实现目标。但是,我的问题可以在单个查询中实现......也许是单个自联接。

    澄清:(A,B,C)的集合是A1独有的。如果输入(A,B,C,D),则查询不应返回A1。

7 个答案:

答案 0 :(得分:4)

SELECT GroupID
  FROM ALPHA
 WHERE Member IN ('A', 'B', 'C')
 GROUP BY GroupID
HAVING COUNT(*) = 3

这依赖于您在IN子句中写出成员列表并在HAVING子句中设置成员列表中的(不同)条目数。如果你不能这样生成SQL,那么你必须更加努力。


正如早期评论中所指出的,这也依赖于您希望A,B,C(以及可能还有其他)三者都是该组成员的群体的解释。获得'组中恰好包含三个人,即A,B,C'的一种方式,不一定是最好的方法,是使用:

SELECT GroupID
  FROM ALPHA A1
 WHERE Member IN ('A', 'B', 'C')
   AND 3 = (SELECT COUNT(*) FROM ALPHA A2 WHERE A2.GroupID = A1.GroupID)
 GROUP BY GroupID
HAVING COUNT(*) = 3

这明确检查组中的总人数是3,成员是A,B和C(假设Alpha上有唯一约束(GroupID,Member),以便成员不能被列为属于同一组的两次。)

答案 1 :(得分:2)

SELECT DISTINCT aa.GroupId
FROM Alpha aa
JOIN Alpha ab ON (aa.GroupId = ab.GroupId)
JOIN Alpha ac ON (aa.GroupId = ac.GroupId)
LEFT OUTER JOIN Alpha ax ON (aa.GroupId = ax.GroupId AND ax.Member NOT IN ('A', 'B', 'C')
WHERE aa.Member = 'A' AND ab.Member = 'B' AND ac.Member = 'C'
 AND ax.GroupId IS NULL;

还有涉及GROUP BY的解决方案,但我发现JOIN解决方案通常具有更好的性能。我通常在MySQL工作,我理解MS SQL Server更擅长分组查询。因此,尝试两种解决方案,看看哪种解决方案最适合您使用的RDBMS品牌。

答案 2 :(得分:2)

到目前为止给出的答案假设Member字段对于任何给定的GroupID都是唯一的。在工作中,我所做的并非如此。而且,如果该小组拥有您正在寻找的内容,还有一些额外内容,那么您需要将该小组排除在外。

SELECT
   [Alpha].GroupID
FROM
   [Alpha]
GROUP BY
   [Alpha].GroupID
HAVING
       SUM(CASE WHEN [alpha].Member IN ('A','B','C') THEN 1 ELSE 0 END) = 3
   AND MIN(CASE WHEN [alpha].Member IN ('A','B','C') THEN 1 ELSE 0 END) = 1


您还可以将IN子句替换为包含您正在搜索的成员的表的连接...

SELECT
   [Alpha].GroupID
FROM
   [Alpha]
LEFT JOIN
   [Search]
       ON [Search].Member
GROUP BY
   [Alpha].GroupID
HAVING
       SUM(CASE WHEN [alpha].Member = [search].Member THEN 1 ELSE 0 END) = (SELECT COUNT(*) FROM [search])
   AND MIN(CASE WHEN [alpha].Member = [search].Member THEN 1 ELSE 0 END) = 1

答案 3 :(得分:2)

试试这个:

declare @YourTable table (GroupID char(2),Member char(1))

insert into @YourTable values ('A1','A')
insert into @YourTable values ('A1','B')
insert into @YourTable values ('A1','C')
insert into @YourTable values ('A2','A')
insert into @YourTable values ('A2','B')
insert into @YourTable values ('A3','A')
insert into @YourTable values ('A3','D')
insert into @YourTable values ('A3','E')
insert into @YourTable values ('A5','A')
insert into @YourTable values ('A5','B')
insert into @YourTable values ('A5','C')
insert into @YourTable values ('A5','D')
SELECT t1.GroupID
    FROM @YourTable t1
        LEFT OUTER JOIN @YourTable t2 ON t1.GroupID=t2.GroupID AND t2.Member NOT IN ('A', 'B', 'C') 
    WHERE t1.Member IN ('A', 'B', 'C') 
        AND t2.GroupID IS NULL
    GROUP BY t1.GroupID
    HAVING COUNT(*) = 3

输出:

GroupID
-------
A1

(1 row(s) affected)

这是一个完整的解决方案:

在使用我的功能之前,您需要设置一个“帮助程序”表,每个数据库只需执行一次这样的操作:

CREATE TABLE Numbers
(Number int  NOT NULL,
    CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number ASC)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
DECLARE @x int
SET @x=0
WHILE @x<8000
BEGIN
    SET @x=@x+1
    INSERT INTO Numbers VALUES (@x)
END

使用此函数来分割您的字符串,该字符串不会循环且非常快:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn              char(1)              --REQUIRED, the character to split the @List string on
    ,@List                 varchar(8000)        --REQUIRED, the list to split apart
)
RETURNS
@ParsedList table
(
    ListValue varchar(500)
)
AS
BEGIN

/**
Takes the given @List string and splits it apart based on the given @SplitOn character.
A table is returned, one row per split item, with a column name "ListValue".
This function workes for fixed or variable lenght items.
Empty and null items will not be included in the results set.


Returns a table, one row per item in the list, with a column name "ListValue"

EXAMPLE:
----------
SELECT * FROM dbo.FN_ListToTable(',','1,12,123,1234,54321,6,A,*,|||,,,,B')

    returns:
        ListValue  
        -----------
        1
        12
        123
        1234
        54321
        6
        A
        *
        |||
        B

        (10 row(s) affected)

**/



----------------
--SINGLE QUERY-- --this will not return empty rows
----------------
INSERT INTO @ParsedList
        (ListValue)
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''



RETURN

END --Function FN_ListToTable

您现在可以使用这样的函数来查询任何列表:

DECLARE @List varchar(100)
SET @List='A,B,C'
declare @YourTable table (GroupID char(2),Member char(1))

insert into @YourTable values ('A1','A')
insert into @YourTable values ('A1','B')
insert into @YourTable values ('A1','C')
insert into @YourTable values ('A2','A')
insert into @YourTable values ('A2','B')
insert into @YourTable values ('A3','A')
insert into @YourTable values ('A3','D')
insert into @YourTable values ('A3','E')
insert into @YourTable values ('A5','A')
insert into @YourTable values ('A5','B')
insert into @YourTable values ('A5','C')
insert into @YourTable values ('A5','D')

SELECT t1.GroupID
    FROM @YourTable t1
        LEFT OUTER JOIN @YourTable t2 ON t1.GroupID=t2.GroupID AND t2.Member NOT IN (SELECT ListValue FROM dbo.FN_ListToTable(',',@List))
    WHERE t1.Member IN (SELECT ListValue FROM dbo.FN_ListToTable(',',@List))
        AND t2.GroupID IS NULL
    GROUP BY t1.GroupID
    HAVING COUNT(*) = (SELECT COUNT(*) FROM dbo.FN_ListToTable(',',@List)) 

输出:

GroupID
-------
A1

答案 4 :(得分:1)

从ALPHA中选择* 会员在哪里( 从ALPHA中选择会员 小组成员 有COUNT(*)= 3)

答案 5 :(得分:1)

试试这个:

SELECT GroupId
  FROM ALPHA
 GROUP BY GroupId
HAVING SUM(CASE WHEN Member='A' THEN 1.0
                WHEN Member='B' THEN 2.0
                WHEN Member='C' THEN 4.0
                ELSE 7.31415
          END) = 7.0

答案 6 :(得分:0)

我的建议是将分隔的字符串解析为临时表,然后尝试这样的事情。

create table #temp(member varchar(10))

create table #groups
(
groupID varchar(2),
member char(1)
)

--#temp holds the members from your delimited string.
--#groups holds your relationships.



select distinct groupID
from #groups
where 
 (select count(*) from #groups i, #temp t
  where i.member = t.member and i.groupID = #groups.groupID) = 
(select count(*) from #temp)