在每个GROUP BY组中选择第一行?


1320

顾名思义,我想选择以分组的每一行的第一行GROUP BY

具体来说,如果我有一个purchases看起来像这样的表:

SELECT * FROM purchases;

我的输出:

id | 客户| 总
--- + ---------- + ------
 1 | 乔| 5
 2 | 莎莉| 3
 3 | 乔| 2
 4 | 莎莉| 1个

我想查询每个人id最大的购买金额(totalcustomer。像这样:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY total DESC;

预期产量:

FIRST(id)| 客户| 第一(总计)
---------- + ---------- + -------------
        1 | 乔| 5
        2 | 莎莉| 3

由于您只在寻找最大的一个,为什么不查询MAX(total)
phil294

4
@ phil294查询max(total)不会将该总数与发生该总数的行的'id'值相关联。
gwideman

Answers:


1113

在Oracle 9.2+(不是最初所说的8i +),SQL Server 2005 +,PostgreSQL 8.4 +,DB2,Firebird 3.0 +,Teradata,Sybase,Vertica上:

WITH summary AS (
    SELECT p.id, 
           p.customer, 
           p.total, 
           ROW_NUMBER() OVER(PARTITION BY p.customer 
                                 ORDER BY p.total DESC) AS rk
      FROM PURCHASES p)
SELECT s.*
  FROM summary s
 WHERE s.rk = 1

任何数据库支持:

但是您需要添加逻辑来打破平局:

  SELECT MIN(x.id),  -- change to MAX if you want the highest
         x.customer, 
         x.total
    FROM PURCHASES x
    JOIN (SELECT p.customer,
                 MAX(total) AS max_total
            FROM PURCHASES p
        GROUP BY p.customer) y ON y.customer = x.customer
                              AND y.max_total = x.total
GROUP BY x.customer, x.total

2
Informix 12.x还支持窗口功能(尽管CTE需要转换为派生表)。Firebird 3.0也将支持Window功能
a_horse_with_no_name 2014年

37
ROW_NUMBER() OVER(PARTITION BY [...])加上其他一些优化措施,使我的查询时间从30秒缩短到了几毫秒。谢谢!(PostgreSQL 9.2)
山姆

8
如果有多个购买,total每个客户的购买金额最高,则第一个查询将返回任意赢家(取决于实现细节;id每次执行都会改变!)。通常(不总是)你想一个每个客户行,由附加标准像“一个具有最小定义id”。要修复,请附加id到的ORDER BY列表row_number()。然后,您将获得与第二个查询相同的结果,在这种情况下这效率非常低。另外,您还需要为每个其他列添加另一个子查询。
Erwin Brandstetter 2014年

2
Google的BigQuery还支持第一个查询的ROW_NUMBER()命令。对我们而言就像魅力一样
-Praxiteles

2
请注意,带有window函数的第一个版本从SQLite 3.25.0版开始运行:sqlite.org/windowfunctions.html#history
brianz

1146

PostgreSQL中,这通常更简单,更快(下面将进行更多性能优化):

SELECT DISTINCT ON (customer)
       id, customer, total
FROM   purchases
ORDER  BY customer, total DESC, id;

或更短(如果不清楚),输出列的序号为:

SELECT DISTINCT ON (2)
       id, customer, total
FROM   purchases
ORDER  BY 2, 3 DESC, 1;

如果total可以为NULL(无论哪种方式都没有问题,但是您需要匹配现有索引):

...
ORDER  BY customer, total DESC NULLS LAST, id;

要点

  • DISTINCT ON是标准的PostgreSQL扩展(仅DISTINCT在整个SELECT列表中定义)。

  • DISTINCT ON子句中列出任意数量的表达式,合并的行值定义重复项。手册:

    显然,如果两行至少有一个列值不同,则认为它们是不同的。在此比较中,将空值视为相等。

    大胆强调我的。

  • DISTINCT ON可以结合使用ORDER BY。中的前导表达式ORDER BY必须在中的表达式集中DISTINCT ON,但是您可以在这些表达式之间自由地重新排列顺序。例。您可以添加其他表达式以ORDER BY从每个对等组中选择特定的行。或者,如手册所述

    DISTINCT ON表达式(一个或多个)必须最左边的匹配ORDER BY 表达式(一个或多个)。该ORDER BY子句通常将包含其他表达式,这些表达式确定每个DISTINCT ON组中行的期望优先级。

    id最后添加了打破关系的项目:
    id从各组中共享最小的组中选择最小的行total。”

    要以与确定每个组第一个的排序顺序不同的方式对结果进行排序,可以将上面的查询嵌套在另一个外部查询中ORDER BY例。

  • 如果total可以为NULL,则您很可能希望具有最大非空值的行。加NULLS LAST样演示。看到:

  • SELECT列表不受表达式的约束DISTINCT ONORDER BY以任何方式。(在上面的简单情况下不需要):

    • 不必DISTINCT ON或中包含任何表达式ORDER BY

    • 可以SELECT列表中包括任何其他表达式。这有助于用子查询和聚合/窗口函数替换更复杂的查询。

  • 我使用Postgres 8.3 – 12版进行了测试。但是至少从7.1版开始,该功能就存在了,因此基本上总是如此。

指数

上面查询的理想索引是一个多列索引,它以匹配顺序和匹配的排序顺序跨越所有三列:

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

可能太专业了。但是,如果特定查询的读取性能至关重要,请使用它。如果您DESC NULLS LAST在查询中,请在索引中使用相同的索引,以便排序顺序匹配并且索引适用。

效果/性能优化

在为每个查询创建量身定制的索引之前,请权衡成本和收益。上述指标的潜力在很大程度上取决于数据分布

使用索引是因为它提供了预排序的数据。在Postgres 9.2或更高版本中,如果索引小于基础表,则查询也可以从仅索引扫描中受益。但是,必须完整扫描索引。

基准测试

我这里有一个简单的基准,现在已经过时了。在这个单独的答案中,我用详细的基准代替了它。


28
对于大多数数据库大小来说,这是一个很好的答案,但是我想指出的是,随着您接近〜数百万行,DISTINCT ON速度变得非常慢。该实现始终对整个表进行排序,并在整个表中进行扫描以查找重复项,而忽略所有索引(即使您已创建了所需的多列索引)。有关可能的解决方案,请参见说明 extendedext.com/2009/05/03/postgresql-optimizing-distinct。
Meekohi 2014年

14
用序号“使代码更短”是一个可怕的想法。将列名保留为可读性如何?
KOTJMF

13
@KOTJMF:我建议您按照个人喜好去。我演示了两种可供选择的教育方式。语法简写对于SELECT列表中的长表达式可能很有用。
Erwin Brandstetter 2015年

1
@jangorecki:最初的基准测试是从2011年开始的,我已经没有设置了。但是现在是时候运行pg 9.4和pg 9.5的测试了。查看添加的答案中的详细信息。。您可能会在下面的安装结果中添加评论?
Erwin Brandstetter,2016年

2
@PirateApp:不是从我的头上。DISTINCT ON只为获得良好的一个每同龄组一行。
Erwin Brandstetter

134

基准测试

使用Postgres 9.49.5以及200k行purchases10k分别customer_id每位客户平均20行)的中途现实表测试最有趣的候选者。

