Extracting columns and table names from an oracle query that is based on dynamic SQL - sql

I want an approach/code snippet to extract column names and the corresponding table name from an oracle query. The queries and consequently the columns and table names change at run time and some of the column names usually are calculated meaning they are wrapped in a function and aliased. I tried different string tokenizing techniques using regexp to separate this out as per the expeted output, but so far, no luck !
Eg:
select mandate_name, investment_sub_team_name,
fn_sum(REG_INV_CMP_AUM) REG_INV_CMP_AUM,
fn_sum(NON_REG_INV_CMP_AUM) NON_REG_INV_CMP_AUM
from DM_SAI_VALUATIONS_STEP3
where position_interval_type = 'E'
and position_type = 'T'
group by mandate_name, investment_sub_team_name;
I want the output for the columns as:
mandate_name
investment_sub_team_name
fn_sum(REG_INV_CMP_AUM)
fn_sum(NON_REG_INV_CMP_AUM)
Note above: I want the columns with the function and not the alias
I want the output for the table name as: DM_SAI_VALUATIONS_STEP3 against all the columns that I listed above
I cannot edit the queries as they are part of an xml output. So, i cannot change the alias. The second point is to just extract the table name from the query. Please consider the fact that nothing can be hard coded like position of the string token etc as the queries containing the columns and the table would be different. I am looking for a generic approach to tokenize them. So, against the column output that I expect, i just need the table name as well..Its always going to only one table in the from clause, so extracting that would not be an issue.
Expected output:
Column Name Table Name
----------- ----------
mandate_name DM_SAI_VALUATIONS_STEP3
investment_sub_team_name DM_SAI_VALUATIONS_STEP3
fn_sum(REG_INV_CMP_AUM) DM_SAI_VALUATIONS_STEP3
fn_sum(NON_REG_INV_CMP_AUM) DM_SAI_VALUATIONS_STEP3
Any help pr pointers would be much appreciated.

You realistically can't solve this problem in general without writing your own SQL compiler (at least the parser and lexer up through the semantic analysis phase). That is a non-trivial exercise particularly if you want to accept any valid Oracle SQL query. Oracle Corporation used to have different SQL parsers for the SQL VM and the PL/SQL VM and couldn't keep them in sync-- it's a major investment of time and effort to keep evolving your parser as the supported SQL grammar improves.
If you're really determined to go down this path, you can start with some of the ANTLR SQL grammars. The O'Reilly Flex and Bison book also has a chapter on parsing SQL that you could potentially use as a starting point. Of course, you'll need to revise and extend the grammars to support whatever SQL features your queries might contain. You'll then need to build the syntax analyzer and semantic analysis portions of the compiler to implement the appropriate scope resolution rules to be able to figure out which table a particular reference to a particular column comes from. Just to reiterate, this is a non-trivial exercise and it's one that has to be enhanced for every new release of the database.
If you can relax your requirements and make some assumptions about what sorts of queries you're going to be seeing, it becomes much easier to write a parser. If you can guarantee that every query references exactly 1 table, identifying which table a particular column comes from is much easier. If you can guarantee that every function call takes at most one column from one table as a parameter, that also makes things easier-- otherwise, you'll need to figure out what you want to return for the table name if the column is the result of a function that takes as arguments columns from multiple tables.

I also agree it is generally not possible. But maybe the solution is to get in touch with the creator of the XML message and agree on a different protocol then a finished up SELECT statement beforehand. Agree with him sending the columns.
If this is not possible and you want to make certain assumptions about how the query is built then you can tokenize after the selectand before from by using the , as a separator. But by all I know you can not really do that by regular expression substring commands. I think you need a bit of PL/SQL function written.
But still take care from keyword could be somewhere part of the columns selecting. What do you do if you suddenly get a query like this:
select
something,
(select count(*) from othertable) as cnt,
andfromthiscolumn xyz
from mytable
So my tip here is to rather sort it out organizationally then trying to code the impossible.

