How can I map numbers to their string equivalents in Perl? - perl

I am writing a script using DBI to execute a select query to an Oracle db. I have successfully able to capture the data but I need help to change the output.
Below is the sample output.
Type
2
6
I want to display 2=>Good and 6=>Bad
Can anyone please suggest me the Perl code to map the output?

# Create a hash of the values you want to output
my %human_text = (2 => 'Good', 6 => 'Bad');
# and then access the hash values like this:
print $human_text{2}; #will output 'Good'
print $human_text{6}; #will output 'Bad'

Usually the easiest way is to change directly the values outputted by the SQL query. With Oracle you can use DECODE.
SELECT DECODE(MY_TYPE, 2, 'TWO', 6, 'SIX', 'DEFAULT_VALUE') FROM MY_TABLE
The standard SQL way is to use a CASE conditional expression. It is a little more verbose, but more powerful and more portable. It works for example in Oracle, PostgreSQL and MS-SQL.
SELECT
CASE
WHEN MY_TYPE = 2 THEN 'TWO'
WHEN MY_TYPE = 6 THEN 'SIX'
ELSE 'DEFAULT_VALUE'
END CASE
FROM MY_TABLE
If you still want to do it in Perl, you might create a Hash. The code sample is quite trivial, and well documented in the link I provided.

Create a lookup table in your RDBMS which maps 2 to Good and 6 to Bad. Create an INNER JOIN (or LEFT JOIN if you anticipate having values that will not match the lookup) with your SQL statement (or create a VIEW which returns the JOINed tables). Trying to use Perl or SQL SELECT statements to replace database design is probably a bad idea.

$inWords = ("","very good","good","satisfactory","sufficient","poor","bad")[$number];

Related

How to save a string that contains both single and double quotes to postgres column

