作为连接出错的原因是因为您已经安排了它,以便您的连接在连接到默认值时总是成功,并且有时在您连接到数据时也会成功。但关键的是,连接到默认数据会导致与真实数据的联接不同的行,因此它们不再是相关行
真的很难告诉你为什么你的查询失败了,因为你没有包含足够的样本数据,但我会试着发明一些
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 关系