从表中选择多个列,但按一个分组

Posted

技术标签:

【中文标题】从表中选择多个列,但按一个分组【英文标题】:Select multiple columns from a table, but group by one 【发布时间】:2014-02-08 15:57:56 【问题描述】:

表名是“OrderDetails”,列如下:

OrderDetailID || ProductID || ProductName || OrderQuantity

我正在尝试选择多个列并按 ProductID 分组,同时具有 OrderQuantity 的总和。

 Select ProductID,ProductName,OrderQuantity Sum(OrderQuantity)
 from OrderDetails Group By ProductID

当然,这段代码会出错。我必须添加其他列名来分组,但这不是我想要的,因为我的数据有很多项目,所以结果出乎意料。

样本数据查询:

来自 OrderDetails 的 ProductID、ProductName、OrderQuantity

结果如下:

 ProductID     ProductName    OrderQuantity
    1001          abc               5
    1002          abc               23    (ProductNames can be same)
    2002          xyz               8
    3004          ytp               15
    4001          aze               19
    1001          abc               7     (2nd row of same ProductID)

预期结果:

 ProductID     ProductName    OrderQuantity
    1001          abc               12    (group by productID while summing)
    1002          abc               23
    2002          xyz               8
    3004          ytp               15
    4001          aze               19

由于 ProductName 不是唯一的,如何选择多个列和 Group By ProductID 列?

同时,获取 OrderQuantity 列的总和。

【问题讨论】:

您可能想查看字符串的聚合。不幸的是,我没有这方面的经验。 ***.com/questions/13639262/… 【参考方案1】:

当我选择多列时,我使用此技巧按一列分组:

SELECT MAX(id) AS id,
    Nume,
    MAX(intrare) AS intrare,
    MAX(iesire) AS iesire,
    MAX(intrare-iesire) AS stoc,
    MAX(data) AS data
FROM Produse
GROUP BY Nume
ORDER BY Nume

这行得通。

【讨论】:

巧妙,谢谢!对于那些路过的人:您将max() 放在每个未分组的列周围,将as ___ 重命名为您希望它显示的内容,然后将group by 放在您想要区分的列周围没有max() . 哈哈,欺骗 SQL 的好方法,但我想知道这是否适用于所有情况? 这没有意义,而且可能是错误的!如果您的数据中每个 column_A 有几个 column_B,那么如果您按 Column_A 分组并在选择中使用 MAX(Column_B) 绕过分组限制,那么它只是这些 column_B 值之一(这里是由最大限度)。这通常不是你想要的!如果您的数据中每个 column_A 没有不同的 column_B 值,那么您应该简单地将您的 column_B 添加到 GROUP BY 子句中,如其他答案所述。 @安德鲁 我同意@Andrew - S.Serpooshan 当我们在列中有不同的值时它不起作用 如果您的列类型是布尔值,这将不起作用【参考方案2】:

您的数据

DECLARE @OrderDetails TABLE 
(ProductID INT,ProductName VARCHAR(10), OrderQuantity INT)

INSERT INTO @OrderDetails VALUES
(1001,'abc',5),(1002,'abc',23),(2002,'xyz',8),
(3004,'ytp',15),(4001,'aze',19),(1001,'abc',7)

查询

 Select ProductID, ProductName, Sum(OrderQuantity) AS Total
 from @OrderDetails 
 Group By ProductID, ProductName  ORDER BY ProductID

结果

╔═══════════╦═════════════╦═══════╗
║ ProductID ║ ProductName ║ Total ║
╠═══════════╬═════════════╬═══════╣
║      1001 ║ abc         ║    12 ║
║      1002 ║ abc         ║    23 ║
║      2002 ║ xyz         ║     8 ║
║      3004 ║ ytp         ║    15 ║
║      4001 ║ aze         ║    19 ║
╚═══════════╩═════════════╩═══════╝

【讨论】:

但我说过,我不想将其他列名添加到分组依据,它会产生意想不到的结果。 好吧,除非您有多个产品名称与同一个产品 ID 相关联,否则它不会给您带来意想不到的结果。如果是这种情况并且您想避免这种情况,请参阅我的更新 我之前使用的查询确实提供了您在示例数据中显示的预期结果集。 @OzanAyten 我已将您更新的数据用于相同的查询,它向我显示的结果如您预期的结果集中所示。 是的,但我的问题很清楚。如果我把它放在我的问题上,有太多的数据是无法理解的。所以这就是为什么我要求只选择多列而只按一列分组。【参考方案3】:

我只是想添加一种更有效、更通用的方法来解决这类问题。 主要思想是处理子查询。

按照表的 ID 进行分组并加入同一个表。

您的情况更具体,因为您的 productId 不是唯一的,因此有两种方法可以解决此问题。

我将从更具体的解决方案开始: 由于您的 productId 不是唯一的,我们需要一个额外的步骤,即在分组并执行如下子查询后选择 DISCTINCT 产品 ID:

