Advantage Database, case sensitivity, and collations - sql

I have something similar to this in my Advantage database:-
drop table #test;
create table #test (x cichar(50));
insert into #test (x) values ('cheese');
insert into #test (x) values ('fromage');
insert into #test (x) values ('Queso');
select
t1.x t1_x,
t2.x t2_x
from
#test t1
inner join
(
select
'CHEESE' x // String literal will be of type VARCHARFOX
from
system.iota
) t2
on t1.x=t2.x
This gives me the message:-
poQuery: Error 7200: AQE Error: State = HY000; NativeError = 2213;
[iAnywhere Solutions] [Advantage SQL Engine]Invalid comparison or
operation on strings with different collations. ** Script error
information: -- Location of error in the SQL statement is: 137 (line:
5 column: 1)
When I would like:-
t1_x t2_x
cheese CHEESE
This is because the 'CHEESE' string literal becomes type VARCHARFOX and the column in the temp table is of type cichar because I want case insensitive comparisons.
I can fix this instance by adding a "COLLATE ads_default_ci" to the comparison, however this is cumbersome and I can never remember the exact syntax of it.
I think I must be doing something fundamentally wrong with the column types or the configuration of the database, what's the elegant/right way to do this?

It's not so much the string literal which is the problem here, string literals usually behave just fine.
They get the COERCION_COMPATBILE collation:
TRY drop table #test; CATCH ALL END TRY;
create table #test (x cichar(50));
insert into #test (x) values ('cheese');
insert into #test (x) values ('fromage');
insert into #test (x) values ('Queso');
select
t1.x t1_x,
'CHEESE' t2_x
from
#test t1
where
t1.x='CHEESE'
The problem is that you are introducing a derived table and that the ADS engine determines the data type of the derived table field to be VARCHARFOX(6):
select
'CHEESE' x
from
system.iota
I don't think that there is an easy way to do this.
I have created an upstream request on the ADS forum.
Also I have added a feature request based on this. If it was implemented the solution would be:
select
CICHAR'CHEESE' x
from
system.iota
I think that this is would be a clean solution.
You should also be aware of something else:
There is a collation associated with each connection.
The NCHAR also built-in support for case-sensitive and case-insensitive collations. Which one is used depends on the collation of the current connection.
You can set the collation for the current connection using the AdsSetCollation function (or it's equivalent for you development environment, for example in ARC you can set it in the connection properties).

Related

varchar to numeric:Error converting data type varchar to numeric

I am trying to convert a column formatted in varchar to decimal(19,12) with the following line of code
ALTER TABLE [tablename]
ALTER COLUMN [columnname][format]
and get the following prompt:
Msg 8114, Level 16, State 5, Line 25
Error converting data type varchar to numeric.
Has worked before like a charm. The issue here seems to be that the values in the column are 19 or so digit numeric values formatted as text.
I tried to create a new column, pasted shortened cell values (used the left() function) into it from the original column but that doesn't seem to do the trick either since the code above ends up occationally with the additional "Arithmetic overflow occurred." message.
When some of the rows have incorrect values, ALTER COLUMN would not work. A typical course of action goes like this:
Add a new column of the desired type
Update the column with values that you would like to keep
Drop the old column
Rename the new column
Step 2 would go like this:
UPDATE MyTable
SET NewColumn =
CASE WHEN ISNUMERIC(OldColumn)=1 AND DATALENGTH(OldColumn) <= 19 THEN
CAST(OldColumn AS decimal(19,12))
ELSE
NULL
END
You could also turn ANSI warnings off with SET ANSI_WARNINGS OFF command, which would let you run ALTER COLUMN ignoring data trunction errors. The drawback of this approach is that potential errors get ignored. On the other hand, when you do conversion explicitly with a CASE expression you have an option to supply an alternative value for the error case (I used NULL above, but you can put any number you want).
Could you try to seperate your problem? This does work on SQL 2012:
set nocount on
if object_id ('tempdb..#t1') is not null drop table #t1
create table #t1 (c1 varchar(100))
insert #t1 values ('1234567.8901234567890')
select * from #t1
alter table #t1
alter column c1 decimal(19,12)
select * from #t1
If you play around a bit with the strings you easily can produce an arimetic overflow error. But 'Error converting data type varchar to numeric' needs character or empty sting.
Maybe you can try with your data?

SQL AS statement without column name shortcut

what does the AS in a statement such as below do?
SELECT column AS
FROM table
Since there is no shortcut, does it even do anything?
You did not tag your actual RDBMS, the following is valid for SQL-Server, but you'll be able to transfer this to your system:
--A dummy table as declared table variable
DECLARE #DummyTbl TABLE(SomeColumn INT);
INSERT INTO #DummyTbl VALUES(1),(2),(3);
--Variation of namings
SELECT SomeColumn --The orginal name
,SomeColumn AS OtherName --A classical alias
,SomeColumn test --The alias does NOT need the 'AS'
,SomeColumn [AS] --You may name your output 'AS'
FROM #DummyTbl;
But your syntax is - at least with SQL Server - not valid. AS is - in this context - a reserved word.

Insert Operation Failed for SQL Server

I tried to insert some rows into SQL server database and throws an error:
Conversion failed when converting the varchar value 'null' to data type bit
Could anyone explain what is this for?
This is not in a program.
What you might be trying
INSERT INTO TESTTABLE(BITCOLUMN) VALUES('NULL')
What should it actually be..
INSERT INTO TESTTABLE(BITCOLUMN) VALUES(NULL)
If you want to insert a null value into a column, you need to write NULL, and not 'NULL' (wrapping it in quotes indicates that it is a VARCHAR containing the letters N, U, L L - not a null value).
That is what the error message is telling you: you're trying to insert the VARCHAR value 'NULL' into a column that will only accept bit data.
Change the 'NULL' in your insert query to NULL.
You need something like this:
INSERT INTO TableName(ColumnName) VALUES(NULL); --this is the right way
Instead of this:
INSERT INTO TableName(ColumnName) VALUES('NULL'); --this is the wrong way

How to do an insert with multiple rows in Informix SQL?

I want to insert multiple rows with a single insert statement.
The following code inserts one row, and works fine:
create temp table mytmptable
(external_id char(10),
int_id integer,
cost_amount decimal(10,2)
) with no log;
insert into mytmptable values
('7662', 232, 297.26);
select * from mytmptable;
I've tried changing the insert to this, but it gives a syntax error:
insert into mytmptable values
('7662', 232, 297.26),
('7662', 232, 297.26);
Is there a way to get it working, or do I need to run many inserts instead?
You could always do something like this:
insert into mytmptable
select *
from (
select '7662', 232, 297.26 from table(set{1})
union all
select '7662', 232, 297.26 from table(set{1})
)
Pretty sure that's standard SQL and would work on Informix (the derived table is necessary for Informix to accept UNION ALL in INSERT .. SELECT statements).
As you found, you can't use multiple lists of values in a single INSERT statement with Informix.
The simplest solution is to use multiple INSERT statements each with a single list of values.
If you're using an API such as ESQL/C and you are concerned about performance, then you can create an INSERT cursor and use that repeatedly. This saves up the inserts until a buffer is full, or you flush or close the cursor:
$ PREPARE p FROM "INSERT INTO mytmptable VALUES(?, ?, ?)";
$ DECLARE c CURSOR FOR p;
$ OPEN c;
while (...there's more data to process...)
{
$PUT c USING :v1, :v2, :v3;
}
$ CLOSE c;
The variables v1, v2, v3 are host variables to hold the string and numbers to be inserted.
(You can optionally use $ FLUSH c; in the loop if you wish.) Because this buffers the values, it is pretty efficient. Of course, you could also simply use $ EXECUTE p USING :v1, :v2, :v3; in the loop; that foregoes the per-row preparation of the statement, too.
If you don't mind writing verbose SQL, you can use the UNION technique suggested by Matt Hamilton, but you will need a FROM clause in each SELECT with Informix. You might specify:
FROM "informix".systables WHERE tabid = 1, or
FROM sysmaster:"informix".sysdual, or
use some other technique to ensure that the SELECT has a FROM clause but only generates one row of data.
In my databases, I have either a table dual with a single row in it, or a synonym dual that is a synonym for sysmaster:"informix".sysdual. You can get away without the "informix". part of those statements if the database is 'normal'; the owner name is crucial if your database is an Informix MODE ANSI database.
In some versions of Infomix you can build a virtual table using the TABLE keyword followed by a value of one of the COLLECTION data types, such as a LIST collection. In your case, use a LIST of values of Unnamed Row type using the ROW(...) constructor syntax.
Creating a TABLE from COLLECTION value
http://www.ibm.com/support/knowledgecenter/SSGU8G_11.50.0/com.ibm.sqls.doc/ids_sqs_1375.htm
ROW(...) construction syntax, for literals of Unnamed Row data type
http://www.ibm.com/support/knowledgecenter/SSGU8G_11.50.0/com.ibm.sqlr.doc/ids_sqr_136.htm
Example:
select *
from TABLE(LIST{
ROW('7662', 232, 297.26),
ROW('7662', 232, 297.26)
}) T(external_id, int_id, cost_amount)
into temp mytmptable with no log
In the above, the data types are implied by the value, but when needed you can explicitly cast each value to the desired data type in the row constructor, like so:
ROW('7662'::char(10), 232::integer, 297.26::decimal(10,2))
You can also insert multiple rows by storing the values in an external file and executing the following statement in dbaccess:
LOAD FROM "externalfile" INSERT INTO mytmptable;
However, the values would have to be DELIMITED by a pipe "|" symbol, or whatever you set the DBDELIMITER environment variable to be.
If you're using the pipe delimiter, the data in your external file would look like:
7662|232|297.26|
7663|233|297.27|
...
NOTE that the data in the external file must be properly formatted or able to be converted to successfully be inserted into each mytmptable.column datatype.
Here is a simple solution fro bulk insert with SELECT part solving the rest
INSERT INTO cccmte_pp
( cmte, pref, nro, eje, id_tri, id_cuo, fecha, vto1, vto2, id_tit, id_suj, id_bie, id_gru )
SELECT * FROM TABLE (MULTISET {
row('RC', 4, 10, 2020, 1, 5, MDY(05,20,2020), MDY(05,20,2020),MDY(05,27,2020),101, 1, 96, 1 ),
row('RC', 4, 11, 2020, 1, 5, MDY(05,20,2020), MDY(05,20,2020),MDY(05,27,2020),101, 1, 96, 1 ) })
AS t( cmte, pref, nro, eje, id_tri, id_cuo, fecha, vto1, vto2, id_tit, id_suj, id_bie, id_gru )
;

Informix: Select null problem

Using Informix, I've created a tempory table which I am trying to populate from a select statement. After this, I want to do an update, to populate more fields in the tempory table.
So I'm doing something like;
create temp table _results (group_ser int, item_ser int, restype char(4));
insert into _results (group_ser, item_ser)
select
group_ser, item_ser, null
from
sometable
But you can't select null.
For example;
select first 1 current from systables
works but
select first 1 null from systables
fails!
(Don't get me started on why I can't just do a SQL Server like "select current" with no table specified!)
You don't have to write a stored procedure; you simply have to tell IDS what type the NULL is. Assuming you are not using IDS 7.31 (which does not support any cast notation), you can write:
SELECT NULL::INTEGER FROM dual;
SELECT CAST(NULL AS INTEGER) FROM dual;
And, if you don't have dual as a table (you probably don't), you can do one of a few things:
CREATE SYNONYM dual FOR sysmaster:"informix".sysdual;
The 'sysdual' table was added relatively recently (IDS 11.10, IIRC), so if you are using an older version, it won't exist. The following works with any version of IDS - it's what I use.
-- #(#)$Id: dual.sql,v 2.1 2004/11/01 18:16:32 jleffler Exp $
-- Create table DUAL - structurally equivalent to Oracle's similarly named table.
-- It contains one row of data.
CREATE TABLE dual
(
dummy CHAR(1) DEFAULT 'x' NOT NULL CHECK (dummy = 'x') PRIMARY KEY
) EXTENT SIZE 8 NEXT SIZE 8;
INSERT INTO dual VALUES('x');
REVOKE ALL ON dual FROM PUBLIC;
GRANT SELECT ON dual TO PUBLIC;
Idiomatically, if you are going to SELECT from Systables to get a single row, you should include 'WHERE tabid = 1'; this is the entry for Systables itself, and if it is missing, the fact that your SELECT statement does return any data is the least of your troubles. (I've never seen that as an error, though.)
This page says the reason you can't do that is because "NULL" doesn't have a type. So, the workaround is to create a sproc that simply returns NULL in the type you want.
That sounds like a pretty bad solution to me though. Maybe you could create a variable in your script, set it to null, then select that variable instead? Something like this:
DEFINE dummy INT;
LET dummy = NULL;
SELECT group_ser, item_ser, dummy
FROM sometable
SELECT group_ser, item_ser, replace(null,null) as my_null_column
FROM sometable
or you can use nvl(null,null) to return a null for your select statement.
Is there any reason to go for an actual table? I have been using
select blah from table(set{1})
select blah from table(set{1})
is nice when you are using 10.x database. This statement doesn't touch database. The amount of read/write operations is equal to 0,
but
when you're using 11.x it will cost you at least 4500 buffer reads because this version of Informix creates this table in memory and executes query against it.
select to_date(null) from table;
This works when I want to get a date with null value
You can use this expression (''+1) on the SELECT list, instead of null keyword. It evaluates to NULL value of type DECIMAL(2,0).
This (''+1.0001) evaluates to DECIMAL(16,4). And so on.
If you want DATE type use DATE(''+1) to get null value of type DATE.
(''+1)||' ' evaluates to an empty string of type VARCHAR(1).
To obtain NULL value of type VARCHAR(1) use this expression:
DATE(''+1)||' '
Works in 9.x and 11.x.