对于Postgres 9.5,我对有效的86446个不同的客户进行了第二次测试。参见下文(每个客户平均2.3行)。

设定

主桌

CREATE TABLE purchases (
  id          serial
, customer_id int  -- REFERENCES customer
, total       int  -- could be amount of money in Cent
, some_column text -- to make the row bigger, more realistic
);

我使用serial(在下面添加了PK约束)和一个整数,customer_id因为这是更典型的设置。还添加了some_column以弥补通常更多的列。

虚拟数据,PK,索引-典型表也有一些死元组:

INSERT INTO purchases (customer_id, total, some_column)    -- insert 200k rows
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,200000) g;

ALTER TABLE purchases ADD CONSTRAINT purchases_id_pkey PRIMARY KEY (id);

DELETE FROM purchases WHERE random() > 0.9; -- some dead rows

INSERT INTO purchases (customer_id, total, some_column)
SELECT (random() * 10000)::int             AS customer_id  -- 10k customers
     , (random() * random() * 100000)::int AS total     
     , 'note: ' || repeat('x', (random()^2 * random() * random() * 500)::int)
FROM   generate_series(1,20000) g;  -- add 20k to make it ~ 200k

CREATE INDEX purchases_3c_idx ON purchases (customer_id, total DESC, id);

VACUUM ANALYZE purchases;

