【问题标题】:SQL OR returns both expressionsSQL OR 返回两个表达式
【发布时间】:2020-07-14 10:25:36
【问题描述】:

我有一个存储的 SQL 过程,我想根据 COALESCE 或 OR 的结果加入另一个表,但不知何故,它似​​乎同时返回了两个表达式。

用例:如果播放列表不存在时间表 -> 返回默认时间表 (24/7)。这就像一个魅力,我得到了 ID 1 的时间表。但如果有一个时间表,程序会返回两个时间表。 :-(

这是我的代码:

...
RIGHT JOIN schedule
ON COALESCE(playlists.ID = schedule.PLID,playlists.SCHEDULES_ID=1)
WHERE devices.IDENTIFIER = ParamID;

有什么建议吗?

提前谢谢!

彼得

更新

对于我的情况,coalesce 似乎不是正确的解决方案。有人有什么建议吗?

我有一个表“时间表”,其中有一列“PLID”(PlaylistID)。在我的查询中,我想根据这个值加入这个表。如果没有匹配 PLID 的时间表,我想加入默认时间表(ID = 1)。

更新 2

我现在正在尝试一种新方法,但似乎仍然不起作用。如果没有时间表,我会得到默认时间表(ID1)。如果有时间表 -> 我得到默认 + 实际分配的时间表(PLID = playlists.ID)

LEFT JOIN schedule
ON (schedule.PLID=playlists.ID) 
OR (schedule.ID = '1')

【问题讨论】:

  • 呃,这不是使用 coalesce 的工作方式
  • 谢谢@CaiusJard,你对我的案子有什么建议吗?
  • 请将其改写为LEFT JOIN

标签: mysql mariadb


【解决方案1】:

作为连接出错的原因是因为您已经安排了它,以便您的连接在连接到默认值时总是成功,并且有时在您连接到数据时也会成功。但关键的是,连接到默认数据会导致与真实数据的联接不同的行,因此它们不再是相关行

真的很难告诉你为什么你的查询失败了,因为你没有包含足够的样本数据,但我会试着发明一些

playlists.ID  playlists.schedulesID  playlist.name
1             1                      default playlist schedule holder
2             100
3             null

schedule.ID  schedule.PLID  schedule.name
X            1              default 247 schedule                   
Y            2            custom schedule 100
Z            null           nully schedule


     COALESCE(playlists.ID=schedule.PLID,playlists.SCHEDULES_ID=1)
1,X  true on 1st argument, row shows
1,Y  false on 1, row hides
1,Z  null on 1, true on 2, row shows
2,X  false on 1, row hides
2,Y  true on 1, row shows
2,Z  null on 1, false on 2, row hides
3,X  false on 1, false on 2, row hides
3,Y  false on 1, false on 2, row hides 
3,Z null on 1, null on 2, row hides

注意第一行的真值集 - 两行有一个真值,因此 1,X 和 1,Z 显示给出“重复”行 1

在你的 COALESCE 中放置一个布尔表达式是你可能只能在 mysql 上逃脱的事情,也许是 Postgres。 Oracle 和 sqlserver 不允许这样做。但这并不是问题的真正原因。提供默认值的逻辑被破坏了,因为它不会尝试将默认值与行放在同一行,以防行丢失

你最好做这样的事情:

SELECT 
  COALESCE(a.x, d.x) as x,
  COALESCE(a.y, d.y) as y
FROM
  tableWithAllRows a
  LEFT JOIN
  tableWithSomeRows s ON a.id = s.id

  CROSS JOIN
  (
    SELECT * FROM sometable WHERE ID = -1 --values in row is -1 are defaults
  ) d
    

请注意,这里我们专门使用外部进行连接,因此我们容忍丢失数据,然后我们使用始终成功的交叉连接并确保子查询只有一行。这意味着外连接中的每一行都有一个可用的默认行。我们使用 coalesce 来提取默认值,而不是进行连接。

编辑;您似乎表示您不理解这一点,所以我将举另一个更容易理解的关系示例

在家里你有储存食物的地方。有时储藏室是橱柜,有时是冰箱。食品可以有一个定义的存储位置或默认位置:

ID, Name 
1, Cupboard
2, Refrigerator

Food, Name, StorageID
A, Milk, 2
B, Cereal, 1
C, Beer, null

牛奶在冰箱里,麦片在橱柜里,啤酒是未指定的(我们将默认它)。你没问题:

SELECT * FROM
  storage s
  LEFT JOIN food f ON f.StorageID = s.ID

Beer 的存储结果为 null,因为它没有设置 StorageID。您决定将其默认放入橱柜:

SELECT * FROM
  storage s
  LEFT JOIN food f 
  ON 
    f.StorageID = s.ID OR
    s.ID = 1 --default to cupboard

这对谷物很有用,因为 StorageID = ID,哦,s.ID=1 也是如此,但你不能有“更真实” - 或者只是真或真,这两个条件都为真麦片所以麦片来一次

B,Cereal,1,1,Cupboard

啤酒也很好。 StorageID 为空,因此 StorageID = ID 不为真,但 ID=1 为真:

C,Beer,null,1,Cupboard

到目前为止一切顺利!但这一切都因牛奶而分崩离析:

A,Milk,2,2,Refrigerator --Milk goes in the fridge because StorageID=ID
A,Milk,2,1,Cupboard     --oh no

这个 Oh No 行出现是因为您说 ID=1 作为默认值 - 您可以看到它是真的!那一排确实有 id1, 橱柜在里面。我们的 ON 子句没有任何东西阻止它出现。也许我们可以更改查询:

SELECT * FROM
  storage s
  LEFT JOIN food f 
  ON 
    f.StorageID = s.ID OR
    (f.storageID IS NULL AND s.ID = 1) --default to cupboard if no storage id 

它会一直有效,直到您到达:

D,Wine,3

什么是存储 ID 3?不知道;它不在表中,但左连接带来了它。 3 也不为空,因此该行最终看起来像:

D,Wine,3,null,null

现在我们陷入了困境。没有办法解决这个问题,因为您之前尝试引入默认值确实为啤酒带来了默认值,但它为 Cereal 带来了第二行的额外值(与第一行无关)。我们试图通过在我们的 ON 中添加更多东西来压制它,但无论我们做什么都会出错

在某些情况下,我们需要另一个 JOIN 来为我们提供更多 ,而不是使用 OR,这会导致更多 ,并且我们将确保这些列具有我们需要的值如果没有真实数据,则选择默认值:

SELECT * FROM
  storage s
  LEFT JOIN food f 
  ON 
    f.StorageID = s.ID

  CROSS JOIN 
  (SELECT * FROM storage WHERE ID = 1) def

看看我们如何添加另一个连接,然后只从存储表(橱柜行)中拉出一行。交叉加入意味着它将在每一行重复。

数据现在看起来像:

A,Milk,2,2,Refrigerator,2,Cupboard
B,Cereal,1,1,Cupboard,2,Cupboard
C,Beer,null,null,null,2,Cupboard
D,Wine,3,null,null,2,Cupboard

看到最后两列的默认值了吗?现在我们可以使用 coalesce 来尝试首先获取分配的位置,然后是默认位置:

SELECT COALESCE(s.Name,def.Name)

对于 Milk,这将返回冰箱。对于啤酒,它首先找到 null,所以它会使用默认值并返回它。

然后你意识到啤酒和葡萄酒最好放在冰箱里,因为毕竟你需要一个凉爽的。将子查询改为“where ID = 2”


另一种复杂的方法是使用一对子查询,一个为匹配的行提供真实值,一个为不匹配的行提供默认值,将它们联合在一起,然后将数据内部连接到它们。 Inner 现在可以工作了,因为我们已经确保右边的表格在左边的每一行都有一行

第三种方法是对行进行编号,特定行优先于默认行(较低的行号),然后仅采用“优先级 1”行

顺便说一下,您的日程表与一组列上的播放列表相关,而您的播放列表与另一组列上的日程表相关。这可能是一个非常糟糕的主意,会让你纠结很长时间

决定一个播放列表是否有多个时间表,或者一个时间表是否有多个播放列表。如果多个播放列表有多个时间表,则使用 playlistschedules 表将其分解为两个 1:M 关系

【讨论】:

  • 非常感谢您的详细解答。不幸的是,我仍然不确定我现在应该如何实施整个事情。我在上面发布了更新并使用“OR”测试了另一种方法。不幸的是,这个也不起作用。 :-( 如果我没有时间表,一切都会按预期进行,我会得到一个 ID=1 的默认时间表,但一旦我创建了更多时间表,默认时间表将与新时间表一起列出。跨度>
  • 答案的哪一部分你不明白?我会努力改进它。如果您提供了示例数据,那么编写与您的上下文相关的解释会容易得多。我解释了为什么使用 OR 解决方案会得到重复的行(它实际上与首先提到的 COALESCE 解决方案没有真正的区别,尽管合并中的“only-works-in-mysql”语法很奇怪)
  • 我在答案中添加了另一部分,使用的数据模型比您显然拥有的更简单。查看牛奶/啤酒位
  • 非常感谢!这个解释太棒了! :-D
【解决方案2】:

我想你想要这个逻辑:

...
RIGHT JOIN schedule
    ON playlists.ID = schedule.PLID OR
       playlists.SCHEDULES_ID = 1
WHERE devices.IDENTIFIER = ParamID;

如果 ID 相等,或者如果播放列表的时间表 ID 为 1,则这将进行加入。

【讨论】:

  • 我仍然得到这个解决方案的两个结果 :-( .
猜你喜欢
  • 2020-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-30
  • 2013-08-29
  • 1970-01-01
相关资源
最近更新 更多