SQL JOIN在第一次结果后省略其他列

时间:2014-07-26 14:25:42

标签: sql postgresql window-functions

这是我需要的结果,简化:

select name, phonenumber
from contacttmp
left outer join phonetmp on (contacttmp.id = phonetmp.contact_id);

 name  | phonenumber
-------+--------------
 bob   | 111-222-3333
 bob   | 111-222-4444
 bob   | 111-222-5555
 frank | 111-222-6666
 joe   | 111-222-7777

但查询显示名称,我试图在第一个结果后省略名称:

 name  | phonenumber
-------+--------------
 bob   | 111-222-3333
       | 111-222-4444
       | 111-222-5555
 frank | 111-222-6666
 joe   | 111-222-7777

以下是我制作示例表和数据的方法:

create table contacttmp (id serial, name text);
create table phonetmp (phoneNumber text, contact_id integer);

select * from contacttmp;
 id | name
----+-------
  1 | bob
  2 | frank
  3 | joe

select * from phonetmp ;
 phonenumber  | contact_id
--------------+------------
 111-222-3333 |          1
 111-222-4444 |          1
 111-222-5555 |          1
 111-222-6666 |          2
 111-222-7777 |          3

问题的老部分

我正在使用PHP中的联系人程序,并且要求显示结果,但如果同一记录有多个结果,则在显示第一条记录后省略其他字段。

postgres tutorial join示例中,我正在使用左外连接执行此类操作:

SELECT *
FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name);

    city      | temp_lo | temp_hi | prcp |    date    |     name      | location
--------------+---------+---------+------+------------+---------------+-----------
Hayward       |      37 |      54 |      | 1994-11-29 |               |
San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
San Francisco |      43 |      57 |    0 | 1994-11-29 | San Francisco | (-194,53)

我无法弄清楚如何或者是否有可能改变上述查询,以便在第一个结果之后不显示其他字段。

例如,如果我们添加子句“WHERE location ='( - 194,53)'”我们不希望第二个(和第三个,如果有的话)结果显示除location之外的列,所以查询(加上额外的东西),结果如下:

SELECT *
FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name)
WHERE location = '(-194,53)';

    city      | temp_lo | temp_hi | prcp |    date    |     name      | location
--------------+---------+---------+------+------------+---------------+-----------
San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
              |         |         |      |            |               | (-194,53)

是否可以使用某种JOIN或排除或其他查询?或者在获得所有结果后我是否必须在PHP中删除这些字段(宁愿不这样做)。

为避免混淆,我需要获得如下结果集:

    city      | temp_lo | temp_hi | prcp |    date    |     name      | location
--------------+---------+---------+------+------------+---------------+-----------
San Francisco |      46 |      50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
              |         |         |      |            |               | (-19,5)
              |         |         |      |            |               | (-94,3)
Philadelphia  |    55   |   60    | 0.1  | 1995-12-12 | Philadelphia  | (-1,1)
              |         |         |      |            |               | (-77,55)
              |         |         |      |            |               | (-3,33)

如果具有不同位置的同一记录(城市)的任何其他结果只显示不同的位置。

3 个答案:

答案 0 :(得分:3)

您可以在SQL中执行此类逻辑,但不建议这样做。 SQL查询的结果集采用表格格式。表格表示无序集合,通常所有列都具有相同的含义。

因此,拥有一个依赖于“前一行”值的结果集不是使用SQL的正确方法。虽然你可以在Postgres中得到这个结果,但我不推荐它。通常,这种格式化在应用程序端完成。

答案 1 :(得分:2)

如果您想避免重复相同的信息,可以使用一个窗口函数来告诉您该行中该行的位置(PARTITION用于此目的,而不是{{1}中的一个组感觉),然后隐藏您不想重复的列的文本,如果该组中的位置大于1。

GROUP BY

这应该给你这个:

WITH joined_results AS (
   SELECT
      w.city, c.location, w.temp_lo, w.temp_hi, w.prcp, w.date,
      ROW_NUMBER() OVER (PARTITION BY w.city, c.location ORDER BY date) AS pos
   FROM weather w
       LEFT OUTER JOIN cities c ON (w.city = c.name)
   ORDER BY w.city, c.location
)
SELECT
   CASE WHEN pos > 1 THEN '' ELSE city END,
   CASE WHEN pos > 1 THEN '' ELSE location END,
   temp_lo, temp_hi, prcp, date
FROM joined_results;

要了解 city | location | temp_lo | temp_hi | prcp | date ---------------+-----------+---------+---------+------+------------ Hayward | | 37 | 54 | | 1994-11-29 San Francisco | (-194,53) | 46 | 50 | 0.25 | 1994-11-27 | | 43 | 57 | 0 | 1994-11-29 的作用,可能值得查看ROW_NUMBER() OVER (PARTITION BY w.city, c.location ORDER BY date) AS pos的内容:

SELECT * FROM joined_results

之后,只需使用 city | location | temp_lo | temp_hi | prcp | date | pos ---------------+-----------+---------+---------+------+------------+----- Hayward | | 37 | 54 | | 1994-11-29 | 1 San Francisco | (-194,53) | 46 | 50 | 0.25 | 1994-11-27 | 1 San Francisco | (-194,53) | 43 | 57 | 0 | 1994-11-29 | 2 替换您不想要的空格。

(这就是说,我通常更喜欢在表示层而不是在查询中。)

答案 2 :(得分:1)

考虑下面小提琴中略有修改的测试用例。

简单案例

对于处理每列单列的简单情况,与上一个行和窗口函数lag()进行比较可以完成工作:

SELECT CASE WHEN lag(c.contact) OVER (ORDER BY c.contact, p.phone_nr)
               = c.contact THEN NULL ELSE c.contact END
     , p.phone_nr
FROM   contact c
LEFT   JOIN phone p USING (contact_id);

您可以为 n列重复此操作,但这很乏味

对于许多列

SELECT c.*, p.phone_nr
FROM  (
   SELECT *
        , row_number() OVER (PARTITION BY contact_id ORDER BY phone_nr) AS rn
   FROM   phone
   ) p
LEFT  JOIN contact c ON c.contact_id = p.contact_id AND p.rn = 1;

类似于"反向LEFT JOIN"。这是假设引用完整性(contact中没有丢失的行。此外,phone中没有任何条目的联系人不在结果中。如果需要,可以轻松添加。

SQL Fiddle.

除此之外,您在第一个示例中的查询显示出一个新手错误。

SELECT * FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name)
WHERE location = '(-194,53)';

在右表中没有LEFT JOINWHERE子句的组合。没有意义。详细说明:

除了测试存在...