<think>我们面对的是一个关于Kingbase8数据库的问题:查询所有表中字段定义为非空(NOT NULL)但实际上数据全为空的表和字段信息。
根据引用[2]:统计信息中记录了列的信息,包括列上的空值。但统计信息可能不是实时更新的,所以我们不能完全依赖统计信息。
我们需要查询数据字典(系统表)来获取表结构信息(字段是否非空),然后通过查询数据来验证这些非空字段是否实际为空。
步骤:
1. 查询所有用户表(排除系统表)。
2. 对于每个表,查询其所有定义为非空(NOT NULL)的字段。
3. 对于每个这样的字段,检查表中该字段是否全部为NULL(即空值)。注意:因为字段定义为非空,所以理论上不应该有NULL,但可能存在数据不一致的情况。
然而,注意引用[4]中提到的模式(schema)问题,Kingbase中表可能位于不同的模式下,所以我们需要考虑模式。
具体步骤:
1. 查询所有用户表(指定模式,比如public模式,或者所有用户模式):
SELECT schemaname, tablename FROM sys_catalog.sys_tables WHERE ... (需要过滤系统表)
但Kingbase基于PostgreSQL,所以我们可以参考PostgreSQL的系统表。在Kingbase中,通常使用`sys_class`、`sys_attribute`等系统表(类似于pg_class, pg_attribute)。
然而,根据引用[2]和[4],Kingbase的系统表结构可能与PostgreSQL类似。我们可以这样:
- 获取所有用户表:从`sys_tables`(或`sys_class`,其中relkind='r'表示普通表)中获取。
- 获取字段信息:从`sys_attribute`和`sys_constraint`(用于非空约束)?或者更直接的是,我们可以通过`information_schema.columns`视图(Kingbase也支持)来获取。
考虑到兼容性和可读性,使用`information_schema`可能更标准。
在`information_schema.columns`视图中,有一个列`is_nullable`,如果为'NO',则表示该列不允许为空(即NOT NULL约束)。
因此,我们可以先获取所有不允许为空的列:
查询1:获取所有用户表的不允许为空的列
SELECT table_schema, table_name, column_name
FROM information_schema.columns
WHERE table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema') -- 排除系统模式
AND is_nullable = 'NO'
但是,这里有一个问题:Kingbase中可能还有其他系统模式?根据引用[4],我们看到有test=#,以及模式名为user1(后改为schema1)等。所以我们需要排除已知的系统模式。
2. 然后,对于上面查询得到的每一列,我们需要执行一个查询:检查该表中该列是否全部为NULL(即空值)。如果全部为NULL,则说明违反了非空约束(数据为空)。
注意:由于表可能很大,逐个查询可能会很慢,但这是必要的。
我们可以动态生成SQL语句来检查每个字段。例如,对于表`sch.tbl`和字段`col`,执行:
SELECT COUNT(*) FROM sch.tbl WHERE col IS NOT NULL
如果这个计数为0,则说明该列所有值都是NULL(或者表为空,但表为空时,非空字段也应该没有数据,所以也是违反的?因为非空字段要求至少有一个非空值,但表为空时,没有行,所以没有值,这并不违反非空约束?)
因此,我们需要区分:
- 表为空:那么非空字段没有值,这并不违反非空约束(因为非空约束是在插入数据时检查的,空表没有插入任何数据,所以没有违反)。
- 表不为空,但该列所有值都是NULL:这违反了非空约束。
所以,我们不仅要检查该列是否全为NULL,还要检查表是否为空。但是,如果表为空,我们就不需要报告了,因为非空约束没有被违反。
因此,检查条件应该是:表不为空且该列所有值都是NULL。
所以,对于每个表,我们检查两个条件:
a. 表中有数据(行数>0)
b. 该列的值全部为NULL(即该列中非NULL的个数为0)
因此,我们可以这样写:
SELECT 1 FROM sch.tbl HAVING COUNT(*) > 0 AND COUNT(col) = 0
解释:COUNT(col) 统计非NULL值的数量。如果为0,且COUNT(*)>0(表示表中有行),则说明该列在每一行都是NULL。
3. 将步骤1和步骤2结合起来,我们可以使用动态SQL(因为要遍历所有表),在Kingbase中,我们可以使用PL/SQL(类似于PL/pgSQL)来编写存储过程。
但是,用户可能只需要一个查询语句?我们可以尝试用一条SQL语句,但可能需要使用集合操作或者CTE递归,这样可能很复杂,而且效率可能不高。
因此,我们选择编写一个存储过程来生成报告。
由于用户要求的是查询,我们可以提供两种方式:
- 方式1:编写一个存储过程,返回所有满足条件的表名和字段名。
- 方式2:生成一个包含多个查询的脚本,用户执行后可以逐个检查。
这里,我们采用方式1:存储过程。
注意:引用[5]中出现了错误,提示我们注意函数的使用(如json_array_length)需要正确类型。所以我们在动态SQL中要确保表名和列名的正确引用。
存储过程思路:
- 创建一个临时表来存储结果(模式名,表名,列名)。
- 遍历步骤1中查询到的每一个非空列。
- 对于每个非空列,构建一个动态SQL语句,检查该列是否全为NULL(且表不为空)。
- 如果满足条件,则将信息插入临时表。
- 返回临时表的内容。
但是,由于Kingbase的PL/SQL语法与Oracle或PostgreSQL类似,我们参考PostgreSQL的语法。
由于存储过程编写相对复杂,且用户可能只需要一次性的查询,我们可以提供一个生成动态SQL查询语句的脚本,然后让用户执行这些语句来查看结果。
为了简化,我们也可以生成一个包含多个SELECT语句的脚本,每个SELECT语句检查一个字段,然后使用UNION ALL将所有结果合并。但是,由于字段可能很多,生成的SQL可能很长。
我们选择生成一个脚本,该脚本会生成一系列检查语句,并将结果插入到临时表中,最后查询临时表。
步骤:
1. 创建一个临时表来存储结果(schema_name, table_name, column_name)。
2. 使用游标遍历所有非空列。
3. 对于每个非空列,执行动态SQL检查,如果满足条件(表不为空且该列全为NULL),则插入临时表。
4. 返回临时表的内容。
但是,由于在Kingbase中执行动态SQL需要权限,并且存储过程可能需要创建,我们提供一个存储过程示例。
考虑到用户可能没有权限创建存储过程,我们可以提供一个生成动态SQL文本的查询,用户可以将生成的SQL文本复制出来执行。
因此,这里我们提供两种方案:
方案一:生成动态SQL文本(适用于不想创建存储过程的用户)
生成一个包含多个SELECT语句的脚本,每个SELECT语句检查一个字段,并将结果UNION起来。但是,如果字段很多,这个脚本会很长。
方案二:编写一个存储过程(推荐,但需要用户有创建存储过程的权限)
我们先尝试方案一(生成动态SQL文本):
查询1:获取所有非空列(表模式和表名以及列名),然后为每个非空列生成一个查询片段:
SELECT 'SELECT ''' || c.table_schema || ''' AS schema_name, ''' || c.table_name || ''' AS table_name, ''' || c.column_name || ''' AS column_name FROM ' ||
quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' HAVING COUNT(*) > 0 AND COUNT(' || quote_ident(c.column_name) || ') = 0'
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO'
注意:这里我们使用了quote_ident函数来避免表名或列名是关键字或包含特殊字符。
但是,这样生成的每个查询片段返回的是符合条件的表的一行(如果该字段全空且表非空,则返回一行,否则不返回)。然后我们把这些查询用UNION ALL连接起来。
所以,完整的生成脚本:
SELECT string_agg(
'SELECT ''' || c.table_schema || ''' AS schema_name, ''' || c.table_name || ''' AS table_name, ''' || c.column_name || ''' AS column_name FROM ' ||
quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' HAVING COUNT(*) > 0 AND COUNT(' || quote_ident(c.column_name) || ') = 0',
' UNION ALL '
) AS sql_statement
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO';
但是,这样生成的字符串可能会很长(如果有很多非空列),而且Kingbase的string_agg可能有长度限制。
因此,我们可以不聚合,而是直接输出多个SQL语句,中间用UNION ALL连接,然后用户复制执行。
SELECT 'SELECT ''' || c.table_schema || ''' AS schema_name, ''' || c.table_name || ''' AS table_name, ''' || c.column_name || ''' AS column_name FROM ' ||
quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' HAVING COUNT(*) > 0 AND COUNT(' || quote_ident(c.column_name) || ') = 0 UNION ALL'
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO'
注意:最后会多一个UNION ALL,需要用户手动去掉最后一个UNION ALL,并加上分号。
但是,这样不太友好。我们可以用以下方式避免:
SELECT 'SELECT ''' || c.table_schema || ''' AS schema_name, ''' || c.table_name || ''' AS table_name, ''' || c.column_name || ''' AS column_name FROM ' ||
quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' HAVING COUNT(*) > 0 AND COUNT(' || quote_ident(c.column_name) || ') = 0'
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO';
然后用户将每个查询用UNION ALL连接起来执行。
但是,如果表很大,执行这么多查询可能会很慢。
方案二:存储过程(推荐)
存储过程代码(Kingbase PL/SQL):
注意:Kingbase的PL/SQL语法与Oracle类似。
我们创建一个函数,返回一个包含(schema_name, table_name, column_name)的结果集。
步骤:
1. 创建一个临时表(或者使用返回表)来存储结果。
2. 使用游标遍历所有非空列。
3. 对于每个非空列,构建动态查询,检查是否满足条件(表中有行且该列全为NULL)。
4. 如果满足,则插入结果表。
5. 返回结果。
具体代码:
```sql
CREATE OR REPLACE FUNCTION find_not_null_columns_with_all_null()
RETURNS TABLE (schema_name text, table_name text, column_name text) AS $$
DECLARE
rec record;
v_sql text;
v_count integer;
BEGIN
-- 临时表不需要,因为我们可以使用RETURN QUERY,但这里我们使用循环动态执行
FOR rec IN
SELECT c.table_schema, c.table_name, c.column_name
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO'
LOOP
-- 构建动态SQL:检查表中是否有行,并且该列的非空计数为0
v_sql := 'SELECT COUNT(*) FROM ' || quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) ||
' HAVING COUNT(*) > 0 AND COUNT(' || quote_ident(rec.column_name) || ') = 0';
-- 执行动态SQL,如果返回一行,则说明满足条件
EXECUTE v_sql INTO v_count;
-- 注意:动态SQL返回的是满足条件的表的数量(即HAVING子句过滤后,如果满足条件则返回一行,否则不返回)
-- 所以如果v_count不为空(即返回了一行),则说明该列有问题
IF v_count IS NOT NULL THEN
schema_name := rec.table_schema;
table_name := rec.table_name;
column_name := rec.column_name;
RETURN NEXT;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
```
注意:由于Kingbase可能使用不同的过程语言,这里我们使用plpgsql(因为Kingbase兼容PostgreSQL)。
但是,引用[1]中出现了错误,可能涉及编码问题,所以用户需要注意数据库连接编码。另外,引用[3]提到人大金仓的依赖,说明用户可能是通过JDBC连接,所以存储过程需要在数据库中创建。
用户执行:
SELECT * FROM find_not_null_columns_with_all_null();
即可得到结果。
但是,注意:如果表非常大,动态执行COUNT(*)可能会慢。我们可以考虑只检查是否存在至少一行且该列全空,而不需要精确的行数,这样可以使用EXISTS:
v_sql := 'SELECT 1 FROM ' || quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) ||
' WHERE ' || quote_ident(rec.column_name) || ' IS NOT NULL LIMIT 1';
-- 然后检查是否存在非空值,如果没有非空值,则说明全空(但还要判断表不为空)
-- 但是,这样需要两个查询:先判断表是否为空,再判断该列是否有非空值。
我们原来的方法(一个查询)效率已经相对较高,因为它只需要一次全表扫描(但COUNT(*)可能扫描整个表)。
优化:我们可以先判断表是否为空,如果为空,则跳过。因为空表不违反非空约束。
先判断表行数:
v_sql1 := 'SELECT 1 FROM ' || quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) || ' LIMIT 1';
EXECUTE v_sql1 INTO v_exists;
IF v_exists IS NOT NULL THEN -- 表不为空
-- 然后检查该列是否有非空值
v_sql2 := 'SELECT 1 FROM ' || quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) ||
' WHERE ' || quote_ident(rec.column_name) || ' IS NOT NULL LIMIT 1';
EXECUTE v_sql2 INTO v_exists;
-- 如果v_exists为空,说明没有非空值,即全为空
IF v_exists IS NULL THEN
-- 记录结果
END IF;
END IF;
这样,如果表为空,我们就跳过,避免第二次查询。而且,第一次查询只需要取一行,所以很快。
另外,在检查非空值时,只要找到一行非空值,就可以停止(LIMIT 1)。所以,如果表中有大量数据,但第一行该列就是非空,那么就会很快返回。
因此,我们优化后的存储过程:
```sql
CREATE OR REPLACE FUNCTION find_not_null_columns_with_all_null()
RETURNS TABLE (schema_name text, table_name text, column_name text) AS $$
DECLARE
rec record;
v_exists integer;
BEGIN
FOR rec IN
SELECT c.table_schema, c.table_name, c.column_name
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO'
LOOP
-- 检查表是否为空
EXECUTE 'SELECT 1 FROM ' || quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) || ' LIMIT 1'
INTO v_exists;
IF v_exists IS NOT NULL THEN -- 表不为空
-- 检查该列是否有非空值(只要有一行非空,就跳过)
EXECUTE 'SELECT 1 FROM ' || quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) ||
' WHERE ' || quote_ident(rec.column_name) || ' IS NOT NULL LIMIT 1'
INTO v_exists;
-- 如果v_exists为NULL,说明没有找到非空值
IF v_exists IS NULL THEN
schema_name := rec.table_schema;
table_name := rec.table_name;
column_name := rec.column_name;
RETURN NEXT;
END IF;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
```
注意:这里我们使用了同一个变量v_exists,在第二次查询后,如果没找到非空值,则v_exists会被设置为NULL(因为SELECT INTO没有返回行)。
使用这个存储过程,效率会更高。
总结:用户可以根据自己的情况选择使用存储过程或生成动态SQL。
由于用户问题中引用了人大金仓的异常(引用[1]),可能用户遇到了数据不一致的问题,这个查询可以帮助用户发现违反非空约束的列(这些列应该被修复)。
另外,引用[2]提到统计信息,但统计信息可能不准确(特别是没有及时更新统计信息时),所以我们直接查询数据。
最后,我们提供存储过程的方式作为推荐方案。
注意:在Kingbase中,系统模式可能有不同的命名,用户需要根据实际情况调整排除的模式(例如,可能还有'public'模式?但public是用户模式,不需要排除)。我们这里排除了常见的系统模式。
如果用户没有创建存储过程的权限,则只能使用生成动态SQL文本的方式。
因此,我们提供两种方法。
方法1:存储过程(高效,推荐)
创建上述存储过程,然后执行:
SELECT * FROM find_not_null_columns_with_all_null();
方法2:生成动态SQL(无需创建存储过程)
执行以下查询生成检查语句(用户需要复制结果并执行):
SELECT 'SELECT ''' || c.table_schema || ''' AS schema_name, ''' || c.table_name || ''' AS table_name, ''' || c.column_name || ''' AS column_name FROM ' ||
quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' WHERE EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) || ' LIMIT 1) ' ||
' AND NOT EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' WHERE ' || quote_ident(c.column_name) || ' IS NOT NULL LIMIT 1) '
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO';
这个动态SQL片段会生成一个查询,该查询在满足条件时返回一行。然后用户将所有这些查询用UNION ALL连接起来。
但是,这样生成的每个查询片段已经是一个完整的查询,可以单独执行。为了合并结果,我们生成:
SELECT 'SELECT ''' || c.table_schema || ''' AS schema_name, ''' || c.table_name || ''' AS table_name, ''' || c.column_name || ''' AS column_name ' ||
' WHERE EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) || ' LIMIT 1) ' ||
' AND NOT EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' WHERE ' || quote_ident(c.column_name) || ' IS NOT NULL LIMIT 1) UNION ALL'
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO';
然后用户去掉最后一个UNION ALL,并加上分号。但这样生成的语句可能不太容易处理。
我们可以生成一个完整的SQL脚本:
SELECT 'CREATE TEMPORARY TABLE temp_result AS '; -- 先创建临时表
-- 然后生成每个查询并插入临时表
SELECT 'INSERT INTO temp_result SELECT ''' || c.table_schema || ''', ''' || c.table_name || ''', ''' || c.column_name || ''' ' ||
' WHERE EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) || ' LIMIT 1) ' ||
' AND NOT EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' WHERE ' || quote_ident(c.column_name) || ' IS NOT NULL LIMIT 1);'
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO';
-- 最后加上查询临时表的语句
SELECT 'SELECT * FROM temp_result;';
这样,用户需要先执行创建临时表,然后执行一系列INSERT,最后查询临时表。
但是,创建临时表需要先定义结构。我们可以这样:
SELECT 'CREATE TEMPORARY TABLE temp_result (schema_name text, table_name text, column_name text);';
-- 然后生成INSERT语句
然后,将所有这些语句按顺序输出。
因此,完整的生成脚本:
SELECT 'CREATE TEMPORARY TABLE temp_result (schema_name text, table_name text, column_name text);' AS sql_statement
UNION ALL
SELECT 'INSERT INTO temp_result SELECT ''' || c.table_schema || ''', ''' || c.table_name || ''', ''' || c.column_name || ''' ' ||
' WHERE EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) || ' LIMIT 1) ' ||
' AND NOT EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' WHERE ' || quote_ident(c.column_name) || ' IS NOT NULL LIMIT 1);' AS sql_statement
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO'
UNION ALL
SELECT 'SELECT * FROM temp_result;';
这样,用户执行这个生成脚本,会得到一系列SQL语句,然后复制这些语句(按顺序)执行即可。
注意:第一个语句是创建临时表,然后是一系列INSERT,最后是查询。
由于生成的是多个独立的SQL语句,用户需要一次性全部执行(或者按顺序执行)。
我们选择存储过程作为主要推荐,因为更高效和方便。如果用户没有权限,再使用动态SQL脚本。
最后,我们还需要注意:Kingbase可能有一些特殊的系统表,我们排除的模式可能需要根据实际情况调整。
另外,字段名和表名的大小写:如果创建时使用了双引号(大小写敏感),那么我们在quote_ident时会加上双引号,所以是安全的。
根据引用[4]中,表名和模式名都是小写,所以应该没有问题。
综上所述,我们提供两种方法。
由于用户问题中要求查询所有表中字段为非空但数据为空的表和字段信息,我们给出了解决方案。
相关问题:
1. 如何修复这些非空约束字段为空的问题?
2. 为什么会出现非空约束字段为空的情况?
3. 如何避免非空约束字段为空的情况?
我们可以在回答中提供一些建议:
- 出现这种情况可能是因为数据导入时没有检查,或者程序逻辑错误导致插入了空值(尽管有非空约束,但可能通过绕过约束的方式插入,比如禁用约束?)。
- 修复:需要根据业务逻辑为这些字段设置默认值,或者删除这些行,或者更新这些字段的值。
- 避免:在应用层和数据库层加强约束检查,确保插入和更新数据时非空字段不为空。
但用户的问题主要是查询,所以修复和避免作为相关问题。
因此,回答如下:</think>### 查询 Kingbase8 中非空字段但数据为空的表和字段信息
要查询 Kingbase8 数据库中所有**字段定义为非空(NOT NULL)但实际数据全为空**的表和字段信息,需结合数据字典和动态数据检查。以下是两种解决方案:
---
#### **方法 1:使用存储过程(推荐,高效)**
创建存储过程遍历所有非空字段,动态检查数据是否全为空:
```sql
CREATE OR REPLACE FUNCTION find_not_null_columns_with_all_null()
RETURNS TABLE (schema_name text, table_name text, column_name text) AS $$
DECLARE
rec record;
v_exists integer;
BEGIN
FOR rec IN
SELECT c.table_schema, c.table_name, c.column_name
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO' -- 过滤非空字段
LOOP
-- 检查表是否非空
EXECUTE 'SELECT 1 FROM ' ||
quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) ||
' LIMIT 1'
INTO v_exists;
IF v_exists IS NOT NULL THEN -- 表中有数据
-- 检查字段是否全为空
EXECUTE 'SELECT 1 FROM ' ||
quote_ident(rec.table_schema) || '.' || quote_ident(rec.table_name) ||
' WHERE ' || quote_ident(rec.column_name) || ' IS NOT NULL LIMIT 1'
INTO v_exists;
-- 若无非空值,则记录结果
IF v_exists IS NULL THEN
schema_name := rec.table_schema;
table_name := rec.table_name;
column_name := rec.column_name;
RETURN NEXT;
END IF;
END IF;
END LOOP;
END;
$$ LANGUAGE plpgsql;
```
**执行查询**:
```sql
SELECT * FROM find_not_null_columns_with_all_null();
```
**输出示例**:
```
schema_name | table_name | column_name
-------------+------------+-------------
public | employees | phone -- 表employees的phone字段定义为NOT NULL但实际全为空
sales | orders | discount -- 表orders的discount字段违反非空约束
```
---
#### **方法 2:动态生成检查脚本(无需创建存储过程)**
生成 SQL 脚本手动执行,适合无权限创建存储过程的情况:
```sql
-- 步骤1:生成临时表创建语句
SELECT 'CREATE TEMPORARY TABLE temp_result (schema_name text, table_name text, column_name text);' AS sql_statement
UNION ALL
-- 步骤2:为每个非空字段生成检查语句
SELECT 'INSERT INTO temp_result SELECT ''' || c.table_schema || ''', ''' || c.table_name || ''', ''' || c.column_name || '''
WHERE EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) || ' LIMIT 1)
AND NOT EXISTS (SELECT 1 FROM ' || quote_ident(c.table_schema) || '.' || quote_ident(c.table_name) ||
' WHERE ' || quote_ident(c.column_name) || ' IS NOT NULL LIMIT 1);' AS sql_statement
FROM information_schema.columns c
WHERE c.table_schema NOT IN ('sys_catalog', 'pg_catalog', 'information_schema')
AND c.is_nullable = 'NO'
UNION ALL
-- 步骤3:生成结果查询语句
SELECT 'SELECT * FROM temp_result;';
```
**操作步骤**:
1. 执行上述 SQL,获取生成的脚本(多条语句)。
2. 复制生成的完整脚本,在数据库中执行。
3. 结果将保存在临时表 `temp_result` 中。
---
### **关键说明**
1. **过滤系统表**:
- 排除 `sys_catalog`, `pg_catalog`, `information_schema` 等系统模式[^2][^4]。
2. **非空约束检查**:
- `information_schema.columns.is_nullable = 'NO'` 标识字段定义为非空[^2]。
3. **数据为空判定**:
- 表中有数据(`COUNT(*) > 0`)且字段无任何非空值(`COUNT(column) = 0`)[^2]。
4. **安全性**:
- 使用 `quote_ident()` 处理特殊字符(如大写表名)[^4]。
---
### **可能原因与修复建议**
- **原因**:
数据导入错误、程序逻辑漏洞或约束被绕过导致字段全为空[^1]。
- **修复**:
```sql
-- 示例:清理无效数据(需根据业务逻辑调整)
DELETE FROM public.employees WHERE phone IS NULL;
-- 或填充默认值
UPDATE sales.orders SET discount = 0 WHERE discount IS NULL;
```
---
### 相关问题
1. **如何防止非空字段出现空值?**
- 建议:应用层校验 + 数据库触发器 + 严格约束[^2][^4]。
2. **Kingbase8 中如何批量修改非空字段的默认值?**
- 示例:`ALTER TABLE table_name ALTER COLUMN column_name SET DEFAULT value;`
3. **如何监控数据库约束违规情况?**
- 方案:定期执行此类检查脚本,或使用 `sys_constraint` 系统表[^2][^4]。
[^1]: 数据不一致可能由程序错误或约束绕过导致,需结合业务逻辑修复。
[^2]: 数据字典记录字段约束和统计信息(如空值分布),但需实时验证数据[^2]。
[^4]: 模式(Schema)操作需注意依赖对象,删除前需解除依赖[^4]。