将 .csv 值匹配为 INT。如果 .csv 中某个组中的一个值与另一组中的一个值匹配,则合并字符串

use*_*153 5 postgresql array string string-splitting string-searching

这里我们有两组数字。问题是我无法弄清楚如何从数字的输入到输出(下面的 DDL 和 DML 以及这里的小提琴)。

current_data
1,2,3
1,4
1,5,7
8,9,10
10,11,15

expected_outcome
1,2,3,4,5,7
8,9,10,11,15
Run Code Online (Sandbox Code Playgroud)

我们只是尝试根据单个数字是否与任何其他组匹配来匹配一组数字。然后合并所有这些组。

例如。

如果我们有:

('1,2,3'),
('1,4'),
('1,5,7')
Run Code Online (Sandbox Code Playgroud)

我们想要:

(1,2,3,4,5,7)
Run Code Online (Sandbox Code Playgroud)

我们将它们合并为 PostgreSQL 中的一行。

或(另一个例子):

('8,9,10'), 
('10,11,15')
Run Code Online (Sandbox Code Playgroud)

所需的输出:

(8,9,10,11,15)
Run Code Online (Sandbox Code Playgroud)

查询将对这些数字进行分组,因为它们的共同点是数字 10。但它不会与(1,2,3,4,5,7)不共享数字的前一行(即)分组。

当我们将这些组放在一张表中时。如果他们在每一组中至少有一个匹配的号码,他们才会分组在一起。

======== DDL 和 DML ============

create table current (current_data text not null);
create table expected_output (expected_outcome text not null);

insert into current (current_data) values ('1,2,3'),('1,4'),('1,5,7'),('8,9,10'), ('10,11,15');
insert into expected_output (expected_outcome) values ('1,2,3,4,5,7'),('8,9,10,11,15');
Run Code Online (Sandbox Code Playgroud)

Vér*_*ace 3

如果你想看看这个问题不应该请查看编辑以查看我的前两个解决方案。

\n

