Execute select statement with list in variable gives 'Unsupported feature 'assignment from non-constant source expression' - sql

I'm trying to select table columns that are same in myTable1 and myTable2 and than do a select statement upon those columns in myTable1.
So this works, getting same columns into list in variable:
set columnNames = (select listagg(a.*,',') from
(SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'myTable1') a
inner join
(SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS b
WHERE TABLE_NAME = 'myTable2') b
on a.column_name = b.column_name);
but this fails:
SET sqlText = 'SELECT ' + $columnNames + ' from myDB.MySchema.myTable
where sold > 0 and hour = ''2022-08-01 13:00:00.000''
and id = ''zzzseee2323''
limit 10';
with error:
000002 (0A000): Unsupported feature 'assignment from non-constant
source expression'.
So how to concatenate that variable with text and execute statement?
I would on the end execute just with
EXECUTE IMMEDIATE $sqlText

The size of session variableSET is 256bytes and most likely will not be usable in this context.
This case could be resolved using Snowflake Scripting:
DECLARE
res RESULTSET;
query TEXT;
BEGIN
LET columnNames := (SELECT listagg(a.*,',') FROM
(SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'myTable1') a
inner join
(SELECT column_name
FROM INFORMATION_SCHEMA.COLUMNS b
WHERE TABLE_NAME = 'myTable2') b
on a.column_name = b.column_name);
query := 'SELECT ' + :columnNames + '
from myDB.MySchema.myTable
where sold > 0 and hour = ''2022-08-01 13:00:00.000''
and id = ''zzzseee2323''
limit 10';
res := (EXECUTE IMMEDIATE :query);
RETURN TABLE (res);
END;

Related

dynamic SQL ERROR: column "age" does not exist