customer 表-用于高级查询

CREATE TABLE customer AS
SELECT customer_id, 'customer_' || customer_id AS customer
FROM   purchases
GROUP  BY 1
ORDER  BY 1;

ALTER TABLE customer ADD CONSTRAINT customer_customer_id_pkey PRIMARY KEY (customer_id);

VACUUM ANALYZE customer;

在针对9.5的第二次测试中,我使用了相同的设置,但通过random() * 100000生成customer_id仅获得了几行customer_id

表的对象大小 purchases

使用此查询生成。

               what                | bytes/ct | bytes_pretty | bytes_per_row
-----------------------------------+----------+--------------+---------------
 core_relation_size                | 20496384 | 20 MB        |           102
 visibility_map                    |        0 | 0 bytes      |             0
 free_space_map                    |    24576 | 24 kB        |             0
 table_size_incl_toast             | 20529152 | 20 MB        |           102
 indexes_size                      | 10977280 | 10 MB        |            54
 total_size_incl_toast_and_indexes | 31506432 | 30 MB        |           157
 live_rows_in_text_representation  | 13729802 | 13 MB        |            68
 ------------------------------    |          |              |
 row_count                         |   200045 |              |
 live_tuples                       |   200045 |              |
 dead_tuples                       |    19955 |              |

查询

1. row_number()在CTE中,(请参阅其他答案

WITH cte AS (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   )
SELECT id, customer_id, total
FROM   cte
WHERE  rn = 1;

2. row_number()在子查询中(我的优化)

SELECT id, customer_id, total
FROM   (
   SELECT id, customer_id, total
        , row_number() OVER(PARTITION BY customer_id ORDER BY total DESC) AS rn
   FROM   purchases
   ) sub
WHERE  rn = 1;

3. DISTINCT ON请参阅其他答案

SELECT DISTINCT ON (customer_id)
       id, customer_id, total
FROM   purchases
ORDER  BY customer_id, total DESC, id;

4.带子LATERAL查询的rCTE (请参阅此处

WITH RECURSIVE cte AS (
   (  -- parentheses required
   SELECT id, customer_id, total
   FROM   purchases
   ORDER  BY customer_id, total DESC
   LIMIT  1
   )
   UNION ALL
   SELECT u.*
   FROM   cte c
   ,      LATERAL (
      SELECT id, customer_id, total
      FROM   purchases
      WHERE  customer_id > c.customer_id  -- lateral reference
      ORDER  BY customer_id, total DESC
      LIMIT  1
      ) u
   )
SELECT id, customer_id, total
FROM   cte
ORDER  BY customer_id;

5.带customerLATERAL见这里

SELECT l.*
FROM   customer c
,      LATERAL (
   SELECT id, customer_id, total
   FROM   purchases
   WHERE  customer_id = c.customer_id  -- lateral reference
   ORDER  BY total DESC
   LIMIT  1
   ) l;

6. array_agg()ORDER BY请参阅其他答案

SELECT (array_agg(id ORDER BY total DESC))[1] AS id
     , customer_id
     , max(total) AS total
FROM   purchases
GROUP  BY customer_id;

结果

以上查询的执行时间EXPLAIN ANALYZE(包括所有选项均关闭),最好执行5次

所有查询都使用“ 仅索引扫描purchases2_3c_idx(在其他步骤中)。其中一些只是针对较小的索引大小,而其他一些则更有效。

A. Postgres 9.4,具有20万行,每行约20个 customer_id

1. 273.274 ms  
2. 194.572 ms  
3. 111.067 ms  
4.  92.922 ms  
5.  37.679 ms  -- winner
6. 189.495 ms

B.与Postgres 9.5相同

1. 288.006 ms
2. 223.032 ms  
3. 107.074 ms  
4.  78.032 ms  
5.  33.944 ms  -- winner
6. 211.540 ms  

C.与B.相同,但每个〜2.3行 customer_id

1. 381.573 ms
2. 311.976 ms
3. 124.074 ms  -- winner
4. 710.631 ms
5. 311.976 ms
6. 421.679 ms

相关基准

这是在Postgres 11.5(截至2019年9月)上通过1000万行和6万个唯一“客户”进行的“ ogr”测试得出的新结果。结果仍然符合我们到目前为止所看到的:

2011年的原始(过时)基准

我使用PostgreSQL 9.1在实际的65579行表和涉及的三列中的每一列上的单列btree索引上进行了三个测试,并以5次运行的最佳执行时间进行了测试。
@OMGPonies的第一个查询(A)与上述DISTINCT ON解决方案B)进行比较:

  1. 选择整个表,在这种情况下将导致5958行。

    A: 567.218 ms
    B: 386.673 ms
  2. 使用条件WHERE customer BETWEEN x AND y导致1000行。

    A: 249.136 ms
    B:  55.111 ms
  3. 使用选择一个客户WHERE customer = x

    A:   0.143 ms
    B:   0.072 ms

使用另一个答案中描述的索引重复相同的测试

CREATE INDEX purchases_3c_idx ON purchases (customer, total DESC, id);

1A: 277.953 ms  
1B: 193.547 ms

2A: 249.796 ms -- special index not used  
2B:  28.679 ms

3A:   0.120 ms  
3B:   0.048 ms

5
感谢您提供出色的基准测试。我想知道查询具有时间戳而不是时间戳的事件数据是否将从新的BRIN索引中受益。这样可以潜在地加快时间查询的速度。
jangorecki

3
@jangorecki:任何具有物理排序数据的巨大表都可以从BRIN索引中受益。
Erwin Brandstetter,2016年

@ 2. row_number()和ErnBrandstetter在和5. customer table with LATERAL示例中,如何确保ID最小?
Artem Novikov

@ArtemNovikov:没事。目的是customer_id 按照最高的行进行检索total。在问题的测试数据id中,所选行中的恰好也是的最小,这是一个误导性的巧合customer_id
Erwin Brandstetter,2016年

1
@ArtemNovikov:允许仅索引扫描。
Erwin Brandstetter,2016年

55

这很常见 问题,它已经有经过测试和高度优化的解决方案。就我个人而言,我更喜欢Bill Karwin左联接解决方案带有许多其他解决方案原始帖子)。

