【问题标题】:IIF Function returning incorrect calculated values - SQL ServerIIF 函数返回不正确的计算值 - SQL Server
【发布时间】:2020-09-27 09:42:30
【问题描述】:

我正在编写一个查询来显示在赛马上进行单程投注的回报

PlaceProfit 结果存在问题 - 如果马匹完成位置在 1-4 之间,则此应该显示返回,如果位置 => 5,则显示失败

如果马匹的完赛名次低于第 9 名,则显示正确的回报,但第 10 名及以上被视为获胜。

我在下面包含我的代码以及输出。

ALTER VIEW EachWayBetting
AS
SELECT       a.ID,

RaceDate, 
runners, 
track.NAME AS Track, 
horse.NAME as HorseName, 
IndustrySP,
Place AS 'FinishingPosition',

-- // calculates returns on the win & place parts of an each way bet with 1/5 place terms //
IIF(A.Place = '1', 1.0 * (A.IndustrySP-1), '-1') AS WinProfit,
IIF(A.Place <='4', 1.0 * (A.IndustrySP-1)/5, '-1') AS PlaceProfit 

FROM         dbo.NewRaceResult a

     LEFT OUTER JOIN track ON track.ID = A.TrackID
     LEFT OUTER JOIN horse ON horse.ID = A.HorseID  
WHERE        a.Runners > 22

这会返回:

【问题讨论】:

  • 看起来像您;将Place 存储为字符串类型数据类型,而不是数字数据类型。 '10' 的值低于 '4'Place 的值为 '10',则表达式 A.Place &lt;= '4'true 。修复您的设计,将数值数据存储在数值数据类型中(int 在这里似乎很合适)。 varchar 不是“一刀切”的数据类型;远非如此。
  • 另外,当使用文字数值时,不要将它们作为字符串传递,例如'-1'。他们应该被引用(只是-1)。
  • @Larnu 谢谢,但是 Place 列也可能存储诸如“PU”之类的数据,以表明这匹马没有完成比赛,因为它是“拉起来”的 - 你知道解决办法吗我遇到的问题?
  • 那么,我建议您有 2 列,一列用于数字位置(可以是 NULL),另一列表示马分类失败,例如Pulled Up, Fell, etc. 就目前而言,这是一场 20 匹马的比赛,前“3”马将是第 1、10 和 11 名的马,而“最后”马将是第 9 名的马,不是 20 号,
  • 在您修复可以在计算之前应用try_cast(place as int) 的数据类型之前,这会从 VarChar 更改为 Int 并为非数字字符串(如 'PU')返回 NULL

标签: sql sql-server tsql


【解决方案1】:

正如我在 cmets 中提到的,问题是您为 place 选择的数据类型,它是 varchar。字符串数据类型的排序完全不同于数字数据类型的排序。字符串按字符从左到右排序,按照您使用的排序规则中字符的排序顺序。但是,数值数据类型是从低到高排序的。

这意味着,对于数值数据类型,值 2 的值低于 10,但是,对于 varchar,值 '2' 的值 >高于'10'。对于varchar,这是因为首先在第一个字符上完成排序。 '2' 的值高于'1',因此'2' 的值高于'10'

这里的解决方案很简单,修复你的设计;将数字数据存储在数字数据类型中(int 在这里似乎合适)。您还违反了范式规则,因为您将其他数据存储在列中;主要是马未能分类的原因。此类数据不是“地点”,而是关于马没有地点的原因的信息,因此应该在单独的列中。

因此,您可以通过首先添加一个新列,然后将其值更新为非数字值并使 place 仅包含数字数据,然后最后更改您的 place 列来解决此问题。

ALTER TABLE dbo.YourTable ADD UnClassifiedReason varchar(5) NULL; --Obviously use an appropriate length.
GO

UPDATE dbo.YourTable
SET Place = TRY_CONVERT(int,Place),
    UnClassifiedReason = CASE WHEN TRY_CONVERT(int,Place) IS NULL THEN Place END;
GO

ALTER TABLE dbo.YourTable ALTER COLUMN Place int NULL;
GO

如果Place 不允许NULL 值,则需要先ALTER 列以允许它们。

【讨论】:

  • 是的,分离数字和非数字数据是这里的解决方案。我可能会补充说,在更改列时,我必须先删除约束。我会参考这个关于约束的答案,因为它们描述了给它们明确的名称,这支持 Gordon 关于有意义的命名约定的以下建议:stackoverflow.com/a/48528902/12726001
【解决方案2】:

除了按照 Larnu 的建议修复数据之外,您还应该修复查询:

SELECT nrr.ID, nrr.RaceDate, nrr.runners,
       t.NAME AS Track, t.NAME as HorseName, nrr.IndustrySP,
       Place AS FinishingPosition,
-- // calculates returns on the win & place parts of an each way bet with 1/5 place terms //
      (CASE WHEN nrr.Place = 1 THEN (nrr..IndustrySP - 1.0) ELSE -1 END) AS WinProfit,
      (CASE WHEN nrr.Place <= 4 THEN (nrr.IndustrySP - 1.0) / 5 THEN -1 END) AS PlaceProfit 
FROM dbo.NewRaceResult nrr LEFT JOIN
     track t
     ON t.ID = nrr.TrackID LEFT JOIN
     horse h
     ON h.ID = nrr.HorseID  
WHERE nrr.Runners > 22;

重要的变化是从数字和列名中删除单引号。看来您需要了解字符串、数字和标识符之间的区别。

其他变化是:

  • 有意义的表别名,而不是a等无意义的字母。
  • 限定所有列引用,因此很清楚列的来源。
  • IFF() 切换到CASEIFF() 是定制的 SQL Server; CASE 是条件表达式的标准 SQL(都可以正常工作)。
  • 确保条件表达式所有分支返回的类型一致。

注意:即使您不更改Place 的类型,此版本也可以使用。字符串将在适当的位置转换为数字。我不提倡依赖这种静默转换,所以我建议修复数据。

如果place 可以有非数字值,那么您需要将它们转换:

      (CASE WHEN TRY_CONVERT(int, nrr.Place) = 1 THEN (nrr..IndustrySP - 1.0) ELSE -1 END) AS WinProfit,
      (CASE WHEN TRY_CONVERT(int, nrr.Place) <= 4 THEN (nrr.IndustrySP - 1.0) / 5 THEN -1 END) AS PlaceProfit 

但重要的是要修复数据。

【讨论】:

  • *"即使您不更改 Place 的类型,此版本也可以使用。" 不幸的是,OP 实际上也在 Place 中存储非数字数据,因此您要么需要从数据中删除它,要么在执行 CASE 表达式之前先从数据中删除它。
  • @Larnu 。 . .你怎么知道?我在问题中没有看到任何迹象表明存在非数字值。
  • 来自 OP 的 comment 和图像(显示一行的值为 'PU')。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-29
相关资源
最近更新 更多