我疯了吗:带有嵌套查询的 PostgreSQL IN 运算符返回意外结果

发布于 2024-12-04 04:52:55 字数 1708 浏览 0 评论 0 原文

以下查询返回 2036 行:

SELECT "FooUID" from "Foo" f
LEFT JOIN "Bar" b ON f."BarUID" = b."BarUID"
WHERE f."BarUID" IS NOT NULL AND b."BarUID" IS NULL

但以下语句仅更新了 1870 行:

UPDATE "Foo" f1 set "BarUID" = 'aNewUID'
WHERE f1."FooUID" IN (
   SELECT f2."FooUID" from "Foo" f2
   LEFT JOIN "Bar" b ON f2."BarUID" = b."BarUID"
   WHERE f2."BarUID" IS NOT NULL AND b."BarUID" IS NULL
)

这怎么可能?

编辑1:第一个查询继续返回166行,第二个查询继续更新0行。

编辑2:

在下面,嵌套查询返回包含UID的行,但外部查询返回0行。

SELECT * from "Foo" f1
WHERE f1."FooUID" = (
   SELECT f2."FooUID" FROM "Foo" f2
   LEFT JOIN "Bar" b ON f2."BarUID" = b."BarUID"
   WHERE f2."BarUID" IS NOT NULL AND b."BarUID" IS NULL
   LIMIT 1
)

我疯了吗?

编辑3:

@wildplasser提供的以下语句成功更新了剩余的166行:

UPDATE "Foo" ff
SET "BarUID" = 'aNewUID'
WHERE ff."BarUID" IS NOT NULL
AND NOT EXISTS (
   SELECT * FROM "Bar" bb
   WHERE bb."BarUID"= ff."BarUID"
)

不过,我还是不明白为什么原著没有收录它们。如果嵌套查询选择了 166 个 "FooUID",为什么它们不能与使用 IN"Foo" 表中的行匹配?

编辑4:我想得越多,这个背景可能就越重要:

这一切都发生在最近从另一台克隆的数据库服务器上。我与进行克隆的 IT 人员进行了交谈,结果发现他在将原始数据库进行克隆之前没有关闭在原始数据库上运行的应用程序。这意味着数据库很可能在交易过程中被关闭(我不知道有多不礼貌)。数据库中的某些内容是否有可能处于损坏状态,导致我看到这些幻象行?

不幸的是,自从运行了 wildplasser 的修复程序后,我无法再重现它。原始数据库(再次启动并为应用程序提供服务)没有我试图在副本上修复的无效数据,更不用说我目睹的恶作剧的任何痕迹了。

我应该提到的是,在运行修复之前,我将问题简化为最基本的荒谬:我首先从编辑 2 中的嵌套查询中选择了 FooUID,将其复制到剪贴板,然后运行一个查询,选择来自 Foo,其中 FooUID 等于粘贴的值 - 这仍然返回 0 行。

The following query returns 2036 rows:

SELECT "FooUID" from "Foo" f
LEFT JOIN "Bar" b ON f."BarUID" = b."BarUID"
WHERE f."BarUID" IS NOT NULL AND b."BarUID" IS NULL

But the following statement only updated 1870 rows:

UPDATE "Foo" f1 set "BarUID" = 'aNewUID'
WHERE f1."FooUID" IN (
   SELECT f2."FooUID" from "Foo" f2
   LEFT JOIN "Bar" b ON f2."BarUID" = b."BarUID"
   WHERE f2."BarUID" IS NOT NULL AND b."BarUID" IS NULL
)

How is this possible?

EDIT 1: The first query continues to return 166 rows, and the second continues to update 0 rows.

EDIT 2:

In the following, the nested query returns a row containing a UID, but the outer query returns 0 rows.

SELECT * from "Foo" f1
WHERE f1."FooUID" = (
   SELECT f2."FooUID" FROM "Foo" f2
   LEFT JOIN "Bar" b ON f2."BarUID" = b."BarUID"
   WHERE f2."BarUID" IS NOT NULL AND b."BarUID" IS NULL
   LIMIT 1
)

Am I crazy?

EDIT 3:

The following statement, provided by @wildplasser succeeded in updating the remaining 166 rows:

UPDATE "Foo" ff
SET "BarUID" = 'aNewUID'
WHERE ff."BarUID" IS NOT NULL
AND NOT EXISTS (
   SELECT * FROM "Bar" bb
   WHERE bb."BarUID"= ff."BarUID"
)

However, I still don't understand why the original didn't pick them up. If the nested query selected 166 "FooUID"s, why would they not be matched to rows in the "Foo" table using IN?

EDIT 4: The more I think about it, this background might be important:

This all took place on a database server that was recently cloned from another one. I spoke to the IT guy who did the cloning, and it turns out he didn't shut down an application running on top of the original DB before bringing it down to clone it. This means the DB was mostly likely brought down mid-transaction (I don't know how ungracefully). Is it possible something in the database was left in a corrupted state, leading me to see these phantom rows?

Unfortunately I can no longer repro it, since running wildplasser's fix. The original DB (up and serving the application again) has none of the invalid data I was trying to fix on the copy, much less any trace of the shenanigans I witnessed.

I should mention that before running the fix, I reduced the issue to the most basic absurdity: I first selected the FooUID from the nested query in Edit 2, copied it to the clipboard, then ran a query selecting from Foo where FooUID equaled the pasted value - this still returned 0 rows.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

新一帅帅 2024-12-11 04:52:55

如果你用 NOT EXIST 重写这个,会发生什么,就像

UPDATE Foo ff
SET baruid = 'aNewUID'
WHERE ff.baruid IS NOT NULL
AND NOT EXISTS (SELECT * FROM bar bb
    WHERE bb.baruid = ff.baruid
    );

在我看来比选择外部连接的肢体腿干净得多。

What happens if you rewrite this with NOT EXIST, like

UPDATE Foo ff
SET baruid = 'aNewUID'
WHERE ff.baruid IS NOT NULL
AND NOT EXISTS (SELECT * FROM bar bb
    WHERE bb.baruid = ff.baruid
    );

Looks much cleaner to me than selecting the limb leg of an outer join.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文