注意,在大多数官方资料之一MySQL手册中,可以惊奇地找到许多针对这个常见问题的解决方案!请参阅常见查询的示例::持有特定列的按组最大值的行


22
MySQL手册如何以任何形式“官方”解决Postgres / SQLite(更不用说SQL)问题了?同样,要明确的DISTINCT ON是,该版本比使用带有self LEFT JOIN或semi-anti-join的替代版本更短,更简单并且在Postgres中通常表现更好NOT EXISTS。它还经过“充分测试”。
Erwin Brandstetter,

3
除了Erwin所写的内容之外,我想说的是,使用窗口函数(当今常见的SQL功能)几乎总是比使用带有派生表的
联接

6
很好的参考。我不知道这被称为最大的每组问题。谢谢。
David Mann

这个问题确实作为最大的每组n但第一 ñ。
reinierpost 2015年

1
在两个有序字段的情况下,我尝试过“ Bill Karwin的左连接解决方​​案”会导致性能不佳。请参阅下面的评论stackoverflow.com/a/8749095/684229
Johnny Wong

30

在Postgres中,您可以这样使用array_agg

SELECT  customer,
        (array_agg(id ORDER BY total DESC))[1],
        max(total)
FROM purchases
GROUP BY customer

这将为您id提供每个客户最大的购买量。

注意事项:

  • array_agg是一个汇总函数,因此可以使用GROUP BY
  • array_agg使您可以指定仅限于自身的排序范围,因此它不会限制整个查询的结构。如果需要执行一些与默认值不同的操作,则还提供了有关如何对NULL进行排序的语法。
  • 构建数组后,我们将获取第一个元素。(Postgres数组是1索引的,而不是0索引的)。
  • 您可以array_agg类似的方式将其用于第三个输出列,但max(total)更为简单。
  • 与不同DISTINCT ON,使用array_agg可以保留您的GROUP BY,以防由于其他原因而需要。

14

