【问题标题】:SQL query like GROUP BY with OR condition带有 OR 条件的 SQL 查询,如 GROUP BY
【发布时间】:2011-09-10 23:00:16
【问题描述】:

我将尝试描述真实情况。在我们公司,我们有一个带桌子的预订系统,我们称之为Customers,其中电子邮件和电话联系人与每个收到的订单一起保存 - 这是我无法更改的系统的一部分。我正面临如何计算 unique 客户的问题。对于唯一客户,我指的是拥有相同电子邮件或相同电话号码的一群人。

示例 1:您可以从现实生活中想象已婚的汤姆和桑德拉。订购了 4 种产品的 Tom 在我们的预订系统中填写了 3 个不同的电子邮件地址和 2 个不同的电话号码,其中一个与 Sandra(作为家庭电话)共享,因此我可以假设它们以某种方式连接。除了这个共享电话号码之外,Sandra 还填写了她的私人电话号码,对于这两个订单,她只使用了一个电子邮件地址。对我来说,这意味着将以下行中的所有行计为一个唯一客户。所以事实上这个独特的客户可能会成长为整个家庭。

ID   E-mail              Phone          Comment
---- ------------------- -------------- ------------------------------
0    tom@email.com       +44 111 111    First row
1    tommy@email.com     +44 111 111    Same phone, different e-mail
2    thomas@email.com    +44 111 111    Same phone, different e-mail
3    thomas@email.com    +44 222 222    Same e-mail, different phone
4    sandra@email.com    +44 222 222    Same phone, different e-mail
5    sandra@email.com    +44 333 333    Same e-mail, different phone

正如ypercube 所说,我可能需要一个递归来计算所有这些独特的客户

示例 2:这是我想要做的示例。

是否可以在不使用递归的情况下获取 唯一客户数例如使用游标或其他东西还是需要递归?

ID   E-mail              Phone          Comment
---- ------------------- -------------- ------------------------------
0    linsey@email.com    +44 111 111    ─┐
1    louise@email.com    +44 111 111     ├─ 1. unique customer
2    louise@email.com    +44 222 222    ─┘
---- ------------------- -------------- ------------------------------
3    steven@email.com    +44 333 333    ─┐
4    steven@email.com    +44 444 444     ├─ 2. unique customer
5    sandra@email.com    +44 444 444    ─┘
---- ------------------- -------------- ------------------------------
6    george@email.com    +44 555 555    ─── 3. unique customer
---- ------------------- -------------- ------------------------------
7    xavier@email.com    +44 666 666    ─┐
8    xavier@email.com    +44 777 777     ├─ 4. unique customer
9    xavier@email.com    +44 888 888    ─┘
---- ------------------- -------------- ------------------------------
10   robert@email.com    +44 999 999    ─┐
11   miriam@email.com    +44 999 999     ├─ 5. unique customer
12   sherry@email.com    +44 999 999    ─┘
---- ------------------- -------------- ------------------------------
----------------------------------------------------------------------
Result                                  ∑ = 5 unique customers
----------------------------------------------------------------------

我已尝试使用 GROUP BY 进行查询,但我不知道如何按第一列或第二列对结果进行分组。我正在寻找让我们说类似的东西

SELECT COUNT(*) FROM Customers
GROUP BY Email OR Phone

再次感谢您的任何建议

附注 在完整改写之前,我非常感谢这个问题的答案。现在这里的答案可能与更新不对应,所以如果你打算这样做,请不要在这里投反对票(当然问题除外:)。我完全重写了这篇文章。

感谢并为我的错误开始感到抱歉。

【问题讨论】:

  • 尝试搜索OVERPARTITION BYwindow functions
  • 为什么 George 的计数是 3,为什么他的 Id 从 5 变成 7
  • 也许我的问题并不清楚,但我只寻找这种独特客户的独特数量。我会更新我的问题,非常抱歉参与者并感谢您的努力。
  • @Conrad Frix - 因为 George 和 Thomas 有相同的电话,而 Thomas 和 Thomas 有相同的名字(实际上是电子邮件)。所以这在现实生活中意味着乔治和托马斯是例如。已婚 :) 并且他们有共同的电话,所以我将他们视为同名或相同电话的唯一客户。
  • @daemon_x:不要删除。您可以在问题末尾添加“更新”,标记您可能需要递归查询。

标签: sql-server sql-server-2005 tsql recursion


【解决方案1】:

这是使用递归 CTE 的完整解决方案。

;WITH Nodes AS
(
    SELECT DENSE_RANK() OVER (ORDER BY Part, PartRank) SetId
        , [ID]
    FROM
    (
        SELECT [ID], 1 Part, DENSE_RANK() OVER (ORDER BY [E-mail]) PartRank
        FROM dbo.Customer
        UNION ALL
        SELECT [ID], 2, DENSE_RANK() OVER (ORDER BY Phone) PartRank
        FROM dbo.Customer
    ) A
),
Links AS
(
    SELECT DISTINCT A.Id, B.Id LinkedId
    FROM Nodes A
    JOIN Nodes B ON B.SetId = A.SetId AND B.Id < A.Id
),
Routes AS
(
    SELECT DISTINCT Id, Id LinkedId
    FROM dbo.Customer

    UNION ALL

    SELECT DISTINCT Id, LinkedId
    FROM Links

    UNION ALL

    SELECT A.Id, B.LinkedId
    FROM Links A
    JOIN Routes B ON B.Id = A.LinkedId AND B.LinkedId < A.Id
),
TransitiveClosure AS
(
    SELECT Id, Id LinkedId
    FROM Links

    UNION

    SELECT LinkedId Id, LinkedId
    FROM Links

    UNION

    SELECT Id, LinkedId
    FROM Routes
),
UniqueCustomers AS
(
    SELECT Id, MIN(LinkedId) UniqueCustomerId
    FROM TransitiveClosure
    GROUP BY Id
)
SELECT A.Id, A.[E-mail], A.Phone, B.UniqueCustomerId
FROM dbo.Customer A
JOIN UniqueCustomers B ON B.Id = A.Id

