规范化表:在一系列行中查找唯一列 (Oracle 10.x)

Posted

技术标签:

【中文标题】规范化表:在一系列行中查找唯一列 (Oracle 10.x)【英文标题】:Normalizing a table: finding unique columns over series of rows (Oracle 10.x) 【发布时间】:2011-04-18 16:50:39 【问题描述】:

我有一个结构如下的表:

WorkerPersons
-------------------------------
ID          (PK)
PersonID    (Indicates which version of Person the record describes)
SomeColumn1 (data specific to Worker)
SomeColumn2 (data specific to Person)
....
SomeColumnN
-------------------------------

如您所见,它是一个非规范化表,将 Worker 和 Person(以及一个 Person 的多个版本)数据保存在一个表中。我的愿望是规范化该表,但是,由于该表包含大量数据(很多列),我需要确定哪些列应该进入 Workers 表,哪些列应该进入 Persons 表。结果应该是这样的:

Workers                 Persons
----------------------- ---------------------
ID                      ID
PersonID (now a FK)     PersonColumn1
WorkerColumn1           PersonColumn2
WorkerColumn2           ...
...                     PersonColumnN
WorkerColumnN
----------------------- ---------------------

为此,我需要分析所有唯一人员(在 WorkerPersons 中由 PersonID 分隔)的 Person 范围内哪些数据不同。例如:

WorkerPersons
-------------------------------------------------------
ID      PersonID      Column1      Column2      Column3
-------------------------------------------------------
1       PersonA       10.1         John Doe     Single
2       PersonA       10.1         John Doe     Single
3       PersonA       10.1         John Doe     Married
4       PersonB       09.2         Sully        Single
5       PersonB       09.2         Sullivan     Single

在这种情况下,PersonA 有 3 个版本,PersonB 有 2 个版本。 Column1 的值在所有版本的 Person 中始终相同,我们可以将该列移动到表 Worker。但是 Column 2 和 Column3 的值会随着 Person 的不同版本而变化,因此应该将这些值移动到 Person 表中。

没想到,我有大约 10 个这样的表需要标准化,每个表大约有 40 列。每个表包含大约 500k 到 5m 行。

我需要一个脚本来帮助我分析将哪些列移动到哪里。我需要一个脚本来输出在整个表中唯一 Person 范围内发生变化的所有列。我不知道如何做到这一点。我尝试了 LAG 分析函数来与下一行进行比较,但究竟如何输出更改的列却超出了我的范围。

请指教。

最好的祝愿, 安德鲁

【问题讨论】:

【参考方案1】:

谢谢,但我通过让 Excel 在表架构信息上创建一系列选择来解决了这个问题。它生成的最终查询是一长串选择,但它可以工作(尽管它运行了一个多小时)。 “核心查询”(实际上是 Excel 中创建到核心查询的公式):

=IF(AND(C17<>"CLOB";C17<>"NCLOB");"SELECT '"&A17&".'||initcap('"&B17&"') description,
decode(count(*),0,'SAME OVE VERSIONS','DIFFERENT OVER VERSIONS') values FROM (SELECT 
objektid, count(DISTINCT nvl("&B17&","&IF(C17="DATE";"'01.02.0004'";IF(C17="VARCHAR2"
;"'!#¤¤%¤(%#¤%AS'";"-1234561"))&")) OVER (PARTITION BY objectid) arv FROM "&A17&") 
WHERE number > 1 union all";"SELECT '"&A17&".'||initcap('"&B17&"') description, 'CLOB
field' values from dual union all")

【讨论】:

【参考方案2】:

由于 10 个表并不多,这里是(某种)伪代码

for each table_name in tables
  for each column_name in columns
    case (exists (select 1
          from table_name
          group by PersonID
          having min(column_name) = max(column_name))
       when true then 'Worker'
       when false then 'Person'
    end case
  end for
end for

使用信息架构和动态查询,您可以进行上述正确的 PL/SQL 或获取核心查询并用您喜欢的语言编写脚本。

编辑: 以上假设column_name 中没有NULLs。

EDIT2: 核心查询的其他变体可以是

SELECT 1
FROM 
(SELECT COUNT(DISTINCT column_name) AS distinct_values_by_pid
FROM table_name
GROUP BY PersonID) T
HAVING MIN(distinct_values_by_pid) = MAX(distinct_values_by_pid)

如果每个 PersonID 的所有值都相同,它将返回一行。 (这个查询也有 NULLS 的问题,但我认为 NULLs 是一个单独的问题;出于上述查询的目的,您始终可以将 NULL 强制转换为某个域外值)

上面的查询也可以写成

SELECT MIN(c1)=MAX(c1), MIN(c2)=MAX(c2), ...
FROM 
(SELECT COUNT(DISTINCT column_name_1) AS c1, COUNT(DISTINCT column_name_2) AS c2, ...
FROM table_name
GROUP BY PersonID) T

这将同时测试多个列,对于属于“Workers”的列返回 true,对于应该进入“Persons”的列返回 false。

【讨论】:

以上是关于规范化表:在一系列行中查找唯一列 (Oracle 10.x)的主要内容,如果未能解决你的问题,请参考以下文章

如何根据列和行中提供的条件查找表格单元格值?

查找表中每个 ID 的最大连续年份(Oracle SQL)

ORACLE: 查询(看)表的主键外键唯一性约束和索引

Oracle中用Rowid查找和删除重复记录!

SQL Server 在特定列的所有行中查找和替换特定单词

查找列是不是具有唯一约束