If you know that the structure of your query strings will not change much, you can do something like this:
set serveroutput on
set termout on
clear
declare
v_str varchar2(500) := 'select mandate_name, investment_sub_team_name,
fn_sum(REG_INV_CMP_AUM) REG_INV_CMP_AUM,
fn_sum(NON_REG_INV_CMP_AUM) NON_REG_INV_CMP_AUM
from DM_SAI_VALUATIONS_STEP3
where position_interval_type = ''E''
and position_type = ''T''
group by mandate_name, investment_sub_team_name;';
v_tmp varchar2(500);
v_cols varchar2(500);
v_table varchar2(500);
begin
v_tmp := replace( v_str, 'select ','');
v_tmp := substr( v_tmp, 1, instr(v_tmp, 'where')-1);
dbms_output.put_line('original query: '||v_str);
v_cols := substr (v_tmp, 1, instr(v_tmp, 'from')-1);
dbms_output.put_line('column names: '||v_cols);
v_table := substr(v_tmp, instr(v_tmp, 'from ')+4, 500);
dbms_output.put_line('table name: '||v_table);
end;

Related

SQL DB2 - How to SELECT or compare columns based on their name?

Thank you for checking my question out!
I'm trying to write a query for a very specific problem we're having at my workplace and I can't seem to get my head around it.
Short version: I need to be able to target columns by their name, and more specifically by a part of their name that will be consistent throughout all the columns I need to combine or compare.
More details:
We have (for example), 5 different surveys. They have many questions each, but SOME of the questions are part of the same metric, and we need to create a generic field that keeps it. There's more background to the "why" of that, but it's pretty important for us at this point.
We were able to kind of solve this with either COALESCE() or CASE statements but the challenge is that, as more surveys/survey versions continue to grow, our vendor inevitably generates new columns for each survey and its questions.
Take this example, which is what we do currently and works well enough:
CASE
WHEN SURVEY_NAME = 'Service1' THEN SERV1_REC
WHEN SURVEY_NAME = 'Notice1' THEN FNOL1_REC
WHEN SURVEY_NAME = 'Status1' THEN STAT1_REC
WHEN SURVEY_NAME = 'Sales1' THEN SALE1_REC
WHEN SURVEY_NAME = 'Transfer1' THEN Null
ELSE Null
END REC
And also this alternative which works well:
COALESCE(SERV1_REC, FNOL1_REC, STAT1_REC, SALE1_REC) as REC
But as I mentioned, eventually we will have a "SALE2_REC" for example, and we'll need them BOTH on this same statement. I want to create something where having to come into the SQL and make changes isn't needed. Given that the columns will ALWAYS be named "something#_REC" for this specific metric, is there any way to achieve something like:
COALESCE(all columns named LIKE '%_REC') as REC
Bonus! Related, might be another way around this same problem:
Would there also be a way to achieve this?
SELECT (columns named LIKE '%_REC') FROM ...
Thank you very much in advance for all your time and attention.
-Kendall
Table and column information in Db2 are managed in the system catalog. The relevant views are SYSCAT.TABLES and SYSCAT.COLUMNS. You could write:
select colname, tabname from syscat.tables
where colname like some_expression
and syscat.tabname='MYTABLE
Note that the LIKE predicate supports expressions based on a variable or the result of a scalar function. So you could match it against some dynamic input.
Have you considered storing the more complicated properties in JSON or XML values? Db2 supports both and you can query those values with regular SQL statements.

Bool support Oracle SQL

