防止 sqlplus 截断列名,没有单独的列格式

Posted

技术标签:

【中文标题】防止 sqlplus 截断列名,没有单独的列格式【英文标题】:Preventing sqlplus truncation of column names, without individual column formatting 【发布时间】:2008-12-09 02:59:51 【问题描述】:

默认情况下,sqlplus 将列名截断为基础数据类型的长度。我们数据库中的许多列名都以表名作为前缀,因此在截断时看起来相同。

我需要在锁定的生产环境中为远程 DBA 指定 select * 查询,并拖回假脱机结果进行诊断。列太多,无法指定单独的列格式。 sqlplus 是否提供任何选项来统一消除列名截断?

(我正在使用 SET MARKUP html ON,但我可以使用其他一些模态,csv 等,只要它产生未缩写的输出。)

【问题讨论】:

【参考方案1】:

您可以尝试的一件事是动态生成“列 x 格式 a20”命令。类似于以下内容:

set termout off
set feedback off

spool t1.sql
select 'column ' || column_name || ' format a' || data_length
from all_tab_cols
where table_name='YOUR_TABLE'
/
spool off

@t1.sql
set pagesize 24
set heading on
spool result.txt
select * 
from  YOUR_TABLE;
and   rownum < 30;
spool off

请注意,此示例仅适用于 VARCHAR2。例如,您需要添加 decode 来更改为 DATE 或 NUMBER 生成的“列”命令。

更新:原来的 SQL 并没有真正改变 SQL*Plus 的行为。我唯一能想到的是将字段名称重命名为一个字符值 A、B、C 等,方法如下:

select 'column ' || column_name ||
       ' heading "' ||
       chr(ascii('A') - 1 + column_id) ||
       '"'
from all_tab_cols
where table_name='YOUR_TAB_NAME'

它会产生类似的输出:

column DEPT_NO heading "A"
column NAME heading "B"
column SUPERVIS_ID heading "C"
column ADD_DATE heading "D"
column REPORT_TYPE heading "E"

【讨论】:

嗯,有点笨拙,但我想这可能会奏效。除了我不认为你的意思是data_length。这不只是基础数据类型的长度,因此会导致默认的 sqlplus 行为吗?是否有可以应用于列名的字符串长度操作? 克里斯,你是对的。我刚刚意识到它只影响值的格式,而不影响标题的格式。选择声明有点不同的方法在帖子的更新中。 所以我最终会向我显示标记为 A、B、C 等的列...但我的目标是输出显示原始列名,未截断,以便我可以知道哪一列是哪个。 知道了,这行得通:选择'column' ||列名 || '格式a' || best(length(column_name), data_length) from all_tab_cols where table_name='YOUR_TAB_NAME' 如果您在其中进行编辑,我会将您的答案标记为已接受。谢谢! 呃,我之前评论中的代码几乎可以正常工作。但 data_length 与格式化输出长度不同,因此当列名短于 9 个字符时,例如日期不会获得足够的空间,例如:11-DEC-08。不知道应该如何处理......【参考方案2】:

建议的解决方案都不能显示原始列名,所以我不确定人们为什么要投票...我确实有一个适用于原始请求的“hack”,但我真的不t 喜欢它...也就是说,您实际上是在每列的查询中附加或前缀一个字符串,因此它们对于列标题总是足够长。如果您在 HTML 模式下,如海报所示,额外的空白间距几乎没有什么害处...它当然会减慢您的查询速度...

例如

SET ECHO OFF
SET PAGESIZE 32766
SET LINESIZE 32766
SET NUMW 20
SET VERIFY OFF
SET TERM OFF
SET UNDERLINE OFF
SET MARKUP HTML ON
SET PREFORMAT ON
SET WORD_WRAP ON
SET WRAP ON
SET ENTMAP ON
spool '/tmp/Example.html'
select 
   (s.ID||'                  ') AS ID,
   (s.ORDER_ID||'                  ') AS ORDER_ID,
   (s.ORDER_NUMBER||'                  ') AS ORDER_NUMBER,
   (s.CONTRACT_ID||'                  ') AS CONTRACT_ID,
   (s.CONTRACT_NUMBER||'                  ') AS CONTRACT_NUMBER,
   (s.CONTRACT_START_DATE||'                  ') AS CONTRACT_START_DATE,
   (s.CONTRACT_END_DATE||'                  ') AS CONTRACT_END_DATE,
   (s.CURRENCY_ISO_CODE||'                  ') AS CURRENCY_ISO_CODE,
