如何在sql中使用窗口函数来带来不同的值?

Posted

技术标签:

【中文标题】如何在sql中使用窗口函数来带来不同的值?【英文标题】:How to use window functions in sql to bring distinct values? 【发布时间】:2014-04-06 07:55:28 【问题描述】:

我有这个查询要带上公司名称及其前 5 个联系人姓名和前 5 个电话号码。

当我只带联系人或只带电话时,它工作正常,但当我尝试带两个时,返回的所有值都不是不同的(例如,每家公司有不止一行)。 我认为它与分区有关,但我不知道它是什么。

有人可以帮我吗:

    修复此查询。 了解修复的含义。

查询:

select
     p.company_name,
     p.Contact_1, p.Contact_2, p.Contact_3, p.Contact_4, p.Contact_5,
     p.Phone_1, p.Phone_2, p.Phone_3, p.Phone_4, p.Phone_5
from
(
 select contact.first_name + ' ' + contact.last_name as contact_name, 
     phone.display_phone,
     company.company_name,
'Contact_'+
  cast(row_number() over(partition by relation.company_id 
                         order by contact.first_name, contact.last_name) as varchar(50)) row,
'Phone_'+
  cast(row_number() over(partition by phone.contact_id 
                         order by phone.display_phone) as varchar(50)) row2
 from contacts company
left join contact_company_relation_additional_information relation
    on company.id = relation.company_id and relation.ContactCompanyRelation_IsActive = 1
left join contacts contact
    on relation.contact_id = contact.id and contact.is_company = 0 and contact.is_active = 1
left join contact_phones phone on company.id = phone.contact_id  and phone.is_active = 1
 where company.is_company = 1 and company.is_active = 1
) d
pivot
(
  max(contact_name)
  for row in (Contact_1, Contact_2, Contact_3, Contact_4, Contact_5)
) x
pivot
(
  max(display_phone)
  for row2 in (Phone_1, Phone_2, Phone_3, Phone_4, Phone_5)
) p

这里是重复行的 sql fiddle 链接:Contacts and Phones

以下是仅包含联系人或仅电话的查询链接,每个公司都有一行:

Contacts only Phones only

【问题讨论】:

【参考方案1】:

为了获得您想要的结果,我建议采用稍微不同的方法。由于您想在两列数据ContactsPhone 上进行旋转,因此我首先将这些列取消旋转为多行,然后应用 PIVOT - 我认为这样做更容易,然后尝试应用 PIVOT 两次。

我看到了一些我会在您当前的查询中解决的问题。连接到所有表的查询的主要部分有几处需要更改。首先,我只会创建一个Row 列:

row_number() over(partition by relation.company_id 
                 order by contact.first_name, contact.last_name) row

此列将由contact_company_relation 表中的company_Id 分区。这个新的行号将用于ContactPhone 数字列。

其次,您当前返回Phone 号码的加入似乎不正确。您当前的代码使用主要的company id,但您想加入每个联系人。更改您的代码:

left join contact_phones phone 
  on company.id = phone.contact_id

到:

left join contact_phones phone 
  on contact.id = phone.contact_id  

这将使您的子查询:

select 
  contact.first_name + ' ' + contact.last_name as contact_name, 
  phone.display_phone,
  company.company_name,
  row_number() over(partition by relation.company_id 
                    order by contact.first_name, contact.last_name) row
from contacts company
left join contact_company_relation relation
  on company.id = relation.company_id
left join contacts contact
  on relation.contact_id = contact.id 
  and contact.is_company = 0 
left join contact_phones phone 
  on contact.id = phone.contact_id  -- change to join on contact
where company.is_company = 1;

见SQL Fiddle with Demo。数据现在看起来像:

| CONTACT_NAME | DISPLAY_PHONE | COMPANY_NAME | ROW |
|--------------|---------------|--------------|-----|
|   Ben Gurion |       2222222 |      Analist |   1 |
|   Ofer Jerus |       3333333 |      Analist |   2 |
|   Ori Reshef |       1111111 |      Analist |   3 |

一旦您获得了带有行号的数据,您就可以将display_phonecompany_name 还原为多行而不是列。您没有指定您使用的 SQL Server 版本,但您可以使用 UNPIVOT 或 CROSS APPLY 来执行此操作。当您取消透视数据时,您将使用 Row 值来关联每个 contactphone 对 - 这确保每个联系人仍与正确的电话号码相关联。代码类似于:

;with cte as
(
  -- query from above here
)
select compnay_name, col, value
from
(
  select company_name, 
    col = col+'_'+cast(row as varchar(50)), 
    value
  from cte
  cross apply
  (
    select 'Contact', Contact_name union all
    select 'Phone', display_phone
  ) c (col, value)
) src;

见SQL Fiddle with Demo。数据现在将采用每个公司名称、联系人和电话具有多行的格式:

| COMPANY_NAME |       COL |      VALUE |
|--------------|-----------|------------|
|      Analist | Contact_1 | Ben Gurion |
|      Analist |   Phone_1 |    2222222 |
|      Analist | Contact_2 | Ofer Jerus |
|      Analist |   Phone_2 |    3333333 |
|      Analist | Contact_3 | Ori Reshef |
|      Analist |   Phone_3 |    1111111 |

最后一步是添加 PIVOT 函数来制作最终代码:

;with cte as
(
  select 
    contact.first_name + ' ' + contact.last_name as contact_name, 
    phone.display_phone,
    company.company_name,
    row_number() over(partition by relation.company_id 
                      order by contact.first_name, contact.last_name) row
  from contacts company
  left join contact_company_relation relation
    on company.id = relation.company_id
  left join contacts contact
    on relation.contact_id = contact.id 
    and contact.is_company = 0 
  left join contact_phones phone 
    on contact.id = phone.contact_id  -- change to join on contact
  where company.is_company = 1
)
select company_name, 
  contact_1, contact_2, contact_3, contact_4, contact_5,
  phone_1, phone_2, phone_3, phone_4, phone_5
from
(
  select company_name, 
    col = col+'_'+cast(row as varchar(50)), 
    value
  from cte
  cross apply
  (
    select 'Contact', Contact_name union all
    select 'Phone', display_phone
  ) c (col, value)
) src
pivot
(
  max(value)
  for col in (contact_1, contact_2, contact_3, contact_4, contact_5,
              phone_1, phone_2, phone_3, phone_4, phone_5)
) p;

见SQL Fiddle with Demo。最终结果如下:

| COMPANY_NAME |  CONTACT_1 |  CONTACT_2 |  CONTACT_3 | CONTACT_4 | CONTACT_5 | PHONE_1 | PHONE_2 | PHONE_3 | PHONE_4 | PHONE_5 |
|--------------|------------|------------|------------|-----------|-----------|---------|---------|---------|---------|---------|
|      Analist | Ben Gurion | Ofer Jerus | Ori Reshef |    (null) |    (null) | 2222222 | 3333333 | 1111111 |  (null) |  (null) |
|      Bar Net | Dima Brods | Maya Leshe | Yossi Farc |    (null) |    (null) | 7777777 | 4444444 | 6666666 |  (null) |  (null) |

【讨论】:

以上是关于如何在sql中使用窗口函数来带来不同的值?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 SQL Server 中使用带有框架的窗口函数执行 COUNT(DISTINCT)

在 SQL 中使用窗口函数运行“匹配”总数

具有 CTE 的 T-SQL 窗口函数,使用先前计算的值

如何使用 SQL Server 2012 窗口函数来完成我的查询?

C# 主窗口调用子窗口的值传递?

如何使用窗口函数检查特定范围值是不是填充在 SQL 中?