WITH CTE_TEST AS (SELECT productId, SUM(OrderQuantity) Total
                    FROM OrderDetails
                    GROUP BY productId)
SELECT DISTINCT(OrderDetails.ProductID), OrderDetails.ProductName, CTE_TEST.Total
FROM OrderDetails 
INNER JOIN CTE_TEST ON CTE_TEST.ProductID = OrderDetails.ProductID

这完全符合预期

 ProductID     ProductName         Total
    1001          abc               12    
    1002          abc               23
    2002          xyz               8
    3004          ytp               15
    4001          aze               19

但是有一种更简洁的方法可以做到这一点。我猜 ProductId 是 products 表的外键,我猜应该有和 OrderId 主键(唯一)在这个表中。

在这种情况下,只需执行几个步骤即可包含额外的列,同时仅对一个列进行分组。这将与以下解决方案相同

我们以这个t_Value 表为例:

如果我想按描述分组并显示所有列。

我要做的就是:

    使用 GroupBy 列和 COUNT 条件创建 WITH CTE_Name 子查询 从值表中选择全部(或您要显示的任何内容)并从 CTE 中选择总计 INNER JOIN 在 ID(主键或唯一约束) 列上带有 CTE

就是这样!

这里是查询

WITH CTE_TEST AS (SELECT Description, MAX(Id) specID, COUNT(Description) quantity 
                    FROM sch_dta.t_value
                    GROUP BY Description)
SELECT sch_dta.t_Value.*, CTE_TEST.quantity 
FROM sch_dta.t_Value 
INNER JOIN CTE_TEST ON CTE_TEST.specID = sch_dta.t_Value.Id

结果如下:

【讨论】:

【参考方案4】:

mysql GROUP_CONCAT 函数可以帮助https://dev.mysql.com/doc/refman/8.0/en/group-by-functions.html#function_group-concat

SELECT ProductID, GROUP_CONCAT(DISTINCT ProductName) as Names, SUM(OrderQuantity)
FROM OrderDetails GROUP BY ProductID

这将返回:

ProductID     Names          OrderQuantity
1001          red            5
1002          red,black      6
1003          orange         8
1004          black,orange   15

与@Urs Marian 在这里发布的https://***.com/a/38779277/906265 类似的想法

【讨论】:

非常酷的功能 :) 看起来微软不久前终于有了类似的东西,database.guide/the-sql-server-equivalent-to-group_concat【参考方案5】:
    WITH CTE_SUM AS (
      SELECT ProductID, Sum(OrderQuantity) AS TotalOrderQuantity 
      FROM OrderDetails GROUP BY ProductID
    )
    SELECT DISTINCT OrderDetails.ProductID, OrderDetails.ProductName, OrderDetails.OrderQuantity,CTE_SUM.TotalOrderQuantity 
    FROM 
    OrderDetails INNER JOIN CTE_SUM 
    ON OrderDetails.ProductID = CTE_SUM.ProductID

请检查这是否有效。

【讨论】:

【参考方案6】:

你可以试试这个:

Select ProductID,ProductName,Sum(OrderQuantity) 
 from OrderDetails Group By ProductID, ProductName

您只需要Group By Select 子句中不带有聚合函数的列。因此,在这种情况下,您可以只使用Group By ProductID 和 ProductName。

【讨论】:

这个答案也一样,我说过,我不想添加其他列名来分组,它会产生意想不到的结果。 一个productId 只能有一个相关的ProductName 不是吗?所以 Group By ProductId, ProductName 在这种情况下将给出与 Group By ProductId 相同的结果 ProductName 不是唯一的,只有 ProductID 是唯一的。另外,我知道您的回答是什么意思,但在我的问题中,我只要求按一列分组。【参考方案7】:

您可以尝试以下查询。我假设您的所有数据都有一个表。

SELECT OD.ProductID, OD.ProductName, CalQ.OrderQuantity
FROM (SELECT DISTINCT ProductID, ProductName
      FROM OrderDetails) OD
INNER JOIN (SELECT ProductID, OrderQuantity SUM(OrderQuantity)
            FROM OrderDetails
            GROUP BY ProductID) CalQ
ON CalQ.ProductID = OD.ProductID

【讨论】:

【参考方案8】:

在我看来,这是一个严重的语言缺陷,使 SQL 落后于其他语言数年。这是我令人难以置信的 hacky 解决方法。这是一个完全的混搭,但它总是有效的。

在此之前,我想提请注意@Peter Mortensen 的答案,我认为这是正确的答案。我这样做的唯一原因是因为大多数 SQL 实现的连接操作都非常慢,并迫使你打破“不要重复自己”。我需要我的查询快速填充。

这也是一种古老的做事方式。 STRING_AGG 和 STRING_SPLIT 干净多了。我再次这样做是因为它总是有效。