from Example s
order  by s.order_number, s.contract_number;
spool off;

当然,您可以编写一个存储过程来做一些更好的事情,但对于这个简单的场景来说,这似乎有点矫枉过正。

这仍然不符合原始发帖人的要求。因为它需要在列上手动列出,而不是使用 select *。但至少当您愿意详细说明字段时,它是有效的解决方案。

但是,由于 HTML 中的字段过长确实没有问题,因此有一种相当简单的方法可以修复 Chris 的解决方案以使其在此示例中运行。那只是选择使用oracle允许的最大值。遗憾的是,这仍然不适用于每个表的每个字段,除非您为每种数据类型显式添加格式。此解决方案也不适用于连接,因为不同的表可以使用相同的列名但不同的数据类型。

SET ECHO OFF
SET TERMOUT OFF
SET FEEDBACK OFF
SET PAGESIZE 32766
SET LINESIZE 32766
SET MARKUP HTML OFF
SET HEADING OFF

spool /tmp/columns_EXAMPLE.sql
select 'column ' || column_name || ' format A32766' 
from all_tab_cols
where data_type = 'VARCHAR2' and table_name = 'EXAMPLE'
/
spool off

SET HEADING ON
SET NUMW 40
SET VERIFY OFF
SET TERM OFF
SET UNDERLINE OFF
SET MARKUP HTML ON
SET PREFORMAT ON
SET WORD_WRAP ON
SET WRAP ON
SET ENTMAP ON
@/tmp/columns_EXAMPLE.sql
spool '/tmp/Example.html'
select *
from Example s
order  by s.order_number, s.contract_number;
spool off;

【讨论】:

【参考方案3】:

这应该提供一些合理的格式。 当然,您可以随意用自己的偏好替换 char 列的最大宽度,以及如何处理 LONG、RAW 和 LOB 列。

SELECT 'COLUMN ' || column_name || ' FORMAT ' ||
       CASE
          WHEN data_type = 'DATE' THEN
           'A9'
          WHEN data_type LIKE '%CHAR%' THEN
           'A' ||
           TRIM(TO_CHAR(LEAST(GREATEST(LENGTH(column_name),
                        data_length), 40))) ||
           CASE
              WHEN data_length > 40 THEN
               ' TRUNC'
              ELSE
               NULL
           END
          WHEN data_type = 'NUMBER' THEN
           LPAD('0', GREATEST(LENGTH(column_name),
           NVL(data_precision, data_length)), '9') ||
           DECODE(data_scale, 0, NULL, NULL, NULL, '.' ||
           LPAD('0', data_scale, '0'))
          WHEN data_type IN ('RAW', 'LONG') THEN
           'A1 NOPRINT'
          WHEN data_type LIKE '%LOB' THEN
           'A1 NOPRINT'
          ELSE
           'A' || TRIM(TO_CHAR(GREATEST(LENGTH(column_name), data_length)))
       END AS format_cols
  FROM dba_tab_columns
 WHERE owner = 'SYS'
   AND table_name = 'DBA_TAB_COLUMNS';

【讨论】:

【参考方案4】:

我不认为 sqlplus 提供您所要求的功能。您可以使用某种脚本语言(例如 Perl 或 Python)来自动格式化。换句话说,查询ALL_TAB_COLS 视图以获取架构和表,然后使用格式列属性动态创建脚本。当然,这只有在您有权查询 ALL_TAB_COLS 视图(或其他等效视图)时才有效。

这是我拼凑的一个快速概念验证:

#!/usr/bin/python

import sys
import cx_Oracle

response=raw_input("Enter schema.table_name:  ")
(schema, table) = response.split('.')
schema = schema.upper()
table = table.upper()
sqlstr = """select column_name,
                   data_type,
                   data_length
              from all_tab_cols
             where owner      = '%s'
               and table_name = '%s'""" % ( schema, table )