TL;DR(小提琴

\n
SELECT num AS "Token", STRING_AGG(val::TEXT, \',\' ORDER BY val::INT) AS "The string"\nFROM\n(\n  SELECT DISTINCT t02.d AS num, UNNEST(t03.d2) AS val FROM\n  (\n    SELECT d, COUNT(d) AS cnt\n    FROM\n    (\n      SELECT \n        UNNEST(STRING_TO_ARRAY(c.current_data, \',\'))::INT AS d,\n        ROW_NUMBER() OVER () AS rn\n      FROM current c\n    ) AS t01\n    GROUP BY d\n    HAVING COUNT(d) > 1\n  ) AS t02\n  JOIN\n  (\n    SELECT \n      STRING_TO_ARRAY(c.current_data, \',\')::INT[] AS d2\n    FROM current c\n  ) AS t03\n  ON t02.d = ANY(t03.d2)\n) AS t04\nGROUP BY num\nORDER BY num;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
Token             The string\n   11                 1,11,99,1203,2222,6666\n   44                  13,44,1005,1110,10078\n 1005    13,44,992,1005,1007,1008,1110,10078\n
Run Code Online (Sandbox Code Playgroud)\n

/TL;博士

\n

介绍:

\n

SQL 并不是为处理字符串而设计的,特别是查看字符串内部并操作该字符串的各个元素(不像,比如说C)——尽管大多数 RDBMS 供应商现在都拥有健康稳定的字符串函数,但它不是 SQL \的堡垒\xc3\xa9(或强点)!

\n

我对数据进行了相当多的更改(甚至很多),以便我可以测试我的解决方案 - 当您处理与实际数据相似的行号时,事情很快就会变得混乱!

\n

C使用(或您选择的其他语言)在应用程序端执行此操作可能会更好(性能方面) !仅仅因为它可以在 SQL 中完成"pure",并不意味着它应该完成!

\n

关于这个词"pure",这个解决方案使用了一些非标准扩展 - SQL Server 解决方案也是如此,我想如果没有这些方便的工具,解决这个问题几乎是不可能的!

\n

我发现这些答案很有帮助:

\n\n

该表是在问题中创建的,输入数据如下:

\n
INSERT INTO current (current_data) VALUES \n(\'992,1005,1007,1008\'),\n(\'44,1005,1110\'),\n(\'13,44,1005,10078\'),\n(\'11,1203,6666\'),\n(\'1,11,99,2222\'),\n(\'1234\');           -- note the singleton!\n
Run Code Online (Sandbox Code Playgroud)\n

所需的输出是:

\n
Token                       The string\n   11           1,11,99,1203,2222,6666\n   44            13,44,1005,1110,10078\n 1005     13,44,992,1005,1007,1008,100\n
Run Code Online (Sandbox Code Playgroud)\n

1234不是包含在所需的结果集中,因为问题提出:

\n
\n

如果该组中的一个值与另一组匹配。

\n
\n

我对这个()的解读if .... matches...是单身人士被排除在外!

\n

最终的 SQL 如上所示,但也可以在fiddle上找到(以及整个过程的逐步过程):

\n

步骤1:

\n

我们需要找到一种方法来识别字符串中哪些数字属于一起 - 我们使用ROW_NUMBER()函数来做到这一点!如果您对 SQL 窗口函数不清楚,我建议您观看 Bruce Momjian 的 YouTube演示“Postgres 中窗口函数的魔力” 。它们非常强大,是 SQL 标准的一部分,并且多次学习如何使用它们所付出的努力都会得到回报!

\n

因此,我们运行这个查询:

\n
SELECT \n  c.current_data AS d,\n  ROW_NUMBER() OVER () AS rn\nFROM current c;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
         d             rn\n992,1005,1007,1008      1\n      44,1005,1110      2\n  13,44,1005,10078      3\n      11,1203,6666      4\n      1,11,99,2222      5\n              1234      6\n
Run Code Online (Sandbox Code Playgroud)\n

第2步:

\n

上面,我提到字符串不是SQL的堡垒 xc3 xa9 - 它们的堡垒 xc3 xa9 是表和记录 - 因此,我们通过运行以下命令将数据转换为表:

\n
SELECT \n  UNNEST(STRING_TO_ARRAY(c.current_data, \',\'))::INT AS d,\n  ROW_NUMBER() OVER () AS rn\nFROM current c\nORDER BY rn, d;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
    d         rn\n  992          1\n 1005          1\n 1007          1\n 1008          1\n   44          2\n 1005          2\n  ...\n  ... snipped for brevity\n  ...\n
Run Code Online (Sandbox Code Playgroud)\n

这利用了两个非标准函数 -STRING_TO_ARRAY()UNNEST()!

\n

STRING_TO_ARRAY 就按照罐头上所说的那样操作!它采用字符串和提供的分隔符 ( \',\') 并将其转换为数组。PostgreSQL 在“底层”使用数组,因此这是一种非常高效的类型!

\n

::运算符是 PostgreSQL 中CASTing 的简写形式 - 比任何其他 RDBMS 都优雅得多 - 所以我们现在有一个整数数组。

\n

然后,我们使用 UNNEST 函数将这些数组的元素转换为“虚拟”表中的行 - 所以我们现在有一个表,其中包含原始组合字符串数据作为表中的整数行!

\n

步骤3:

\n

现在,我们确定我们感兴趣的标记 - 即跨组重复的标记!我们利用上面的 SQL 和聚合COUNT()函数(也可以是对数据的子集(即“窗口”)进行操作的窗口函数)和 HAVING 子句,因为我们只对这些感兴趣重复 - 即 COUNT(x) > 1 如下:

\n
SELECT \n  token, COUNT(token)  -- the COUNT(token) here is not required and can be omitted\nFROM\n(\n  SELECT \n    t.rn AS rn,\n    UNNEST(STRING_TO_ARRAY(t.d, \',\')::INT[]) AS token\n  FROM\n  (\n    SELECT \n      c.current_data AS d,\n      ROW_NUMBER() OVER () AS rn\n    FROM current c\n  ) AS t\n) AS u\nGROUP BY token\nHAVING COUNT(token) > 1;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
token   count\n   11       2\n   44       2\n 1005       3\n
Run Code Online (Sandbox Code Playgroud)\n

步骤4:

\n

现在,我们将重复的标记连接回原始数据(使用 STRING_TO_ARRAY 转换为整数数组,无需 UNNESTing)以挑选出与重复标记相同范围内出现的单个字符串元素作为整数。

\n

匹配测试是ANY()数组函数:

\n
\n

右侧是一个带括号的表达式,它必须产生一个数组值。使用给定运算符对左侧表达式进行求值并与数组的每个元素进行比较,这必须产生布尔结果。如果获得任何 true 结果,则 ANY 的结果为 \xe2\x80\x9ctrue\xe2\x80\x9d。如果没有找到正确的结果(包括数组有零个元素的情况),结果为 \xe2\x80\x9cfalse\xe2\x80\x9d。

\n
\n

因此,对于每个 dupe,如果数组(由原始字符串构造)中存在任何与整数 dupe 标记匹配的匹配整数元素,则将返回dupe和该匹配值!

\n

DISTINCT子句将消除两个字段具有相同值的记录,并使上游 SQL 更加清晰:

\n
SELECT DISTINCT t02.d AS num, UNNEST(t03.d2) AS val FROM\n(\n  SELECT d, COUNT(d) AS cnt\n  FROM\n  (\n    SELECT \n      UNNEST(STRING_TO_ARRAY(c.current_data, \',\'))::INT AS d,\n      ROW_NUMBER() OVER () AS rn\n    FROM current c\n  ) AS t01\n  GROUP BY d\n  HAVING COUNT(d) > 1\n) AS t02\nJOIN\n(\n  SELECT \n    STRING_TO_ARRAY(c.current_data, \',\')::INT[] AS d2\n  FROM current c\n) AS t03\nON t02.d = ANY(t03.d2);\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
  num        val\n   44        1005\n   44        1110\n   11          99\n   11        2222\n 1005         992\n 1005        1008\n   11          11\n  ...\n  ... snipped for brevity\n  ...\n
Run Code Online (Sandbox Code Playgroud)\n

因此,我们可以看到,对于每个重复的整数标记,我们都有一条记录,记录了也出现在同一行中的每个整数 - 包括标记本身。

\n

第 5 步(最终):

\n

现在,我们使用该STRING_AGG()函数通过对重复项进行分组来将匹配元素及其重复项进行分组。上面使用 DISTINCT 意味着我们不必在 STRING_AGG() 函数中使用它。请注意排序 - 它是通过使用::INT转换运算符完成的,因此我们将字符串中的值按其数值排序!

\n
SELECT \n  num AS "Token", -- the token is not required and can be omitted\n  STRING_AGG(val::TEXT, \',\' ORDER BY val::INT) AS "The string"\nFROM\n(\n  SELECT DISTINCT t02.d AS num, UNNEST(t03.d2) AS val FROM\n  (\n    SELECT d, COUNT(d) AS cnt\n    FROM\n    (\n      SELECT \n        UNNEST(STRING_TO_ARRAY(c.current_data, \',\'))::INT AS d,\n        ROW_NUMBER() OVER () AS rn\n      FROM current c\n    ) AS t01\n    GROUP BY d\n    HAVING COUNT(d) > 1\n  ) AS t02\n  JOIN\n  (\n    SELECT \n      STRING_TO_ARRAY(c.current_data, \',\')::INT[] AS d2\n    FROM current c\n  ) AS t03\n  ON t02.d = ANY(t03.d2)\n) AS t04\nGROUP BY num\nORDER BY num;\n
Run Code Online (Sandbox Code Playgroud)\n

结果:

\n
Token                              The string\n   11                  1,11,99,1203,2222,6666\n   44                   13,44,1005,1110,10078\n 1005     13,44,992,1005,1007,1008,1110,10078\n
Run Code Online (Sandbox Code Playgroud)\n

如果您对上述任何内容都不清楚 - 并且需要消化很多内容,我建议您删除一些函数嵌套,并看看如果将每个复合函数分解为单独的部分会发生什么。在小提琴的末尾,我已经包含了输出

\n
EXPLAIN (ANALYZE, BUFFERS, VERBOSE)\n< the query >\n
Run Code Online (Sandbox Code Playgroud)\n

如上所述,SQL 并不是为字符串操作而设计的,因此我无法保证大型结果集的性能 - 除了给出一般印象之外,来自小提琴的任何内容都毫无意义!我强烈建议您使用真实的数据集测试任何解决方案!

\n

最后,如上所述,仅仅因为它可以完成,并不意味着它应该完成!您真正应该做的是将数据放入适当的标准化数据结构和数据库记录中。

\n

AnINTEGER是一个数字,不应存储TEXT连接的 .csv 字符串,尤其不应该存储为连接的 .csv 字符串 - 如果您的数据库正确规范化,您的查询将变得微不足道!此外,您无法强制执行任何类型的数据完整性 - 任何人都可以34, 54, XXX, 73, Oh...作为字符串插入。您可以在 CHECK CONSTRAINT 中使用某种奇特的REXEXP表达式 - 但是花在编写该表达式上的时间最好花在更改您的模式上!

\n