-- remember Substring is 1 indexed, not 0 indexed
SELECT ProductId
  , SUBSTRING (
      MAX(enc.pnameANDoq), 1, CHARINDEX(';', MAX(enc.pnameANDoq)) - 1
    ) AS ProductName
  , SUM ( CAST ( SUBSTRING (
      MAX(enc.pnameAndoq), CHARINDEX(';', MAX(enc.pnameANDoq)) + 1, 9999
    ) AS INT ) ) AS OrderQuantity
FROM (
    SELECT CONCAT (ProductName, ';', CAST(OrderQuantity AS VARCHAR(10)))
      AS pnameANDoq, ProductID
    FROM OrderDetails
  ) enc
GROUP BY ProductId

或者用简单的语言:

将除一个字段之外的所有内容粘贴到一个字符串中,并使用您知道不会使用的分隔符 分组后使用子字符串提取数据

在性能方面,我一直使用字符串比诸如 bigints 之类的东西具有出色的性能。至少用 microsoft 和 oracle 的 substring 是一个快速的操作。

这避免了您在使用 MAX() 时遇到的问题,当您在多个字段上使用 MAX() 时,它们不再一致并且来自不同的行。在这种情况下,可以保证您的数据完全按照您要求的方式粘合在一起。

要访问第三个或第四个字段,您需要嵌套子字符串,“在第一个分号之后查找第二个”。这就是为什么 STRING_SPLIT 可用时更好的原因。

注意:虽然超出了您的问题范围,但当您处于相反的情况并且您正在对组合键进行分组但不希望显示所有可能的排列时,这尤其有用,即您想要公开 ' foo' 和 'bar' 作为组合键,但希望按 'foo' 分组

【讨论】:

【参考方案9】:

==编辑==

我再次检查了您的问题并得出结论,这是无法完成的。

ProductName 不是唯一的,它必须是 Group By 的一部分或从您的结果中排除。

例如,如果您 Group By 只有 ProductID,SQL 将如何向您呈现这些结果?

ProductID | ProductName | OrderQuantity 
---------------------------------------
1234      | abc         | 1
1234      | def         | 1
1234      | ghi         | 1
1234      | jkl         | 1

【讨论】:

我正在使用 sql,第一个代码块给出了语法错误。另外,我不想将其他列添加到 Group By。 我附上了我能想到的唯一方法,无需按两个项目分组。问题是如果您按数字分组,则无法选择相应的字符串而不对其进行一些聚合。 @har07 发布的答案看起来是最好的选择。例如,如果两个项目具有相同的 OrderQuantity 但具有不同的 ProductName,则服务器不知道要显示哪个 ProductName。希望这是有道理的。 我想合并和求和相同 ProductID 的行的 OrderQuantity :) 我也知道为什么这不起作用。这一切都说得通,但真的不可能吗? 我刚刚注意到这让你回到了第一方......你得到的结果有什么问题?也许您的查询正是您想要的,只是格式错误? 对不起,但正如我在我的问题中所说,我也需要其他专栏。我知道如何分组。我可以自己做,但我的问题不同。我已经编辑了我的问题,请阅读最后一个阶段【参考方案10】:

我遇到了与 OP 类似的问题。然后我看到了@Urs Marian 的回答,这很有帮助。 但另外我一直在寻找的是,当一列中有多个值并且它们将被分组时,我如何获得最后提交的值(例如,按日期/id 列排序)。

例子:

我们有如下表结构:

CREATE TABLE tablename(
    [msgid] [int] NOT NULL,
    [userid] [int] NOT NULL,
    [username] [varchar](70) NOT NULL,
    [message] [varchar](5000) NOT NULL
) 

现在表中至少有两个数据集:

+-------+--------+----------+---------+
| msgid | userid | username | message |
+-------+--------+----------+---------+
|     1 |      1 | userA    | hello   |
|     2 |      1 | userB    | world   |
+-------+--------+----------+---------+

因此,如果相同的用户 ID 具有不同的用户名值,以下 SQL 脚本确实可以对它进行分组(在 MSSQL 上检查)。在下面的示例中,将显示具有最高 msgid 的用户名:

SELECT m.userid, 
(select top 1 username from table where userid = m.userid order by msgid desc) as username,
count(*) as messages
FROM tablename m
GROUP BY m.userid
ORDER BY count(*) DESC

【讨论】:

【参考方案11】:

SELECT ProductID, ProductName, OrderQuantity, SUM(OrderQuantity) FROM OrderDetails WHERE(OrderQuantity) IN(SELECT SUM(OrderQuantity) FROM OrderDetails GROUP BY OrderDetails) GROUP BY ProductID, ProductName, OrderQuantity;

我用上面的方案解决了Oracle12c中类似的问题。

【讨论】:

问题是如何在不使用所有列的情况下进行分组。

以上是关于从表中选择多个列,但按一个分组的主要内容,如果未能解决你的问题,请参考以下文章

从表中保存多个下拉选择 - PHP MySQL

通过在Oracle中分组一列从表中选择数据

从表中选择具有最大日期的行

如何按 2 列分组,但按 count() 降序排列

如果存在则从表中选择,否则从oracle中的另一个表中选择

仅从表中选择一些列