【讨论】:

  • +1 并接受;谢谢;虽然这很好用,但它也非常慢。现在我正在考虑在某个单独的列中对插入触发器进行分组(这将提高速度,因为我将使用 INT 作为它的数据类型)。无论如何祝贺你即将到来的 2k :)
  • 感谢@daemon_x。我看到你也达到了 2k :-)。你是对的,它很慢。您可以将其重写为多行表函数。使用表变量。用 WHILE 循环替换递归部分,这会在每次迭代中删除多余的重复项。这应该会减少临时行的爆炸式增长,并有望加快速度。
【解决方案2】:

查找只有相同电话的组:

SELECT
    ID
  , Name
  , Phone
  , DENSE_RANK() OVER (ORDER BY Phone) AS GroupPhone
FROM 
    MyTable
ORDER BY
    GroupPhone
  , ID

查找只有相同名称的组:

SELECT
    ID
  , Name
  , Phone
  , DENSE_RANK() OVER (ORDER BY Name) AS GroupName
FROM 
    MyTable
ORDER BY
    GroupName
  , ID

现在,对于您描述的(复杂)查询,假设我们有一个这样的表:

ID   Name          Phone
---- ------------- -------------
0    Kate          +44 333 333
1    Sandra        +44 000 000
2    Thomas        +44 222 222
3    Robert        +44 000 000
4    Thomas        +44 444 444
5    George        +44 222 222
6    Kate          +44 000 000
7    Robert        +44 444 444
--------------------------------

所有这些都应该在一个组中吗?因为他们都与其他人共享姓名或电话,从而形成了相关人员的“链”:

0-6   same name
6-1-3 same phone
3-7   same name
7-4   same-phone
4-2   same name
2-5   bame phone

【讨论】:

  • 是的,完全正确;您提到的表可能会返回 count = 1,正如您所说,我想构建一个 chain。在现实生活中,名称是电子邮件,对于世界上任何人来说都是独一无二的,但您可以有 2 个具有相同电子邮件的电话号码或具有不同电子邮件的相同电话。我觉得这是一个非常复杂的查询。
  • 这很复杂,是的。它需要递归,但我相信有人会找到一种方法来编写它。
【解决方案3】:

对于示例中的数据集,您可以编写如下内容:

;WITH Temp AS (
    SELECT Name, Phone,
        DENSE_RANK() OVER (ORDER BY Name) AS NameGroup,
        DENSE_RANK() OVER (ORDER BY Phone) AS PhoneGroup
    FROM MyTable)
SELECT MAX(Phone), MAX(Name), COUNT(*)
FROM Temp
GROUP BY NameGroup, PhoneGroup

【讨论】:

    【解决方案4】:

    我不知道这是否是最好的解决方案,但这里是:

    SELECT
      MyTable.ID, MyTable.Name, MyTable.Phone,
      CASE WHEN N.No = 1 AND P.No = 1 THEN 1
           WHEN N.No = 1 AND P.No > 1 THEN 2
           WHEN N.No > 1 OR P.No > 1  THEN 3
      END as GroupRes
    FROM
      MyTable 
      JOIN (SELECT Name, count(Name) No FROM MyTable GROUP BY Name) N on MyTable.Name = N.Name
      JOIN (SELECT Phone, count(Phone) No FROM MyTable GROUP BY Phone) P on MyTable.Phone = P.Phone
    

    问题在于,这里有一些在 varchars 上进行的连接,最终可能会增加执行时间。

    【讨论】:

    • ID: 7, Name: George 的计数为 2,但这可能是正确的。您应该注意,您在内联视图中缺少Group by,添加Order by GroupRes 可能不会有什么坏处
    • @Conrad Frix:Ups,忘了放 GROUP BY。谢谢
    【解决方案5】:

    这是我的解决方案:

    SELECT p.LastName, P.FirstName, P.HomePhone,
    CASE 
        WHEN ph.PhoneCount=1 THEN       
            CASE 
                WHEN n.NameCount=1 THEN 'unique name and phone'
                ELSE 'common name'
            END
    
        ELSE        
            CASE 
                WHEN n.NameCount=1 THEN 'common phone'
                ELSE 'common phone and name'        
            END             
    END
    FROM Contacts p
    INNER JOIN 
    (SELECT HomePhone, count(LastName) as PhoneCount
    FROM Contacts
    GROUP BY HomePhone) ph ON ph.HomePhone = p.HomePhone
    
    INNER JOIN 
    (SELECT FirstName, count(LastName) as NameCount
    FROM Contacts
    GROUP BY FirstName) n ON n.FirstName = p.FirstName
    
    
    LastN       FirstN  Phone       Comment
    Hoover      Brenda  8138282334  unique name and phone
    Washington  Brian   9044563211  common name
    Roosevelt   Brian   7737653279  common name
    Reagan      Charles 7734567869  unique name and phone
    

    【讨论】:

      猜你喜欢
      • 2021-12-27
      • 2021-02-04
      • 1970-01-01
      • 2014-04-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-02
      相关资源
      最近更新 更多