How to do an insert with multiple rows in Informix SQL? - 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 )
;

Related

Is there an easier version of this SQL script?

I'm trying to run a script on an Oracle Database via SQL Developer. There are five columns: ID, LAST_NAME, FIRST_NAME, USERID, and SALARY. This is the code I've written to accept values and edit the USERID field with the lowercase of the 1st letter of the FIRST_NAME and the whole LAST_NAME.
INSERT INTO ACT_MY_EMPLOYEE
VALUES (&P_ID, '&P_LAST_NAME', '&P_FIRST_NAME',
LOWER(SUBSTR('&P_FIRST_NAME', 1, 1) ||
SUBSTR('&P_LAST_NAME', 1, 7)), &P_SALARY);
Is there an easier version to this? Thank you!
You should definitely use && instead of & as single & will ask you for the value each time it is uses in your sql while double & will store substitution value (asks for value only once) and use it if same variable is used second time in the same session.
INSERT INTO ACT_MY_EMPLOYEE
VALUES (&P_ID, '&&P_LAST_NAME', '&&P_FIRST_NAME',
LOWER(SUBSTR('&&P_FIRST_NAME', 1, 1) ||
SUBSTR('&&P_LAST_NAME', 1, 7)), &P_SALARY);
Cheers!!
Maybe a stored procedure would be a good idea, especially if you frequently add new rows. Instead of keeping the INSERT INTO statement in some script (and have to remember where you put it), a procedure is permanently stored in the database.
It might look like this:
create or replace procedure p_ins_ame (
par_id in act_my_employee.id%type,
par_first_name in act_my_employee.first_name%type,
par_last_name in act_my_employee.last_name%type,
par_salary in act_my_employee.salary%type)
is
begin
insert into act_my_employee (id,
last_name,
first_name,
some_column,
salary)
values (
par_id,
par_last_name,
par_first_name,
lower (
substr (par_first_name, 1, 1)
|| substr (par_last_name, 1, 7)),
par_salary);
END;
You'd call it as
begin
p_ins_ame (par_id => 100,
par_first_name => 'Little',
par_last_name => 'Foot',
par_salary => 1000);
end;
Of course, it can be further developed so that it inserts a sequence number instead of a "fixed" ID value (or, if your database version supports it, use an identity column). Any changes you make will be used in all subsequent procedure calls.
As of a single INSERT INTO statement: as you've already been told, certain tools (such as SQL*Plus) allow you to use two ampersands (&&) along with the variable name. It means that you won't be prompted to enter the same variable's value twice (or as many times as it appears in that script), but only once.
That's OK, just beware! If you enter a slash (/) at the prompt and hit the ENTER key, you'll insert the same row once again (unless it fails on uniqueness). Don't forget to UNDEFINE those variables!

SQLITE_RANGE: bind or column out of range for INSERT statement

