从 SELECT 生成 WITH clause/UNIONs
Generate a WITH clause/UNIONs from a SELECT
我想生成一个 WITH clause/UNIONs — 目的是轻松共享 small 数据样本(10- 20 行)。
我想在不创建 table 或插入行的情况下执行此操作。
示例:
进行 table 或这样的查询:
...并生成这个:
with cte as(
select 10 as asset_id, 1 as vertex_num, 118.56 as x, 3.8 as y from dual
union all
select 10 as asset_id, 2 as vertex_num, 118.62 as x, 1.03 as y from dual
union all
select 10 as asset_id, 3 as vertex_num, 121.93 as x, 1.03 as y from dual)
--There are lots more rows. But it's too much work to write them all out.
select * from cte
使用 SQL,如何从结果集中自动生成 WITH clause/UNIONs?
- 我相信 Toad 中的 OOTB 导出功能可以做到这一点。但是我认为 SQL Developer 中没有任何工具可以做到这一点,这就是我正在使用的。
- 尝试使用 SQL 时,我认为主要的挑战是遍历 n 列。我不确定该怎么做。
您可以使用 dbms_sql
to execute a query against your real table, interrogate the data types,并使用该信息生成 CTE 及其内部查询。
作为第一次尝试:
create or replace procedure print_cte (p_statement varchar2) as
-- dbms_sql variables
l_c pls_integer;
l_col_cnt pls_integer;
l_rows pls_integer;
l_desc_t dbms_sql.desc_tab;
l_first_row boolean := true;
l_varchar2 varchar2(4000);
l_number number;
l_date date;
-- etc.
begin
-- ideally add some checks for p_statement being a sinple query
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c => l_c, statement => p_statement, language_flag => dbms_sql.native);
l_rows := dbms_sql.execute(c => l_c);
dbms_sql.describe_columns(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
-- define columns, and output CTE columns at the same time
dbms_output.put('with cte (');
for i in 1..l_col_cnt loop
case l_desc_t(i).col_type
when 1 then
dbms_sql.define_column(c => l_c, position=> i, column => l_varchar2, column_size => 4000);
when 2 then
dbms_sql.define_column(c => l_c, position=> i, column => l_number);
when 12 then
dbms_sql.define_column(c => l_c, position=> i, column => l_date);
-- etc. plus else to skip or throw error for anything not handled
end case;
if i > 1 then
dbms_output.put(', ');
end if;
dbms_output.put('"' || l_desc_t(i).col_name || '"');
end loop;
dbms_output.put(') as (');
while dbms_sql.fetch_rows(c => l_c) > 0 loop
if (l_first_row) then
l_first_row := false;
else
dbms_output.put(' union all');
end if;
dbms_output.new_line;
for i in 1..l_col_cnt loop
if i = 1 then
dbms_output.put(' select ');
else
dbms_output.put(', ');
end if;
case l_desc_t(i).col_type
when 1 then
dbms_sql.column_value(c => l_c, position => i, value => l_varchar2);
dbms_output.put(q'[']' || l_varchar2 || q'[']');
when 2 then
dbms_sql.column_value(c => l_c, position => i, value => l_number);
dbms_output.put(l_number);
when 12 then
dbms_sql.column_value(c => l_c, position => i, value => l_date);
dbms_output.put(q'[to_date(']'
|| to_char(l_date, 'SYYYY-MM-DD-HH24:MI:SS')
|| q'[', 'SYYYY-MM-DD HH24:MI:SS')]');
-- etc. plus else to skip or throw error for anything not handled
end case;
end loop;
dbms_output.put(' from dual');
dbms_output.new_line;
end loop;
dbms_output.put_line(')');
dbms_output.put_line('select * from cte;');
dbms_sql.close_cursor(c => l_c);
end print_cte;
/
然后你可以做:
begin
print_cte('select * from your_table');
end;
/
产生:
with cte ("ASSET_ID", "VERTEX_NUM", "X", "Y") as (
select 10, 1, 118.56, 3.8 from dual
union all
select 10, 2, 118.62, 1.03 from dual
union all
select 10, 3, 121.93, 1.03 from dual
)
select * from cte;
当然,您的客户端必须配置为处理 dbms_output。
如内联注释中所述,您应该检查 passed-in 语句不会做一些令人讨厌的事情;并且您需要添加对其他数据类型的处理。这只是一个起点。
将 xmltable
或(json_table
for Oracle 12+)用于此类目的会更容易。
示例xmltable
:
- 只需将所有需要的数据汇总到
xmltype
:
你可以使用 xmltype(cursor(select...from...))
:
select xmltype(cursor(select * from test)) xml from dual;
或dbms_xmlgen.getxmltype(query_string)
:
select dbms_xmlgen.getxmltype('select * from test') xml from dual;
- 然后您可以使用返回的 XML 和
xmltable('/ROWSET/ROW' passing xmltype(your_xml) columns ...)
示例:
select *
from xmltable(
'/ROWSET/ROW'
passing xmltype(q'[<?xml version="1.0"?>
<ROWSET>
<ROW>
<ASSET_ID>10</ASSET_ID>
<VERTEX_NUM>1</VERTEX_NUM>
<X>118.56</X>
<Y>3.8</Y>
</ROW>
<ROW>
<ASSET_ID>10</ASSET_ID>
<VERTEX_NUM>2</VERTEX_NUM>
<X>118.62</X>
<Y>1.03</Y>
</ROW>
</ROWSET>
]')
columns
asset_id,vertex_num,x,y
) test
DBFiddle: https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=036b718f2b18df898c3e3de722c97378
上的完整示例
我知道我的回答不纯粹 SQL 或 PLSQL。
但是我建议你使用Javascript生成CTE查询,因为数据量很小。
Javascript 比 SQL 或 PLSQL 更容易维护。
您可以随时在这里使用这个小脚本。 (不需要额外的编辑器)
https://jsfiddle.net/pLvgr8oh/
或者,如果您担心安全问题,您可以 运行 使用 Chrome 浏览器编写脚本。
https://developer.chrome.com/docs/devtools/javascript/snippets/
function convertToSelect(tsv, firstRowIsColumn, columnsComma, typesComma) {
function getCol(column, value, type) {
// In case type is 'date', after colon is date format
const [ typeNew, dateFormat ] = type.split(':')
switch (typeNew) {
case 'string': return `'${value}' as ${column}`
case 'number': return `${value} as ${column}`
case 'date': return `to_date('${value}', '${dateFormat}') as ${column}`
}
}
const columns = columnsComma ? columnsComma.split(',') : []
const types = typesComma ? typesComma.split(',') : []
// Split row by '\r\n' or '\n'
const list = tsv.split(/\r*\n/)
const colCount = list[0].split(/\t/).length
let columnsNew = []
let typesNew = types
// If first row is column name
if (firstRowIsColumn) {
columnsNew = list[0].split(/\t/);
list.shift(0)
}
// If column name is specified then override column names in first row
if (columns.length > 0) {
columnsNew = columns
}
// If type is not specified set all type to 'string'
if (typesNew.length === 0) {
typesNew = [...Array(colCount)].map(t => 'string')
}
const rows = list.map(item => {
// [ '2 as F_INT', '2.223 as F_LONG'... ]
const cols = item
.split(/\t/)
.map((value, index) => {
return getCol(columnsNew[index], value, typesNew[index])
})
.join(', ')
// select 2 as F_INT, 2.223 as F_LONG... from dual
return ` select ${cols} from dual`
})
const selectUnion = rows.join(`
union all
`)
return `with cte as
(
${selectUnion}
)
select * from cte;
`
}
const tsv = `F_INT F_LONG F_VARCHAR F_DATE
1 1.123 a 2022-12-01
2 2.223 b 2022-12-02
3 3.323 c 2022-12-03`
const firstRowIsColumn = true
const columnsComma = 'v_num,v_lng,v_str,v_date'
//const columnsComma = ''
const typesComma = 'number,number,string,date:YYYY-MM-DD'
//const typesComma = ''
const ret = convertToSelect(tsv, firstRowIsColumn, columnsComma, typesComma)
console.log(ret)
生成(如果代码段没有破坏制表符):
with cte as
(
select 1 as v_num, 1.123 as v_lng, 'a' as v_str, to_date('2022-12-01', 'YYYY-MM-DD') as v_date from dual
union all
select 2 as v_num, 2.223 as v_lng, 'b' as v_str, to_date('2022-12-02', 'YYYY-MM-DD') as v_date from dual
union all
select 3 as v_num, 3.323 as v_lng, 'c' as v_str, to_date('2022-12-03', 'YYYY-MM-DD') as v_date from dual
)
select * from cte;
我想生成一个 WITH clause/UNIONs — 目的是轻松共享 small 数据样本(10- 20 行)。
我想在不创建 table 或插入行的情况下执行此操作。
示例:
进行 table 或这样的查询:
...并生成这个:
with cte as(
select 10 as asset_id, 1 as vertex_num, 118.56 as x, 3.8 as y from dual
union all
select 10 as asset_id, 2 as vertex_num, 118.62 as x, 1.03 as y from dual
union all
select 10 as asset_id, 3 as vertex_num, 121.93 as x, 1.03 as y from dual)
--There are lots more rows. But it's too much work to write them all out.
select * from cte
使用 SQL,如何从结果集中自动生成 WITH clause/UNIONs?
- 我相信 Toad 中的 OOTB 导出功能可以做到这一点。但是我认为 SQL Developer 中没有任何工具可以做到这一点,这就是我正在使用的。
- 尝试使用 SQL 时,我认为主要的挑战是遍历 n 列。我不确定该怎么做。
您可以使用 dbms_sql
to execute a query against your real table, interrogate the data types,并使用该信息生成 CTE 及其内部查询。
作为第一次尝试:
create or replace procedure print_cte (p_statement varchar2) as
-- dbms_sql variables
l_c pls_integer;
l_col_cnt pls_integer;
l_rows pls_integer;
l_desc_t dbms_sql.desc_tab;
l_first_row boolean := true;
l_varchar2 varchar2(4000);
l_number number;
l_date date;
-- etc.
begin
-- ideally add some checks for p_statement being a sinple query
l_c := dbms_sql.open_cursor;
dbms_sql.parse(c => l_c, statement => p_statement, language_flag => dbms_sql.native);
l_rows := dbms_sql.execute(c => l_c);
dbms_sql.describe_columns(c => l_c, col_cnt => l_col_cnt, desc_t => l_desc_t);
-- define columns, and output CTE columns at the same time
dbms_output.put('with cte (');
for i in 1..l_col_cnt loop
case l_desc_t(i).col_type
when 1 then
dbms_sql.define_column(c => l_c, position=> i, column => l_varchar2, column_size => 4000);
when 2 then
dbms_sql.define_column(c => l_c, position=> i, column => l_number);
when 12 then
dbms_sql.define_column(c => l_c, position=> i, column => l_date);
-- etc. plus else to skip or throw error for anything not handled
end case;
if i > 1 then
dbms_output.put(', ');
end if;
dbms_output.put('"' || l_desc_t(i).col_name || '"');
end loop;
dbms_output.put(') as (');
while dbms_sql.fetch_rows(c => l_c) > 0 loop
if (l_first_row) then
l_first_row := false;
else
dbms_output.put(' union all');
end if;
dbms_output.new_line;
for i in 1..l_col_cnt loop
if i = 1 then
dbms_output.put(' select ');
else
dbms_output.put(', ');
end if;
case l_desc_t(i).col_type
when 1 then
dbms_sql.column_value(c => l_c, position => i, value => l_varchar2);
dbms_output.put(q'[']' || l_varchar2 || q'[']');
when 2 then
dbms_sql.column_value(c => l_c, position => i, value => l_number);
dbms_output.put(l_number);
when 12 then
dbms_sql.column_value(c => l_c, position => i, value => l_date);
dbms_output.put(q'[to_date(']'
|| to_char(l_date, 'SYYYY-MM-DD-HH24:MI:SS')
|| q'[', 'SYYYY-MM-DD HH24:MI:SS')]');
-- etc. plus else to skip or throw error for anything not handled
end case;
end loop;
dbms_output.put(' from dual');
dbms_output.new_line;
end loop;
dbms_output.put_line(')');
dbms_output.put_line('select * from cte;');
dbms_sql.close_cursor(c => l_c);
end print_cte;
/
然后你可以做:
begin
print_cte('select * from your_table');
end;
/
产生:
with cte ("ASSET_ID", "VERTEX_NUM", "X", "Y") as (
select 10, 1, 118.56, 3.8 from dual
union all
select 10, 2, 118.62, 1.03 from dual
union all
select 10, 3, 121.93, 1.03 from dual
)
select * from cte;
当然,您的客户端必须配置为处理 dbms_output。
如内联注释中所述,您应该检查 passed-in 语句不会做一些令人讨厌的事情;并且您需要添加对其他数据类型的处理。这只是一个起点。
将 xmltable
或(json_table
for Oracle 12+)用于此类目的会更容易。
示例xmltable
:
- 只需将所有需要的数据汇总到
xmltype
: 你可以使用xmltype(cursor(select...from...))
:
select xmltype(cursor(select * from test)) xml from dual;
或dbms_xmlgen.getxmltype(query_string)
:
select dbms_xmlgen.getxmltype('select * from test') xml from dual;
- 然后您可以使用返回的 XML 和
xmltable('/ROWSET/ROW' passing xmltype(your_xml) columns ...)
示例:
select *
from xmltable(
'/ROWSET/ROW'
passing xmltype(q'[<?xml version="1.0"?>
<ROWSET>
<ROW>
<ASSET_ID>10</ASSET_ID>
<VERTEX_NUM>1</VERTEX_NUM>
<X>118.56</X>
<Y>3.8</Y>
</ROW>
<ROW>
<ASSET_ID>10</ASSET_ID>
<VERTEX_NUM>2</VERTEX_NUM>
<X>118.62</X>
<Y>1.03</Y>
</ROW>
</ROWSET>
]')
columns
asset_id,vertex_num,x,y
) test
DBFiddle: https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=036b718f2b18df898c3e3de722c97378
上的完整示例我知道我的回答不纯粹 SQL 或 PLSQL。
但是我建议你使用Javascript生成CTE查询,因为数据量很小。
Javascript 比 SQL 或 PLSQL 更容易维护。
您可以随时在这里使用这个小脚本。 (不需要额外的编辑器) https://jsfiddle.net/pLvgr8oh/
或者,如果您担心安全问题,您可以 运行 使用 Chrome 浏览器编写脚本。 https://developer.chrome.com/docs/devtools/javascript/snippets/
function convertToSelect(tsv, firstRowIsColumn, columnsComma, typesComma) {
function getCol(column, value, type) {
// In case type is 'date', after colon is date format
const [ typeNew, dateFormat ] = type.split(':')
switch (typeNew) {
case 'string': return `'${value}' as ${column}`
case 'number': return `${value} as ${column}`
case 'date': return `to_date('${value}', '${dateFormat}') as ${column}`
}
}
const columns = columnsComma ? columnsComma.split(',') : []
const types = typesComma ? typesComma.split(',') : []
// Split row by '\r\n' or '\n'
const list = tsv.split(/\r*\n/)
const colCount = list[0].split(/\t/).length
let columnsNew = []
let typesNew = types
// If first row is column name
if (firstRowIsColumn) {
columnsNew = list[0].split(/\t/);
list.shift(0)
}
// If column name is specified then override column names in first row
if (columns.length > 0) {
columnsNew = columns
}
// If type is not specified set all type to 'string'
if (typesNew.length === 0) {
typesNew = [...Array(colCount)].map(t => 'string')
}
const rows = list.map(item => {
// [ '2 as F_INT', '2.223 as F_LONG'... ]
const cols = item
.split(/\t/)
.map((value, index) => {
return getCol(columnsNew[index], value, typesNew[index])
})
.join(', ')
// select 2 as F_INT, 2.223 as F_LONG... from dual
return ` select ${cols} from dual`
})
const selectUnion = rows.join(`
union all
`)
return `with cte as
(
${selectUnion}
)
select * from cte;
`
}
const tsv = `F_INT F_LONG F_VARCHAR F_DATE
1 1.123 a 2022-12-01
2 2.223 b 2022-12-02
3 3.323 c 2022-12-03`
const firstRowIsColumn = true
const columnsComma = 'v_num,v_lng,v_str,v_date'
//const columnsComma = ''
const typesComma = 'number,number,string,date:YYYY-MM-DD'
//const typesComma = ''
const ret = convertToSelect(tsv, firstRowIsColumn, columnsComma, typesComma)
console.log(ret)
生成(如果代码段没有破坏制表符):
with cte as
(
select 1 as v_num, 1.123 as v_lng, 'a' as v_str, to_date('2022-12-01', 'YYYY-MM-DD') as v_date from dual
union all
select 2 as v_num, 2.223 as v_lng, 'b' as v_str, to_date('2022-12-02', 'YYYY-MM-DD') as v_date from dual
union all
select 3 as v_num, 3.323 as v_lng, 'c' as v_str, to_date('2022-12-03', 'YYYY-MM-DD') as v_date from dual
)
select * from cte;