【问题标题】:Correct format for Select in SQL ServerSQL Server 中选择的正确格式
【发布时间】:2018-08-30 23:46:24
【问题描述】:

我对任何数据库都有一个简单的查询,它总是在 MySQL 而不是 SQL Server 中运行

select
tagalerts.id,
ts,
assetid,
node.zonename,
battlevel
from tagalerts, node
where
ack=0 and
tagalerts.nodeid=node.id
group by assetid
order by ts desc 

错误是:

列 tagalerts.id 在选择列表中无效,因为它不包含在聚合函数或 group by 子句中。

tagalerts.id 添加到group by 子句不是一个简单的情况,因为tsassetid 等的错误重复,这意味着所有选择都需要在一个组或聚合函数中...其中任何一个都会导致毫无意义和不准确的结果。

将选择拆分为子查询以正确排序和分组(这同样适用于 MySQL,正如您所期望的那样)会使事情变得更糟

SELECT * from
(select
tagalerts.id,
ts,
assetid,
node.zonename,
battlevel
from tagalerts, node
where
ack=0 and
tagalerts.nodeid=node.id
order by ts desc 
)T1
group by assetid

order by 子句在视图、内联函数、派生表和表达式中无效,除非使用 TOP 等

“正确的输出”应该是

id     ts                 assetid     zonename     battlevel
1234   a datetime         1569        Reception    0
3182   another datetime   1572        Reception    0

要么我完全错误地阅读 SQL Server 的规则,要么这是该数据库的主要缺陷。

如何编写此代码以在两个系统上工作?

【问题讨论】:

  • Order By 移动到group by 下的外部查询中,但分组仍然会出错。
  • 恰恰相反,漏洞在mysql。如果您使用跨三列的聚合,则只需要按其他列分组才有意义。如果其他列的值超过 1 个,如果没有分组依据,则返回哪个值?
  • 你想要什么结果,为什么要使用 group by?
  • 谢谢,但这会导致 T1.id 在选择列表中无效,因为它既不包含在聚合函数中,也不包含在按 cluase 分组中
  • MySql 允许你在这里做的事情实际上违反了 SQL 标准。 Sql Server 方式(要求您使用聚合函数,如 MAX()AVG() 与不在 group by 中的列)是正确的。

标签: mysql sql-server


【解决方案1】:

在大多数数据库中,您不能只包含不在GROUP BY 中的列而不使用聚合函数。

MySql 是一个例外。但 MS SQL Server 不是。

所以你可以只用“assetid”保留GROUP BY
但随后对所有其他列使用适当的聚合函数。

另外,请使用 JOIN 语法,看在天堂的份上。
select * from table1, table2 where table1.id2 = table2.id 这样的 SQL 使用的是上个世纪的语法。

SELECT
MAX(node.id) AS id,
MAX(ta.ts) AS ts,
ta.assetid,
MAX(node.zonename) AS zonename,
MAX(ta.battlevel) AS battlevel
FROM tagalerts AS ta
JOIN node ON node.id = ta.nodeid
WHERE ta.ack = 0
GROUP BY ta.assetid
ORDER BY ta.ts DESC;

在 MS SQL Server 中使用的另一个技巧是窗口函数 ROW_NUMBER。
但这可能不是您所需要的。

例子:

SELECT id, ts, assetid, zonename, battlevel
FROM
(
  SELECT
   node.id,
   ta.ts,
   ta.assetid,
   node.zonename,
   ta.battlevel,
   ROW_NUMBER() OVER (PARTITION BY ta.assetid ORDER BY ta.ts DESC) AS rn
  FROM tagalerts AS ta
  JOIN node ON node.id = ta.nodeid
  WHERE ta.ack = 0
) q
WHERE rn = 1
ORDER BY ts DESC;

【讨论】:

  • 感谢 LukStorms 我添加了最大值,但它随后抱怨 order by 所以我删除了 order by 并且它起作用了(可能是因为所有最大值)。对我来说似乎很奇怪。我会远离 SQL Server。
  • 是的,在 MS SQL 中,如果使用 TOP 或 FOR XML,则 ORDER BY 只能在子查询中使用。但这真的没那么糟糕,你知道的。带有 group by 的规则实际上是一个 ANSI SQL 标准。偏离它的是MySql,尽管他们解释了为什么他们在某处这样做。也许对您来说最简单的方法是迁移到 MySql 8.x 或 MariaDB。一些很酷的新 SQL 东西,比如 CTE 和窗口函数也在其中。
【解决方案2】:

我强烈怀疑这个查询是错误,即使在 MySql 中也是如此。

我们遗漏了很多细节(示例数据,我们不知道所有列属于哪个表),但我所知道的是您按assetid 分组,看起来像一个assetid 值在组中可能有多个ts(时间戳)值。看起来您还指望order by ts desc 确保both您首先在结果中看到最近的时间戳并且每个assetid 组使用最近的该组可能的ts 时间戳。

MySql 只保证前者,不保证后者。 Nothing 在这个查询中保证每个assetid 都使用最新的可用时间戳。您可能会看到错误的时间戳,然后还将这些错误的时间戳用于order by。这是 Sql Server 规则要停止的问题。 MySql 违反了 SQL 标准,允许您编写错误的查询。

相反,您需要查看每一列并将其添加到 group by(最好在所有值都已知相同的情况下,无论如何)将其包装在一个聚合中函数如MAX()MIN()AVG() 等,因此有一个确定性结果,使用该组的值。

如果组中某列的所有值都相同,则将其添加到group by 中没有问题。如果值不同,您希望准确确定为结果集选择了哪一个。

当我在这里时,tagalerts, node 连接语法已经过时了 20 多年。对每个表使用别名并在每列前加上别名也是一种很好的做法。我提到这些是为了解释为什么我在下面的代码示例中更改了它,尽管我只在我确信该列属于哪个表的列前加上前缀。

这个查询应该在两个数据库上运行:

SELECT ta.assetid, MAX(ta.id) "id", MAX(ta.ts) "ts",
    MAX(n.zonename) "zonename", MAX(battlevel) "battlevel"
FROM tagalerts ta
INNER JOIN node n ON ta.nodeid = n.id
WHERE ack = 0
GROUP BY ta.assetid
ORDER BY ts DESC

这里还有一个问题,结果可能是从连接的node 表中的不同记录中选择值。因此,如果battlevelnode 表的一部分,您可能会看到将zonenamebattlevel 匹配的结果,该结果从未出现在数据的任何记录中。在 Sql Server 中,通过使用APPLY 将一个node 记录与每个tagalert 匹配,可以轻松解决此问题。 MySql 不支持这一点(至少自 2012 年以来,其他所有主要数据库中都存在 APPLY 或等效项),但在这种情况下,您可以使用 两个 JOIN 来模拟它,其中第一个连接是使用 GROUP BY 确定值的子查询将唯一标识所需的 node 记录,第二个连接是到 node 表以实际生成该记录。不幸的是,我们需要更多地了解相关表格才能真正为您编写此代码。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-08-01
    • 2018-01-11
    • 2012-08-15
    • 2020-11-17
    • 1970-01-01
    • 2010-09-28
    • 1970-01-01
    相关资源
    最近更新 更多