I have the following string:
SELECT hello as "helloHello", '' as "empty" FROM tbl_test
I want to do something like this:
INSERT INTO tbl_x (a, b, c) VALUES (x, y, string_from_above)
The strings are going to be dynamic (basically they're strings of sql statements), so I do not want to escape all the ' and ". If I open the postgres database and double click on the varchar column, I can copy and paste the string and it goes in exactly as it looks. I want to be able to programmatically do so. Is there a way using an insert command? I tried using pgFormat, but that didn't work. See attached pic with what it looks like in the table.
All PostgreSQL APIs worth their money have a way to escape strings for inclusion in a dynamic SQL statement.
For example, if you write in C, you can use libpq's PQescapeLiteral().
There are also SQL functions for this: quote_literal() and format(). They can be used in PL/pgSQL code.
Don't try to write your own code for this. Use prior art and avoid the risk of getting it wrong.

Alphanumeric sorting without any pattern on the strings [duplicate]

I've got a Postgres ORDER BY issue with the following table:
em_code name
EM001 AAA
EM999 BBB
EM1000 CCC
To insert a new record to the table,
I select the last record with SELECT * FROM employees ORDER BY em_code DESC
Strip alphabets from em_code usiging reg exp and store in ec_alpha
Cast the remating part to integer ec_num
Increment by one ec_num++
Pad with sufficient zeors and prefix ec_alpha again
When em_code reaches EM1000, the above algorithm fails.
First step will return EM999 instead EM1000 and it will again generate EM1000 as new em_code, breaking the unique key constraint.
Any idea how to select EM1000?
Since Postgres 9.6, it is possible to specify a collation which will sort columns with numbers naturally.
https://www.postgresql.org/docs/10/collation.html
-- First create a collation with numeric sorting
CREATE COLLATION numeric (provider = icu, locale = 'en#colNumeric=yes');
-- Alter table to use the collation
ALTER TABLE "employees" ALTER COLUMN "em_code" type TEXT COLLATE numeric;
Now just query as you would otherwise.
SELECT * FROM employees ORDER BY em_code
On my data, I get results in this order (note that it also sorts foreign numerals):
Value
0
0001
001
1
06
6
13
۱۳
14
One approach you can take is to create a naturalsort function for this. Here's an example, written by Postgres legend RhodiumToad.
create or replace function naturalsort(text)
returns bytea language sql immutable strict as $f$
select string_agg(convert_to(coalesce(r[2], length(length(r[1])::text) || length(r[1])::text || r[1]), 'SQL_ASCII'),'\x00')
from regexp_matches($1, '0*([0-9]+)|([^0-9]+)', 'g') r;
$f$;
Source: http://www.rhodiumtoad.org.uk/junk/naturalsort.sql
To use it simply call the function in your order by:
SELECT * FROM employees ORDER BY naturalsort(em_code) DESC
The reason is that the string sorts alphabetically (instead of numerically like you would want it) and 1 sorts before 9.
You could solve it like this:
SELECT * FROM employees
ORDER BY substring(em_code, 3)::int DESC;
It would be more efficient to drop the redundant 'EM' from your em_code - if you can - and save an integer number to begin with.
Answer to question in comment
To strip any and all non-digits from a string:
SELECT regexp_replace(em_code, E'\\D','','g')
FROM employees;
\D is the regular expression class-shorthand for "non-digits".
'g' as 4th parameter is the "globally" switch to apply the replacement to every occurrence in the string, not just the first.
After replacing every non-digit with the empty string, only digits remain.
This always comes up in questions and in my own development and I finally tired of tricky ways of doing this. I finally broke down and implemented it as a PostgreSQL extension:
https://github.com/Bjond/pg_natural_sort_order
It's free to use, MIT license.
Basically it just normalizes the numerics (zero pre-pending numerics) within strings such that you can create an index column for full-speed sorting au naturel. The readme explains.
The advantage is you can have a trigger do the work and not your application code. It will be calculated at machine-speed on the PostgreSQL server and migrations adding columns become simple and fast.
you can use just this line
"ORDER BY length(substring(em_code FROM '[0-9]+')), em_code"
I wrote about this in detail in this related question:
Humanized or natural number sorting of mixed word-and-number strings
(I'm posting this answer as a useful cross-reference only, so it's community wiki).
I came up with something slightly different.
The basic idea is to create an array of tuples (integer, string) and then order by these. The magic number 2147483647 is int32_max, used so that strings are sorted after numbers.
ORDER BY ARRAY(
SELECT ROW(
CAST(COALESCE(NULLIF(match[1], ''), '2147483647') AS INTEGER),
match[2]
)
FROM REGEXP_MATCHES(col_to_sort_by, '(\d*)|(\D*)', 'g')
AS match
)
I thought about another way of doing this that uses less db storage than padding and saves time than calculating on the fly.
https://stackoverflow.com/a/47522040/935122
I've also put it on GitHub
https://github.com/ccsalway/dbNaturalSort
The following solution is a combination of various ideas presented in another question, as well as some ideas from the classic solution:
create function natsort(s text) returns text immutable language sql as $$
select string_agg(r[1] || E'\x01' || lpad(r[2], 20, '0'), '')
from regexp_matches(s, '(\D*)(\d*)', 'g') r;
$$;
The design goals of this function were simplicity and pure string operations (no custom types and no arrays), so it can easily be used as a drop-in solution, and is trivial to be indexed over.
Note: If you expect numbers with more than 20 digits, you'll have to replace the hard-coded maximum length 20 in the function with a suitable larger length. Note that this will directly affect the length of the resulting strings, so don't make that value larger than needed.

How to store a list of integers as variable in PostgreSQL

I am just wondering how to store a list of integers as a variable in PostreSQL stored procedure.
For example, I have statements like these:
select A from B where C IN (1,2,3);
select A from B where C IN (1,2);
And I want to declare a variable to store (1,2,3) or (1,2).
So I would end up with a statement like:
select A from B where C in numberList;
(numberList has the value (1,2,3))
I don't know which datatype I should use,I looked up online and can't find there is a list type in psql. And what's the syntax for that as well?
You can store them as an integer array (i.e. int[]) type, and then invoke using the ANY operator as such:
WITH RECURSIVE CTE AS (
SELECT 1 AS i
UNION ALL
SELECT i+1 FROM CTE WHERE i < 15
)
SELECT * FROM CTE WHERE i = ANY( ARRAY[1, 5, 7, 9] )
which yields back the piecewise-dynamic IN operator result we're looking for:
i
-
1
5
7
9
with myconstants (i) as (values(1),(2),(3))
select A from B, myconstants where C=any(i);
further reading
With this solution you can use the "variable" in several queries. Note that it is actually a function, but you can use it as a variable.
Also consider that each time you use it, the function will be executed, therefore it may not be the most efficient way, but for simple uses it helps to avoid typing or pasting the same list many times in different queries.
CREATE FUNCTION get_constant_array()
RETURNS INTEGER[]
AS $$
BEGIN
RETURN ARRAY[1,2,3,4];
END;
$$ LANGUAGE plpgsql;
Then you can use it, for example, like this:
SELECT get_constant_array();
or like this:
SELECT * from sometable where some column in (SELECT get_constant_array)
PS: This solution is based on PostgreSQL, although I think it may work, perhaps with little changes, in other dialects as well.
PPS: With a similar approach you can also store a list of strings, let me know if you need me to edit my answer to use strings instead.

syb_describe in DBD::Sybase

I am looking to extract Sybase datatype for all the columns in a table. When I try to achieve this using $sth->{TYPE}, I get a numeric version of the datatype (i.e. instead of sybase datatype varchar, I get 0).
From the DBD::Sybase documentation, I noticed that SYBTYPE attribute of syb_describe function might be able to produce what I am looking for. But it seems that my understanding is not proper. SYBTYPE also prints datatype in numeric form only.
Is there any way to fetch the textual representation of actual Sybase datatype (instead of the number)?
It sounds like you wish to reverse engineer the create table definition. Here is an SQL script you can use for Sybase or SQL Server tables.
select c.name,
"type(size)"=case
when t.name in ("char", "varchar") then
t.name + "(" + rtrim(convert(char(3), c.length)) + ")"
else t.name
end,
"null"=case
when convert(bit, (c.status & 8)) = 0 then "NOT NULL"
else "NULL"
end
from syscolumns c, systypes t
where c.id = object_id("my_table_name")
and c.usertype *= t.usertype
order by c.colid
go
Note: This could still be edited with a nawk script to create a real SQL schema file.
The nawk script would strip the header, add "create table my_table_name", add commas, strip the footer and add a "go".
Good SQL, good night!
I found a workaround (Note: This does not answer the question though):
What I did was simply joined the sysobjects, systypes and syscolumns system tables.

How do I execute multiple mysql queries in Jasper Reports (not what you think...)

I have a complex query that requires a rank in it. I've learned that the standard way of doing that is by using the technique found on this page: http://thinkdiff.net/mysql/how-to-get-rank-using-mysql-query/. I'm using Infobright as the back end and it doesn't work quite as expected. That is, while a standard MySQL engine would show the rank as 1, 2, 3, 4, etc... Brighthouse (Infobright's engine) would return 1, 1, 1, 1, etc.... So I came up with a strategy of setting a variable, a function, and then execute it in the query. Here's a proof of concept query that does just that:
SET #rank = 0;
DROP FUNCTION IF EXISTS __GetRank;
DELIMITER $$
CREATE FUNCTION __GetRank() RETURNS INT
BEGIN
SET #rank = #rank + 1;
return #rank;
END$$
DELIMITER ;
select __GetRank() AS rank, id from accounts;
I then copied and pasted the function into Jasper Report's iReport and then compiled my report. After executing it, I get syntax errors. So I thought that perhaps the ; was throwing it off. So at the top of the query, I put in DELIMITER ;. This did not work either.
Is what I'm wanting to do even possible? If so, how? And if there's an Infobright way of getting a rank without writing a function, I'd be open to that too.
Infobright does not support functions.
From the site: http://www.infobright.org/forums/viewthread/1871/#7485
Indeed, IB supports stored procedures, but does not support stored functions nor user defined functions.
select if(#rank is null,#rank:= 0,#rank:= #rank +1) as rank, id from accounts
Does not work, because you cannot write to #vars in queries.
This:
SELECT
(SELECT COUNT(*)
FROM mytable t1
WHERE t1.rankedcolumn > t2.rankedcolumn) AS rank,
t2.rankedcolumn
FROM mytable t2 WHERE ...;
will work, but is very slow of course.
Disclaimer, not my code, but Jakub Wroblewski's (Infobright founder)
Hope this helps...
Here's how I solved this. I had my server side program execute a mysql script. I then took the output and converted it to a CSV. I then used this as the input data for my report. A little convoluted, but it works.