## open a connection to databases...
try:
    oracle = cx_Oracle.Connection( oracleLogin )
    oracle_cursor = oracle.cursor()

except cx_Oracle.DatabaseError, exc:
    print "Cannot connect to Oracle database as", oracleLogin
    print "Oracle Error %d:  %s" % ( exc.args[0].code, exc.args[0].message )
    sys.exit(1)

try:
    oracle_cursor.execute( sqlstr )

    # fetch resultset from cursor
    for column_name, data_type, data_length in oracle_cursor.fetchmany(256):
        data_length = data_length + 0
        if data_length < len(column_name):
            if data_type == "CHAR" or data_type == "VARCHAR2":
                print "column %s format a%d" % ( column_name.upper(), len(column_name) )
            else:
                print "-- Handle %s, %s, %d" % (column_name, data_type, data_length)

except cx_Oracle.DatabaseError, e:
    print "[Oracle Error %d: %s]:  %s" % (e.args[0].code, e.args[0].message, sqlstr)
    sys.exit(1)

try:
    oracle_cursor.close()
    oracle.close()
except cx_Oracle.DatabaseError, exc:
    print "Warning: Oracle Error %d:  %s" % ( exc.args[0].code, exc.args[0].message )

print "select *"
print "from %s.%s" % ( schema, table )

【讨论】:

感谢python代码,不幸的是我们的客户严格禁止在他们的生产环境中安装任何其他软件。甚至没有 grep 或 tail。啊。所以是 sqlplus 或 bust。 发现这对完全无关的原因很有帮助。很高兴你发布了它! :-D【参考方案5】:

如果您不需要或不想要 XML 格式,这有点麻烦,但您应该能够使用 DBMS_XMLGEN package。该脚本应该为您提供一个 XML 文件,用于以完整列名作为标记名的任意查询。

VARIABLE resultXML clob;
SET LONG 100000; -- Set to the maximum size of the XML you want to display (in bytes) 
SET PAGESIZE 0;

DECLARE
   qryCtx DBMS_XMLGEN.ctxHandle;
BEGIN
  qryCtx := dbms_xmlgen.newContext('SELECT * from scott.emp');

  -- now get the result
  :resultXML := DBMS_XMLGEN.getXML(qryCtx);

  --close context
  DBMS_XMLGEN.closeContext(qryCtx);
END;
/

print resultXML

【讨论】:

【参考方案6】:

我在尝试在VoraX 中实现此功能时遇到了同样的问题。在下一个版本中,我想到了以下解决方案:

set feedback off 
set serveroutput on
declare
  l_c number;
  l_col_cnt number;
  l_rec_tab DBMS_SQL.DESC_TAB2;
  l_col_metadata DBMS_SQL.DESC_REC2;
  l_col_num number;
begin
  l_c := dbms_sql.open_cursor;
  dbms_sql.parse(l_c, '<YOUR QUERY HERE>', DBMS_SQL.NATIVE);
  DBMS_SQL.DESCRIBE_COLUMNS2(l_c, l_col_cnt, l_rec_tab);
  for colidx in l_rec_tab.first .. l_rec_tab.last loop
    l_col_metadata := l_rec_tab(colidx);
    dbms_output.put_line('column ' || l_col_metadata.col_name || ' heading ' || l_col_metadata.col_name);
  end loop;
  DBMS_SQL.CLOSE_CURSOR(l_c);
end;

无需调整列大小、格式和内容,只需使用您想要的列名称强制列标题。我认为同样的方法也适用于 DBA_TAB_COLUMNS 解决方案,但我更喜欢 DBMS_SQL 解决方案,因为它也考虑别名并且只获取您查询的列。

编辑:仅使用“列标题”是行不通的。仍然需要使用“列格式”语句。所以,请忽略我之前的回答。

【讨论】:

以上是关于防止 sqlplus 截断列名,没有单独的列格式的主要内容,如果未能解决你的问题,请参考以下文章

SQL 字符串或二进制数据将被截断,列名

Oracle sqlplus 语法

1265 数据在几个值后截断第 1 行的列

在 sqlplus 中执行立即截断表

sql数据库insert into values的数据格式是怎么规定的?

从包含来自单独列的数据的列中选择数据