I want to insert rows into a SQLite3 table using the knex.raw method. Unfortunately I get a 'SQLITE_RANGE' error, which makes my test fail.
I have verified the bindings passed to the raw query in the following fashion:
They respect the order of the INSERT statement
They respect the specified column types
They respect the number of bindings requested in the raw query
Beyond that I have looked online, but couldn't find a solution to my issue. Below are the details of the operation attempted:
Engine: sqlite3 ^3.1.13
SQL Client: knex ^0.14.4
Environment: electron ^1.7.11
Error:
SQLITE_RANGE: bind or column index out of range errno: 25, code: 'SQLITE_RANGE'
Table definition:
-- --------------------------------------------------------
--
-- Table structure for table `ds13odba`
--
CREATE TABLE IF NOT EXISTS `ds13odba` (
`SURGERY_CODE` VARCHAR(6) ,
`TYPE` VARCHAR(1) ,
`FP59STALIB` VARCHAR(6) ,
`ID` INT UNSIGNED NOT NULL ,
`createdAt` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL,
`updatedAt` DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL
);
-- --------------------------------------------------------
*Take note that the column types defined here are affinity types, i.e. MySQL types. These are valid in SQLite3 and are casted and optimized by the engine to their equivalent in SQLite3.
Query:
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE) VALUES (?,?,?,?);
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE) VALUES (?,?,?,?);
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE) VALUES (?,?,?,?);
Bindings:
[
'047202', 1, '000001', 'D',
'047203', 2, '000002', 'D',
'047204', 3, '000003', 'D'
]
Calling code:
await knex.raw(...convertToInsertSQL(records));
Which resolves to:
await knex.raw(insertStatements.join('\n'), bindings);
Could you help me with this issue?
Cheers 🦋
The issue stems from SQLite3's lack of support of multi-statements per exec() call, as documented here.
After some testing on my end, I discovered that the SQLite3 engine will assign automatically all the bindings to the first statement of the prepared SQL. Any following statements will be ignored.
This still applies for transactions, as the bindings will be applied to the 'BEGIN TRANSACTION;' statement rather than to the following statements.
The solution is to use a compound INSERT statement with bindings.
Hence this:
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE) VALUES (?,?,?,?);
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE) VALUES (?,?,?,?);
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE) VALUES (?,?,?,?);
becomes this:
INSERT INTO `ds13odba` (FP59STALIB, ID, SURGERY_CODE, TYPE)
VALUES (?,?,?,?), (?,?,?,?), (?,?,?,?);
*Bear in mind that compound INSERT statements are only available as of version 3.7.11 of the SQLite3 engine.
I don't see anything obviously wrong with the info you've posted, but you haven't posted the actual .raw() statements, which would help with debugging.
So attempting to assist, I would suggest that you add an .on('query-error'... clause like that below, which will log the SQL that is failing. Many times this will make the problem obvious.
knex.raw(...your-stuff...)
.on('query-error', function(ex, obj) {
console.log("KNEX-query-error ex:", ex, "obj:", obj);
})
Good luck!

Advantage Database, case sensitivity, and collations

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).

PL/SQL varchar(10) to varchar(9)

How would I write a loop that has a select in the “in” clause of the loop that selects a col of type varchar(10), but then inserts those values in a col that wants them to be varchar(9)? Basically I’m trying to “typecast” from one precision to another, if that makes any sense. Example:
FOR V_TEN IN (SELECT THIS_IS_VARCHAR_TEN FROM TABLE WHERE SOMETHING=’VALUE’)
LOOP
INSERT INTO OTHER_TABLE
(THIS_IS_VARCHAR_NINE)
VALUES
(V_TEN);
END LOOP;
The error is that the column types aren’t the same. I’ve tried looking at to_char() and cast() but neither seem to be what I want. I realize there is a loss of precision here and am okay with that, since I actually know that the values in the varchar(10) column are always going to be 9 chars.
You are looking for the SUBSTR function.
Also, do not use PL/SQL for this, plain SQL will do and be faster.
INSERT INTO OTHER_TABLE
SELECT OTHER_COLUMN, SUBSTR(THIS_IS_VARCHAR_TEN,1,9)
FROM TABLE WHERE SOMETHING=’VALUE’;
And if there are really no values longer than nine character, you do not even need to call the substr function (it will be converted automatically, and raise an error if too long).
since I actually know that the values
in the varchar(10) column are always
going to be 9 chars.
If that's true, then you don't even need to use SUBSTR as others have been suggesting.
I believe the reason that you're getting an error is that you are trying to insert the value of V_TEN. When you use a construct like FOR x IN (SELECT ...) LOOP, x is implicitly declared as a record type. In your case, it's a record with only one field, but you still can't use it directly as a scalar type.
You just need to reference the field of the record by name in your insert.
FOR V_TEN IN (SELECT THIS_IS_VARCHAR_TEN FROM TABLE WHERE SOMETHING=’VALUE’)
LOOP
INSERT INTO OTHER_TABLE
(THIS_IS_VARCHAR_NINE)
VALUES
(V_TEN.THIS_IS_VARCHAR_TEN);
END LOOP;
In any case, as Thilo pointed out, there's no reason to do this in an explicit loop at all. Just write it as a single INSERT ... SELECT.
Use:
FOR V_TEN IN (SELECT SUBSTR(t.this_is_varchar_ten, 1, 9)
FROM TABLE t
WHERE t.something = 'VALUE')
LOOP
INSERT INTO OTHER_TABLE
(THIS_IS_VARCHAR_NINE)
VALUES
(V_TEN);
END LOOP;
Use the SUBSTR function to substring the VARCHAR(10) data so it is returned as VARCHAR(9)

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.