postgres 12
I am trying to loop through a table which has schema , table_names and columns
I want to do various things like finding nulls ,row count etc. I failed at the first hurdle trying to update the col records.
table i am using
CREATE TABLE test.table_study (
table_schema text,
table_name text,
column_name text,
records int,
No_Nulls int,
No_Blanks int,
per_pop int
);
I populate the table with some schema names ,tables and columns from information_schema.columns
insert into test.table_study select table_schema, table_name, column_name
from information_schema.columns
where table_schema like '%white'
order by table_schema, table_name, ordinal_position;
I want to populate the rest with a function
function :-
CREATE OR REPLACE PROCEDURE test.insert_data_population()
as $$
declare s record;
declare t record;
declare c record;
BEGIN
FOR s IN SELECT distinct table_schema FROM test.table_study
LOOP
FOR t IN SELECT distinct table_name FROM test.table_study where table_schema = s.table_schema
loop
FOR c IN SELECT column_name FROM test.table_study where table_name = t.table_name
LOOP
execute 'update test.table_study set records = (select count(*) from ' || s.table_schema || '.' || t.table_name || ') where table_study.table_name = '|| t.table_name ||';';
END LOOP;
END LOOP;
END LOOP;
END;
$$
LANGUAGE plpgsql;
I get this error SQL Error [42703]: ERROR: column "age" does not exist. the table age does exist.
when I take out the where clause
execute 'update referralunion.testinsert ti set records = (select count(*) from ' || s.table_schema || '.' || t.table_name || ') ;';
it works, I just cant figure out whats wrong?
Your procedure is structured entirely wrong. What it results in is an attempt to get every column name for every table name in every schema. I would guess results in your column does not exist error. Further is shows procedural thinking. SQL requires think in terms of sets. Below I use basically your query to demonstrate then a revised version which uses a single loop.
-- setup (dropping schema references)
create table table_study (
table_schema text,
table_name text,
column_name text,
records int,
no_nulls int,
no_blanks int,
per_pop int
);
insert into table_study(table_schema, table_name, column_name)
values ('s1','t1','age')
, ('s2','t1','xyz');
-- procedure replacing EXECUTE with Raise Notice.
create or replace procedure insert_data_population()
as $$
declare
s record;
t record;
c record;
line int = 0;
begin
for s in select distinct table_schema from table_study
loop
for t in select distinct table_name from table_study where table_schema = s.table_schema
loop
for c in select column_name from table_study where table_name = t.table_name
loop
line = line+1;
raise notice '%: update table_study set records = (select count(*) from %.% where table_study.table_name = %;'
, line, s.table_schema, t.table_name, c.column_name;
end loop;
end loop;
end loop;
end;
$$
language plpgsql;
Run procedure
do $$
begin
call insert_data_population();
end;
$$;
RESULTS
1: update table_study set records = (select count(*) from s2.t1 where table_study.table_name = age; 2: update table_study set records = (select count(*) from s2.t1 where table_study.table_name = xyz; 3: update table_study set records = (select count(*) from s1.t1 where table_study.table_name = age; 4: update table_study set records = (select count(*) from s1.t1 where table_study.table_name = xyz;
Notice lines 2 and 3. Each references a column name that does not exist in the table. This results from the FOR structure with the same table name in different schema.
Revision for Single Select statement with Single For loop.
create or replace
procedure insert_data_population()
language plpgsql
as $$
declare
s record;
line int = 0;
begin
for s in select distinct table_schema, table_name, column_name from table_study
loop
line = line+1;
raise notice '%: update table_study set records = (select count(*) from %.% where table_study.table_name = %;'
, line, s.table_schema, s.table_name, s.column_name;
end loop;
end;
$$;
do $$
begin
call insert_data_population();
end;
$$;
RESULTS
1: update table_study set records = (select count(*) from s2.t1 where table_study.table_name = xyz;
2: update table_study set records = (select count(*) from s1.t1 where table_study.table_name = age;
Note: In Postgres DECLARE begins a block. It is not necessary to declared each variable. I would actually consider it bad practice. In theory it could require an end for each declare as each could be considered a nested block. Fortunately Postgres does not require this.

How to get last column in SQL?

For example I have a table with data:
Column1 Column2 Column3 ... Column1xx
1 a 10 x
2 b 20 y
3 c 30 z
What SQL query would get me only the last column?
Column1xx
x
y
z
Note: Column Name and Index of the last column is unknown
Fill your_table_schema and your_table_name. Originated from the post https://stackoverflow.com/a/38993544/18396231
SELECT COLUMN_NAME, ORDINAL_POSITION
FROM information_schema.COLUMNS
WHERE TABLE_SCHEMA = '{your_table_schema}'
AND TABLE_NAME ='{your_table_name}'
ORDER BY ORDINAL_POSITION DESC
LIMIT 1;
create so test data:
CREATE TABLE test.test.testo(aa number, bb number, cc number);
INSERT INTO test.test.testo values (1,10,100),(2,20,200),(3,30,300);
use tasozgurcem11 SQL to show we can get the column name:
SELECT COLUMN_NAME
FROM test.information_schema.COLUMNS
WHERE TABLE_SCHEMA = 'TEST'
AND TABLE_NAME ='TESTO'
ORDER BY ORDINAL_POSITION DESC
LIMIT 1;
and the put that into Snowflake Scripting to having it run dynamically:
DECLARE
query STRING;
col_name STRING;
res RESULTSET;
BEGIN
LET _database := 'TEST';
LET _table := 'TESTO';
LET _schema := 'TEST';
SELECT COLUMN_NAME INTO :col_name
FROM test.information_schema.COLUMNS
WHERE TABLE_SCHEMA = :_schema
AND TABLE_NAME = :_table
ORDER BY ORDINAL_POSITION DESC
LIMIT 1;
QUERY := 'SELECT '|| :col_name ||' FROM '|| _database ||'.'|| _schema ||'.'|| _table ||';';
res := (EXECUTE IMMEDIATE :QUERY);
return table(res);
END;
gives:
CC
100
200
300
this may help you where a function returns the last column name.
create or replace TABLE TEST_TABLE (
ID NUMBER(38,0),
C1 NUMBER(38,0),
C2 VARCHAR(16777216) MASKING POLICY TEST_HK.PUBLIC.MASK_DATA,
C3 DATE
);
CREATE OR REPLACE function LST_COLUMN(tab_nam varchar)
RETURNS VARCHAR
AS 'select column_name from information_schema.columns where table_name = tab_nam and ORDINAL_POSITION
= (SELECT max(ORDINAL_POSITION) from information_schema.columns where table_name = tab_nam)';
select LST_COLUMN('TEST_TABLE') from test_table;

How to find the number of columns in which records are more than 3?

To solve this problem I take names of columns from information_schema.columns and make query which should count number of records in one column. But that subquery returns more than 1 value and I don't know why, because it should return number of rows - one value. Please explain me what I'm doing wrong.
SELECT count(COLUMN_NAME)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'users' group by COLUMN_NAME having((select count(COLUMN_NAME) from users where COLUMN_NAME is not null)>3)
Columns
name | surname
__________________
a | b
c | d
e | f
e | null
Function should return 1 (name column have 4 records)
You can try this solution:
DECLARE dsql STRING;
DECLARE qry STRING;
SET qry = (
SELECT
STRING_AGG(ARRAY_TO_STRING(["SELECT '", column_name, "' AS ColumnName, " || "COUNT(" || column_name || ") AS RowCount FROM mydataset.", table_name, ' union all'], ''), ' ')
FROM
mydataset.INFORMATION_SCHEMA.COLUMNS
WHERE
table_name = 'users' );
SET dsql = 'SELECT * FROM ( ' || SUBSTR(qry, 1, LENGTH(qry) - LENGTH(' union all')) || ') WHERE RowCount > 3';
EXECUTE IMMEDIATE dsql;

CREATE AS SELECT * but with one column obtained from another table

I need to 'recreate' over 50 tables (in Oracle) with CREATE AS SELECT statements. However, all this tables will have one column modified using data from another table. Is there a way to achieve this without declaring each column in the SELECT statement?
Something like:
CREATE TABLE table_name_copy AS SELECT *, (SELECT col_name FROM other_table WHERE other_table.col_id = table_name.col_id) AS col_name FROM table_name`
Basically on all tables I have a column which needs to be replaced with the data in the other_table column.
Generate the SQL string as such:
SELECT 'CREATE TABLE table_name_copy AS SELECT '
|| LISTAGG (column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|| ', (SELECT col_name FROM other_table
WHERE other_table.col_id = table_name.col_id) AS col_name'
|| ' FROM table_name'
FROM all_tab_cols
WHERE owner = 'OWNER'
AND table_name = 'TABLE_NAME'
AND column_name != 'COL_NAME'
If you want to run the above statement, you could use EXECUTE IMMEDIATE:
DECLARE
v_sql VARCHAR2(10000);
BEGIN
SELECT 'CREATE TABLE table_name_copy AS SELECT '
|| LISTAGG (column_name, ', ') WITHIN GROUP (ORDER BY column_name)
|| ', (SELECT col_name FROM other_table
WHERE other_table.col_id = table_name.col_id) AS col_name'
|| ' FROM table_name'
INTO v_sql
FROM all_tab_cols
WHERE owner = 'OWNER'
AND table_name = 'TABLE_NAME'
AND column_name != 'COL_NAME';
EXECUTE IMMEDIATE v_sql;
END;
/
If col_id column is fixed for both of the joined tables,
you may use user_tab_columns and user_tables dictionary views through the schema to produce new tables named as "table_name_copy" by using the following mechanism :
declare
v_ddl varchar2(4000);
v_cln varchar2(400);
begin
for c in ( select *
from user_tables t
where t.table_name in
( select c.table_name
from user_tab_columns c
where c.column_name = 'COL_ID' )
order by t.table_name )
loop
v_ddl := 'create table '||c.table_name||'_copy as
select ';
for d in ( select listagg('t1.'||column_name, ',') within group ( order by column_name ) cln
from user_tab_columns
where table_name = c.table_name
and column_name != 'COL_ID' )
loop
v_cln := v_cln||d.cln;
end loop;
v_ddl := v_ddl||v_cln;
v_ddl := v_ddl||', t2.col_id t2_id
from '||c.table_name||' t1
left outer join other_table t2 on ( t1.col_id = t2.col_id )';
execute immediate v_ddl;
v_ddl := null;
v_cln := null;
end loop;
end;
Maybe you can use a simple join and an asterisk to return all columns from the first table, like that:
CREATE TABLE table_name_copy AS
SELECT * FROM (
SELECT tab1.*, tab2.column_name
FROM table_name tab1 LEFT JOIN other_table tab2 ON tab1.col_id = tab2.col_id
);
I would try this (but I don't have Oracle SQL to test on so please leave me the benefit of the doubt)
CREATE TABLE table_name_copy AS
SELECT * FROM (
SELECT *, (SELECT col_name FROM other_table WHERE other_table.col_id = table_name.col_id) as col_name
FROM table_name`
)
edit:
then run
ALTER TABLE table_name_copy DROP COLUMN <old column>
to remove the column you don't need any more

SQL Oracle - Different count of all table if one particular column exist

I have the following script to count all line of every table for a specific owner. It works perfectly.
However, some tables have a specific column called 'OLD' and other don't...
My current script does not take into account if this column exists or not:
DECLARE
val NUMBER;
BEGIN
FOR I IN (SELECT table_name FROM all_tables where owner='myowner') LOOP
EXECUTE IMMEDIATE 'SELECT count(*) FROM myowner.' || i.table_name INTO val;
DBMS_OUTPUT.PUT_LINE(i.table_name || ';' || val );
END LOOP;
END;
So what I would like to add is something like:
if the OLD column exists, take it in account when OLD=0 (where OLD=0), if it does not exist keep doing the 'normal' count without taking this column into account.
Hope I've been clear enough ;)
Thanks a lot!
An example:
let's say I have 2 tables:
Table1 - columns A B C with the following data:
1 "test" "Steve"
2 "test2" "George"
Table2 - columns E F G OLD with the following data:
1 "test3" "Martin" 0
2 "test4" "Lucas" 0
3 "test5" "Marley" 0
4 "test6" "Bob" 55
The result should then be:
Table1;2 -> there was not the 'OLD' column so I made a simples count
which returned 2
Table2;3 -> there was the 'OLD' column so I made a count where OLD=0
and it returned then 3
Try:
DECLARE
val NUMBER;
BEGIN
FOR I IN (
select a.table_name, c.column_name
from all_tables a
left join all_tab_cols c
ON a.owner = c.owner and a.table_name = c.table_name and c.column_name = 'OLD'
where a.owner = 'MYOWNER'
)
LOOP
IF i.column_name IS NULL THEN
EXECUTE IMMEDIATE 'SELECT count(*) FROM MYOWNER.' || i.table_name INTO val;
ELSE
EXECUTE IMMEDIATE 'SELECT count(*) FROM MYOWNER.' || i.table_name
|| ' WHERE old = 0' INTO val;
END IF;
DBMS_OUTPUT.PUT_LINE(i.table_name || ';' || val );
END LOOP;
END;