It's a constant frustration of mine that Oracle PL/SQL supports the bool data-type, while Oracle SQL does not. It's a big pain in the proverbial when you want to process a PL/SQL boolean return-value back into your everyday SQL (example below).
Even the ask-Tom website is blasé about this misfit, reporting that you should code boolean columns as fixed-values 'Y'/'N' CHAR columns, which is a such a bad cop-out answer on so many different levels that I don't know where to start criticising it. In fact, the only redeeming quality of this response is the fact that (as far as I've recently discovered), many other database-engines don't support the boolean data-type either.
Anyhow - the question...
I have a work-around for the following problem (albeit messy and verbose), so I'm asking this question out of curiosity rather than necessity. But one of the few things that surprises me any more is the ingenuity of clever programmers, so here's hoping that one of you can come up with a solution to the following.
In the following sample, the function stock_pkg.is_in_stock() (which is an inherent part of my application) returns a BOOL value, rendering the SQL invalid (remember, SQL doesn't support BOOL):
SELECT part_no, stock_pkg.is_in_stock(part_no) in_stock
FROM parts_table
What I need is to find a way of using the above function-call to generate a valid string (varchar) output of the format:
PART_NO IN_STOCK
------- ------------
AA YES
BB NO
CC NO
(You may substitute 'yes/no' for 'true/false', 'green/red', 'tory/labour' or even numeric 1/0 for all I care - just so long as the output falls into one of two distinct categories.)
Unfortunately, I don't have the privilege to rewrite the original function to return a different data-type. And besides, there are thousands of functions like this dotted around the larger application, making it impractical to rewrite them all.
So in this sense, the solution must be a 'generic' one (i.e. not specific to this function call). For example, it is not sufficient to rewrite the function as stock_pkg.is_in_stock_chr(), because that would mean having to re-write all the other similar functions in my application too.
I've already tried:
SELECT part_no,
CASE WHEN stock_pkg.is_in_stock(part_no) THEN 'y' ELSE 'n' END in_stock
FROM parts_table
and even my own wrapper function:
SELECT part_no,
my_bool_to_str(stock_pkg.is_in_stock(part_no)) in_stock
FROM parts_table
But even wrapping booleans inside other functional constructs doesn't seem to be allowed by Oracle SQL (at least not in Oracle 10g).
There's also the option of writing a sub-select inside the in_stock column, but that could get excessively complicated in extreme examples too, and would also be case-specific.
As I say, I hope there's an ingenious solution out there somewhere (or at least a very simple one which I happen to have overlooked).
Thanks for your time.
You can write your own wrapper like this:
CREATE OR REPLACE FUNCTION my_bool_to_str(f varchar2) RETURN VARCHAR2 IS
b varchar2(2);
BEGIN
EXECUTE IMMEDIATE 'declare bl boolean; begin bl := ' || f ||
'; if bl then :1 := ''y''; else :1 := ''n''; end if; end;'
using out b;
return b;
END;
Then you can call it like this:
SELECT part_no,
my_bool_to_str('stock_pkg.is_in_stock('|| part_no|| ')') in_stock
FROM parts_table
The difference from your wrapper is that it gets a varchar as input and not a boolean which the SQL engine doesn't recognize

Why do SQL errors not show you the error source?

Is it possible to find the line or column where an error is occurring when executing SQL code in Oracle SQL developer?
For example, imagine you are running a very simple line of code
SELECT * FROM employeesTbl WHERE active = 1
But for some reason, active is VARCHAR and someone has entered the ";!/asd02" into this field.
You will only get an ORA- error, but it does not tell you which row caused it.
Does anyone know why this is?
The reason behind this is that in general developer support in sql, pl/sql and the like is really abysmal. One result is a really broken exception concept in pl/sql, almost useless exceptions in (oracle) sql and little hope that it is better in any rdbms.
I think the reason behind all that is that databases are persistent beasts (pun intended). Many companies and developers change from time to time there preferred main development language (C, C++, VB, Java, C#, Groovy, Scala ..). But they rarely change the database, possibly because you will still have the old databases around with no chance to migrate them.
This in turn means most DB-devs know only a single database system reasonable well, so they don't see what is possible in other systems. Therefore there is little to no pressure to make database systems any more usable for developers.
Multiple rows may contain errors. For the system to be consistent (as a "set-based" language), it ought to return you all rows which contain errors - and not all row errors may be caused by the same error.
However, it could be computationally expensive to compute this entire error set - and the system "knows" that any further computation on this query is going to result in failure anyway - so it represents wasted resources when other queries could be running successfully.
I agree that it would be nice to turn on this type of reporting as an option (especially in non-production environments), but no database vendor seems to have done so.
You get an error because the field is a character and you're assuming it's a number. Which, you shouldn't be doing. If you want the field to be numeric then you have to have a numeric field! This is a general rule, all non-character columns should be the correct data-type to avoid this type of problem.
I'm not certain why Oracle doesn't tell you what row caused the error, it may be physically possible using the rowid in a simple select as you have here. If you're joining tables or using conversion functions such as to_number it would become a lot more difficult, if possible at all.
I would imagine that Oracle did not want to implement something only partially, especially when this is not an Oracle error but a coding error.
To sort out the problem create the following function:
create or replace function is_number( Pvalue varchar2
) return number is
/* Test whether a value is a number. Return a number
rather than a plain boolean so it can be used in
SQL statements as well as PL/SQL.
*/
l_number number;
begin
-- Explicitly convert.
l_number := to_number(Pvalue);
return 1;
exception when others then
return 0;
end;
/
Run the following to find your problem rows:
SELECT * FROM employeesTbl WHERE is_number(active) = 0
Or this to ignore them:
SELECT *
FROM ( SELECT *
FROM employeesTbl
WHERE is_number(active) = 1 )
WHERE active = 1

SQL to filter by multiple criteria including containment in string list

so i have a table lets say call it "tbl.items" and there is a column "title" in "tbl.items" i want to loop through each row and for each "title" in "tbl.items" i want to do following:
the column has the datatype nvarchar(max) and contains a string...
filter the string to remove words like in,out, where etc (stopwords)
compare the rest of the string to a predefined list and if there is a match perform some action which involves inserting data in other tables as well..
the problem is im ignotent when it comes to writing T-sql scripts, plz help and guide me how can i achieve this?
whether it can be achieved by writing a sql script??
or i have to develope a console application in c# or anyother language??
im using mssql server 2008
thanks in advance
You want a few things. First, look up SQL Server's syntax for functions, and write something like this:
-- Warning! Code written off the top of my head,
-- don't expect this to work w/copy-n-paste
create function removeStrings(#input nvarchar(4000))
as begin
-- We're being kind of simple-minded and using strings
-- instead of regular expressions, so we are assuming a
-- a space before and after each word. This makes this work better:
#input = ' ' + #input
-- Big list of replaces
#input = replace(' in ','',#input)
#input = replace(' out ','',#input)
--- more replaces...
end
Then you need your list of matches in a table, call this "predefined" with a column "matchString".
Then you can retrieve the matching rows with:
select p.matchString
from items i
join predefined p
on removeStrings(i.title) = p.matchString
Once you have those individual pieces working, I suggest a new question on what particular process you may be doing with them.
Warning: Not knowing how many rows you have or how often you have to do this (every time a user saves something? Once/day?), this will not exactly be zippy, if you know what I mean. So once you have these building blocks in hand, there may also be a follow-up question for how and when to do it.

SQL to search and replace in mySQL

In the process of fixing a poorly imported database with issues caused by using the wrong database encoding, or something like that.
Anyways, coming back to my question, in order to fix this issues I'm using a query of this form:
UPDATE table_name SET field_name =
replace(field_name,’search_text’,'replace_text’);
And thus, if the table I'm working on has multiple columns I have to call this query for each of the columns. And also, as there is not only one pair of things to run the find and replace on I have to call the query for each of this pairs as well.
So as you can imagine, I end up running tens of queries just to fix one table.
What I was wondering is if there is a way of either combine multiple find and replaces in one query, like, lets say, look for this set of things, and if found, replace with the corresponding pair from this other set of things.
Or if there would be a way to make a query of the form I've shown above, to run somehow recursively, for each column of a table, regardless of their name or number.
Thank you in advance for your support,
titel
Let's try and tackle each of these separately:
If the set of replacements is the same for every column in every table that you need to do this on (or there are only a couple patterns), consider creating a user-defined function that takes a varchar and returns a varchar that just calls replace(replace(#input,'search1','replace1'),'search2','replace2') nested as appropriate.
To update multiple columns at the same time you should be able to do UPDATE table_name SET field_name1 = replace(field_name1,...), field_name2 = replace(field_name2,...) or something similar.
As for running something like that for every column in every table, I'd think it would be easiest to write some code which fetches a list of columns and generates the queries to execute from that.
I don't know of a way to automatically run a search-and-replace on each column, however the problem of multiple pairs of search and replace terms in a single UPDATE query is easily solved by nesting calls to replace():
UPDATE table_name SET field_name =
replace(
replace(
replace(
field_name,
'foo',
'bar'
),
'see',
'what',
),
'I',
'mean?'
)
If you have multiple replaces of different text in the same field, I recommend that you create a table with the current values and what you want them replaced with. (Could be a temp table of some kind if this is a one-time deal; if not, make it a permanent table.) Then join to that table and do the update.
Something like:
update t1
set field1 = t2.newvalue
from table1 t1
join mycrossreferncetable t2 on t1.field1 = t2.oldvalue
Sorry didn't notice this is MySQL, the code is what I would use in SQL Server, my SQL syntax may be different but the technique would be similar.
I wrote a stored procedure that does this. I use this on a per database level, although it would be easy to abstract it to operate globally across a server.
I would just paste this inline, but it would seem that I'm too dense to figure out how to use the markdown deal, so the code is here:
http://www.anovasolutions.com/content/mysql-search-and-replace-stored-procedure