由于存在SubQ,该解决方案不是十分有效,正如Erwin指出的那样

select * from purchases p1 where total in
(select max(total) from purchases where p1.customer=customer) order by total desc;

谢谢,是的,您同意,subq和外部查询之间的连接实际上需要更长的时间。这里的“输入”将不是问题,因为subq将仅产生一行。顺便说一句,您指的是什么语法错误?
user2407394 2013年

喔..用来“Teradata的” ..编辑now..however打破关系在这里不需要,因为它需要为每个客户找到最高总..
user2407394

您知道,如果出现平局,您会为单个客户获得多行?是否理想取决于确切的要求。通常不是。对于眼前的问题,标题非常清楚。
Erwin Brandstetter

从这个问题尚不清楚,如果同一位客户购买了2个不同ID的Max = Max,我认为我们应该同时显示两者。
user2407394

10

我使用这种方式(仅限于PostgreSQL):https : //wiki.postgresql.org/wiki/First/last_%28aggregate%29

-- Create a function that always returns the first non-NULL item
CREATE OR REPLACE FUNCTION public.first_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $1;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.first (
        sfunc    = public.first_agg,
        basetype = anyelement,
        stype    = anyelement
);

-- Create a function that always returns the last non-NULL item
CREATE OR REPLACE FUNCTION public.last_agg ( anyelement, anyelement )
RETURNS anyelement LANGUAGE sql IMMUTABLE STRICT AS $$
        SELECT $2;
$$;

-- And then wrap an aggregate around it
CREATE AGGREGATE public.last (
        sfunc    = public.last_agg,
        basetype = anyelement,
        stype    = anyelement
);

然后您的示例应该几乎可以按以下方式工作:

SELECT FIRST(id), customer, FIRST(total)
FROM  purchases
GROUP BY customer
ORDER BY FIRST(total) DESC;

CAVEAT:忽略NULL行


编辑1-改用postgres扩展名

现在,我使用这种方式:http : //pgxn.org/dist/first_last_agg/

要在ubuntu 14.04上安装:

apt-get install postgresql-server-dev-9.3 git build-essential -y
git clone git://github.com/wulczer/first_last_agg.git
cd first_last_app
make && sudo make install
psql -c 'create extension first_last_agg'

这是一个postgres扩展,为您提供第一个和最后一个功能;显然比上述方法快。


编辑2-排序和过滤

如果使用聚合函数(如此类),则可以对结果进行排序,而无需对数据进行排序:

http://www.postgresql.org/docs/current/static/sql-expressions.html#SYNTAX-AGGREGATES

因此,带有排序的等效示例如下所示:

SELECT first(id order by id), customer, first(total order by id)
  FROM purchases
 GROUP BY customer
 ORDER BY first(total);

当然,您可以按自己认为合适的顺序进行排序和过滤。这是非常强大的语法。


也使用此自定义函数方法。足够通用和简单。为什么使事情复杂化,这种解决方案的性能明显不如其他解决方案?
谢尔盖·谢尔巴科夫

9

查询:

SELECT purchases.*
FROM purchases
LEFT JOIN purchases as p 
ON 
  p.customer = purchases.customer 
  AND 
  purchases.total < p.total
WHERE p.total IS NULL

这是如何运作的!(我去过那儿)

我们要确保每次购买的总金额最高。


一些理论知识(如果只想了解查询,请跳过此部分)

设Total为一个函数T(customer,id),在该函数中它返回给定名称和id的值。为了证明给定的总数(T(customer,id))是最高的,我们必须证明我们想证明

  • ∀xT(客户,id)> T(客户,x)(此总和高于该客户的所有其他总和)

要么

  • ¬∃T(客户,id)<T(客户,x)(该客户没有更高的总数)

第一种方法将需要我们获取我不太喜欢的该名称的所有记录。

第二个将需要一种聪明的方式来说没有比这个更高的记录了。


返回SQL

如果我们保留联接表的名称和总数小于联接表:

      LEFT JOIN purchases as p 
      ON 
      p.customer = purchases.customer 
      AND 
      purchases.total < p.total

我们确保要合并具有同一用户的总计更高的另一条记录的所有记录:

purchases.id, purchases.customer, purchases.total, p.id, p.customer, p.total
1           , Tom           , 200             , 2   , Tom   , 300
2           , Tom           , 300
3           , Bob           , 400             , 4   , Bob   , 500
4           , Bob           , 500
5           , Alice         , 600             , 6   , Alice   , 700
6           , Alice         , 700

这将帮助我们过滤每次购买的最高总额,而无需进行分组:

WHERE p.total IS NULL

purchases.id, purchases.name, purchases.total, p.id, p.name, p.total
2           , Tom           , 300
4           , Bob           , 500
6           , Alice         , 700

这就是我们需要的答案。


8

快速解决方案

SELECT a.* 
FROM
    purchases a 
    JOIN ( 
        SELECT customer, min( id ) as id 
        FROM purchases 
        GROUP BY customer 
    ) b USING ( id );

如果用ID索引表,这真的非常快:

create index purchases_id on purchases (id);

USING子句非常标准。只是某些次要数据库系统没有它。
霍尔格·雅各布斯

2
这没有发现客户的购买总额最大
Johnny Wong

7

在SQL Server中,您可以执行以下操作:

SELECT *
FROM (
SELECT ROW_NUMBER()
OVER(PARTITION BY customer
ORDER BY total DESC) AS StRank, *
FROM Purchases) n
WHERE StRank = 1

说明:这里的 分组依据是根据客户进行的,然后按总数进行订购,然后为每个这样的组指定序列号为StRank,我们将取出第一个有1个StRank为1的客户


谢谢!这非常有效,并且非常易于理解和实施。
ruohola


4

在PostgreSQL中,另一种可能性是结合使用first_valuewindow函数SELECT DISTINCT

select distinct customer_id,
                first_value(row(id, total)) over(partition by customer_id order by total desc, id)
from            purchases;

我创建了一个Composite (id, total),所以两个值都由同一聚合返回。您当然可以始终申请first_value()两次。


3

从我的测试来看,公认的OMG Ponies的“任何数据库支持”解决方案具有良好的速度。

在这里,我提供了一种相同的方法,但是更加完整和干净的任何数据库解决方案。考虑联系(假设每个客户只希望获得一行,甚至希望获得多条记录以获取每个客户的最大总数),并且将为购买表中的实际匹配行选择其他购买字段(例如,purchase_payment_id)。

任何数据库支持:

select * from purchase
join (
    select min(id) as id from purchase
    join (
        select customer, max(total) as total from purchase
        group by customer
    ) t1 using (customer, total)
    group by customer
) t2 using (id)
order by customer

该查询相当快,特别是当购买表上有一个复合索引(例如,客户,总数)时。

备注:

  1. t1,t2是子查询别名,可以根据数据库将其删除。

  2. 注意using (...)截至2017年1月,此子句在MS-SQL和Oracle db中当前不受支持。您必须自己将其扩展到eg on t2.id = purchase.id等。USING语法在SQLite,MySQL和PostgreSQL中有效。



1
  • 如果要从聚合行集中选择任何(根据您的特定条件)行。

  • 如果您还想使用其他(sum/avg)聚合函数max/min。因此,您不能使用线索DISTINCT ON

您可以使用下一个子查询:

SELECT  
    (  
       SELECT **id** FROM t2   
       WHERE id = ANY ( ARRAY_AGG( tf.id ) ) AND amount = MAX( tf.amount )   
    ) id,  
    name,   
    MAX(amount) ma,  
    SUM( ratio )  
FROM t2  tf  
GROUP BY name

您可以amount = MAX( tf.amount )用一个限制条件替换为所需的任何条件:此子查询不得返回多于一行

但是,如果您想做这样的事情,您可能会寻找窗口功能


1

对于SQl Server,最有效的方法是:

with
ids as ( --condition for split table into groups
    select i from (values (9),(12),(17),(18),(19),(20),(22),(21),(23),(10)) as v(i) 
) 
,src as ( 
    select * from yourTable where  <condition> --use this as filter for other conditions
)
,joined as (
    select tops.* from ids 
    cross apply --it`s like for each rows
    (
        select top(1) * 
        from src
        where CommodityId = ids.i 
    ) as tops
)
select * from joined

并且不要忘记为使用的列创建聚簇索引

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.