H2 MERGE INTO ... USING with multiple values, do NOT update if exists - sql

I am currently writing a data.sql SQL-script for my spring-boot application. I want it to fill in default values for various tables (e.g. on initial start-up).
I have set spring.jpa.hibernate.ddl-auto=update in the application.properties to keep the contents of the database, but the data.sql is executed each time the service boots up.
Now I am looking for a way to insert rows if they do not exist without updating them if they do exist.
I am using H2Dialect (and a H2-database).
Initially I wanted to use some sort of "IF NOT EXISTS"-statement along with SELECT COUNT(*) FROM table, but it seems H2 does not support this directly.
So the next best thing to do is to use MERGE INTO, but this does not seem to work as expected and I do not understand why so.
This is what my script looks like, but sadly it does not work as expected:
MERGE INTO Languages AS T USING (SELECT * FROM Languages) AS S ON (T.ID = S.ID)
WHEN NOT MATCHED THEN
INSERT (LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE) VALUES
('en', 'English', 'USA', TRUE, TRUE),
('de', 'German', 'Deutschland', FALSE, FALSE);
I'd like to add these values either if they are absent (which is probably easier) or if there are no values present in the Languages-table (which is what I would prefer).
This is the (shortened) exception I receive when I start my spring-boot application:
Caused by: org.h2.jdbc.JdbcSQLException: Syntax Fehler in SQL Befehl "MERGE INTO LANGUAGES AS[*] T USING (SELECT * FROM LANGUAGES) AS S ON (T.ID = S.ID) WHEN NOT MATCHED THEN INSERT (LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE) VALUES ('en', 'English', 'USA', TRUE, TRUE), ('de', 'German', 'Deutschland', FALSE, FALSE) "; erwartet "., (, KEY, VALUES, (, WITH, SELECT, FROM"
Syntax error in SQL statement "MERGE INTO LANGUAGES AS[*] T USING (SELECT * FROM LANGUAGES) AS S ON (T.ID = S.ID) WHEN NOT MATCHED THEN INSERT (LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE) VALUES ('en', 'English', 'USA', TRUE, TRUE), ('de', 'German', 'Deutschland', FALSE, FALSE) "; expected "., (, KEY, VALUES, (, WITH, SELECT, FROM"; SQL statement:
MERGE INTO Languages AS T USING (SELECT * FROM Languages) AS S ON (T.ID = S.ID) WHEN NOT MATCHED THEN INSERT (LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE) VALUES ('en', 'English', 'USA', TRUE, TRUE), ('de', 'German', 'Deutschland', FALSE, FALSE) [42001-196]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345) ~[h2-1.4.196.jar:1.4.196]
at org.h2.message.DbException.getSyntaxError(DbException.java:205) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.getSyntaxError(Parser.java:541) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parseSelectSimple(Parser.java:2073) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parseSelectSub(Parser.java:1940) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parseSelectUnion(Parser.java:1755) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parseSelect(Parser.java:1743) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parseMerge(Parser.java:1053) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parsePrepared(Parser.java:423) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parse(Parser.java:321) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.parse(Parser.java:297) ~[h2-1.4.196.jar:1.4.196]
at org.h2.command.Parser.prepareCommand(Parser.java:258) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Session.prepareLocal(Session.java:578) ~[h2-1.4.196.jar:1.4.196]
at org.h2.engine.Session.prepareCommand(Session.java:519) ~[h2-1.4.196.jar:1.4.196]
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1204) ~[h2-1.4.196.jar:1.4.196]
at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:176) ~[h2-1.4.196.jar:1.4.196]
at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:164) ~[h2-1.4.196.jar:1.4.196]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_121]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_121]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_121]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121]
at org.apache.tomcat.jdbc.pool.StatementFacade$StatementProxy.invoke(StatementFacade.java:114) ~[tomcat-jdbc-8.5.27.jar:na]
at com.sun.proxy.$Proxy97.execute(Unknown Source) ~[na:na]
at org.springframework.jdbc.datasource.init.ScriptUtils.executeSqlScript(ScriptUtils.java:470) ~[spring-jdbc-4.3.14.RELEASE.jar:4.3.14.RELEASE]
... 78 common frames omitted
The exception stacktrace is huge, but the "caused by"-parts basically say, that beans could not be created due to the deepest exception, the "caused by"-clause of which I posted above. If anyone needs more details, please let me know.
Edit: or is there a best practice for initializing data (such as default user or default anything)? Preferably one I can reuse if a database reset is performed.
Edit (25.04.2018): I figured something might have gone wrong with my Language-class, because the SQL for schema creation looks a little odd. Here it is:
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.particles.authservice.jwtservice.JSONHelper;
import com.particles.authservice.languageservice.converters.LanguageDeserializer;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* This class describes the entity for a language.
*/
#JsonDeserialize(using = LanguageDeserializer.class)
#Data
#NoArgsConstructor
#Entity(name = "Languages")
public class Language {
private static Language defaultLanguage;
#Id
#Column(nullable = false, unique = true)
private String languageCode;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private String i18nName;
#Column(nullable = false)
private boolean localizeUi = false;
#Column(nullable = false)
private boolean canChoose = false;
/**
* This method sets the class attribute {#link Language#defaultLanguage}.
*
* #param defaultLanguage
* ({#link Language}) default language
*/
public static void setDefaultLanguage(final Language defaultLanguage) {
Language.defaultLanguage = defaultLanguage;
}
/**
* #return ({#link Language}) default language
*/
public static Language getDefaultLanguage() {
return Language.defaultLanguage;
}
}

Now I am looking for a way to insert rows if they do not exist without updating them if they do exist.
You could use INSERT INTO ... SELECT ...:
INSERT INTO Languages(LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE)
SELECT LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE
FROM (SELECT 'en' AS Language_code, 'English' AS name,
'USA' AS I18N_NAME, TRUE AS LOCALIZE_UI, TRUE AS CAN_CHOOSE
UNION ALL
SELECT 'de', 'German', 'Deutschland', FALSE, FALSE
) sub
WHERE NOT EXISTS (SELECT 1
FROM Languages l
WHERE sub.Language_code = l.Language_code);
Assuming that LANGUAGE_CODE is UNIQUE, otherwise you need to explicitly specify ID column.
Alternatively using MINUS/EXCEPT:
INSERT INTO Languages(LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE)
SELECT LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE
FROM (SELECT 'en' AS Language_code, 'English' AS name,
'USA' AS I18N_NAME, TRUE AS LOCALIZE_UI, TRUE AS CAN_CHOOSE
UNION ALL
SELECT 'de', 'German', 'Deutschland', FALSE, FALSE
) sub
EXCEPT
SELECT LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE
FROM Languages;
EDIT:
Is there for instance a way to avoid UNION?
I am not sure if H2 supports VALUES clause:
SELECT LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE
FROM (SELECT 'en' AS Language_code, 'English' AS name,
'USA' AS I18N_NAME, TRUE AS LOCALIZE_UI, TRUE AS CAN_CHOOSE
UNION ALL
SELECT 'de', 'German', 'Deutschland', FALSE, FALSE
) sub
<=>
SELECT *
FROM (VALUES('en', 'English', 'USA', TRUE, TRUE),
('de', 'German', 'Deutschland', FALSE, FALSE)
) sub(LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE)
SQLFiddle Demo

Based on your requirements, you should be able to use MERGE statement without using WHEN NOT MATCHED clause.
MERGE:
Updates existing rows, and insert rows that don't exist. If no key
column is specified, the primary key columns are used to find the row.
If more than one row per new row is affected, an exception is thrown.
MERGE INTO Languages
KEY(LANGUAGE_CODE, NAME, I18N_NAME, LOCALIZE_UI, CAN_CHOOSE) VALUES
('en', 'English', 'USA', TRUE, TRUE),
('de', 'German', 'Deutschland', FALSE, FALSE);
Let me know if this still doesn't work for you.

Related

Nodejs Mysql Insert is adding backslashes to strings and fails

I'm trying to do a simple sql insert using nodejs and express trying various formats I've found online, but this appears to be the accepted way. When I view the error it's adding extra backslashes to the query and failing.
The code:
console.log(request.body);
var post = {uid: request.session.uid, title: request.body.title, created: request.body.createdAt};
connection.query('INSERT INTO projects (uid, title, created) SET ? ', post, function (error, results, fields) {
console.log(error);
});
The first body console.log:
{ title: 'aewgawegr', createdAt: '1574219119301' }
The error message:
sqlMessage:
'You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near \'SET `uid` = 1, `title` = \'aewgawegr\', `created` = \'1574219119301\'\' at line 1',
sqlState: '42000',
index: 0,
sql:
'INSERT INTO projects (uid, title, created) SET `uid` = 1, `title` = \'aewgawegr\', `created` = \'1574219119301\' ' }
For reference: https://dev.mysql.com/doc/refman/8.0/en/insert.html
You cannot combine the two usage syntax:
INSERt INTO `table` (column1, ...) VALUES (value1, ...
with
INSERT INTO `table` SET `column1`='value1', ....
You can do something like this instead of passing json object
"INSERT INTO projects (uid, title, created) VALUES (1, 'Michelle', 'Blue Village 1')";
e.g in your case you can use string interpolation:
`INSERT INTO projects (uid, title, created) VALUES ('${request.session.uid}', '${request.body.title}', '${request.body.createdAt}')`;

Querydsl--using postgresql's values

Can I translate this sql into querydsl form?
select count(ppe),v.name
from personal_progress_entity ppe left join user_detail_entity ude
on ppe.student_entity_id=ude.user_id
right join (values ('aaa'),('bbb'),('ccc'),('ddd')) as v(name)
on ude.people_category=v.name
group by v.name;
The PostgreSQL VALUES function is not supported by querydsl. However, you can get the same result using a UNION.
CREATE TABLE personal_progress_entity(student_entity_id INTEGER);
INSERT INTO personal_progress_entity VALUES (1);
CREATE TABLE user_detail_entity(user_id INTEGER, people_category VARCHAR);
INSERT INTO user_detail_entity VALUES (1, 'aaa');
INSERT INTO user_detail_entity VALUES (1, 'bbb');
SELECT COUNT(personal_progress_entity.student_entity_id),
name.value_alias
FROM personal_progress_entity personal_progress_entity
LEFT JOIN user_detail_entity user_detail_entity ON personal_progress_entity.student_entity_id = user_detail_entity.user_id
RIGHT JOIN ((SELECT 'aaa' AS value_alias)
UNION
(SELECT 'bbb' AS value_alias)
UNION
(SELECT 'ccc' AS value_alias)
UNION
(SELECT 'ddd' AS value_alias)) AS name
ON name.value_alias = user_detail_entity.people_category
GROUP BY name.value_alias;
Gives:
1 "aaa"
1 "bbb"
0 "ddd"
0 "ccc"
Here's my querydsl-sql implementation. I've added the private static <T> Union<T> union(PathBuilder<T> pathBuilder, T... values) method to reduce boilerplate.
public List<Tuple> stackoverflowAnswer() {
PathBuilder<String> valueAlias = new PathBuilder<>(String.class, "value_alias");
PathBuilder<String> name = new PathBuilder<>(String.class, "name");
return query().select(personalProgressEntity.studentEntityId.count(), name.get(valueAlias))
.from(personalProgressEntity)
.leftJoin(userDetailEntity).on(personalProgressEntity.studentEntityId.eq(userDetailEntity.userId))
.rightJoin(union(valueAlias, "aaa", "bbb", "ccc", "ddd"), name).on(name.get(valueAlias).eq(userDetailEntity.peopleCategory))
.groupBy(name.get(valueAlias))
.fetch();
}
private static <T> Union<T> union(PathBuilder<T> pathBuilder, T... values) {
return SQLExpressions.union(
Stream.of(values)
.map(p -> SQLExpressions.select(Expressions.constantAs(p, pathBuilder)))
.collect(Collectors.toList()));
}

Access Append Query - Only add certain types

I have a database where a user pastes across values from another software into an access table named "NAVImportTable". Upon clicking an update button the following append, update and delete queries are ran to import this data into the table "ProductionOrderLineEnquiry";
NAVImportAdd
INSERT INTO ProductionOrderLineEnquiry
(ProdOrderNo,
ItemNo,
SalesOrderNo,
Description,
ExtraDescription,
Quantity,
DueDate,
ExpectedDeliveryDate,
ItemWeight,
Status)
SELECT NAVImportTable.ProdOrderNo,
NAVImportTable.ItemNo,
NAVImportTable.SalesOrderNo,
NAVImportTable.Description,
NAVImportTable.ExtraDescription,
NAVImportTable.Quantity,
NAVImportTable.DueDate,
NAVImportTable.ExpectedDelivery,
NAVImportTable.ItemWeight,
NAVImportTable.Status
FROM NAVImportTable;
NAVImportUpdate
UPDATE ProductionOrderLineEnquiry AS a
INNER JOIN NAVImportTable AS e
ON a.[ProdOrderNo] = e.[ProdOrderNo]
SET a.Cut = IIF(e.Status = 'Finished', True, False),
a.Folded = IIF(e.Status = 'Finished', True, False),
a.Finished = IIF(e.Status = 'Finished', True, False),
a.NAVComplete = IIF(
a.NAVComplete IS NULL,
IIF(e.Status = 'Finished', DATE(), ),
a.NAVComplete ),
a.DueDate = e.DueDate,
a.ExpectedDeliveryDate = e.ExpectedDelivery,
a.Status = e.Status;
NAVImportDelete
DELETE *
FROM NAVImportTable;
The item number is related to the same number within the table "ImportProductAndTypes", under the heading "No". What this is for is so that when later queries are ran, the product can be assigned its specifications ie the "type" of product that it is.
My question is;
How do I get it so that when the NAVImportAdd is ran it assigns the "Type" from "ImportProductAndTypes" and that anything with the value of "Box" for the "Type" heading is not added to the table "ProductionOrderLineEnquiry".

Update multiple rows in same query using PostgreSQL

I'm looking to update multiple rows in PostgreSQL in one statement. Is there a way to do something like the following?
UPDATE table
SET
column_a = 1 where column_b = '123',
column_a = 2 where column_b = '345'
You can also use update ... from syntax and use a mapping table. If you want to update more than one column, it's much more generalizable:
update test as t set
column_a = c.column_a
from (values
('123', 1),
('345', 2)
) as c(column_b, column_a)
where c.column_b = t.column_b;
You can add as many columns as you like:
update test as t set
column_a = c.column_a,
column_c = c.column_c
from (values
('123', 1, '---'),
('345', 2, '+++')
) as c(column_b, column_a, column_c)
where c.column_b = t.column_b;
sql fiddle demo
Based on the solution of #Roman, you can set multiple values:
update users as u set -- postgres FTW
email = u2.email,
first_name = u2.first_name,
last_name = u2.last_name
from (values
(1, 'hollis#weimann.biz', 'Hollis', 'Connell'),
(2, 'robert#duncan.info', 'Robert', 'Duncan')
) as u2(id, email, first_name, last_name)
where u2.id = u.id;
Yes, you can:
UPDATE foobar SET column_a = CASE
WHEN column_b = '123' THEN 1
WHEN column_b = '345' THEN 2
END
WHERE column_b IN ('123','345')
And working proof: http://sqlfiddle.com/#!2/97c7ea/1
For updating multiple rows in a single query, you can try this
UPDATE table_name
SET
column_1 = CASE WHEN any_column = value and any_column = value THEN column_1_value end,
column_2 = CASE WHEN any_column = value and any_column = value THEN column_2_value end,
column_3 = CASE WHEN any_column = value and any_column = value THEN column_3_value end,
.
.
.
column_n = CASE WHEN any_column = value and any_column = value THEN column_n_value end
if you don't need additional condition then remove and part of this query
Let's say you have an array of IDs and equivalent array of statuses - here is an example how to do this with a static SQL (a sql query that doesn't change due to different values) of the arrays :
drop table if exists results_dummy;
create table results_dummy (id int, status text, created_at timestamp default now(), updated_at timestamp default now());
-- populate table with dummy rows
insert into results_dummy
(id, status)
select unnest(array[1,2,3,4,5]::int[]) as id, unnest(array['a','b','c','d','e']::text[]) as status;
select * from results_dummy;
-- THE update of multiple rows with/by different values
update results_dummy as rd
set status=new.status, updated_at=now()
from (select unnest(array[1,2,5]::int[]) as id,unnest(array['a`','b`','e`']::text[]) as status) as new
where rd.id=new.id;
select * from results_dummy;
-- in code using **IDs** as first bind variable and **statuses** as the second bind variable:
update results_dummy as rd
set status=new.status, updated_at=now()
from (select unnest(:1::int[]) as id,unnest(:2::text[]) as status) as new
where rd.id=new.id;
Came across similar scenario and the CASE expression was useful to me.
UPDATE reports SET is_default =
case
when report_id = 123 then true
when report_id != 123 then false
end
WHERE account_id = 321;
Reports - is a table here, account_id is same for the report_ids mentioned above. The above query will set 1 record (the one which matches the condition) to true and all the non-matching ones to false.
The answer provided by #zero323 works great on Postgre 12. In case, someone has multiple values for column_b (referred in OP's question)
UPDATE conupdate SET orientation_status = CASE
when id in (66934, 39) then 66
when id in (66938, 49) then 77
END
WHERE id IN (66934, 39, 66938, 49)
In the above query, id is analogous to column_b; orientation_status is analogous to column_a of the question.
In addition to other answers, comments and documentation, the datatype cast can be placed on usage. This allows an easier copypasting:
update test as t set
column_a = c.column_a::number
from (values
('123', 1),
('345', 2)
) as c(column_b, column_a)
where t.column_b = c.column_b::text;
#Roman thank you for the solution, for anyone using node, I made this utility method to pump out a query string to update n columns with n records.
Sadly it only handles n records with the same columns so the recordRows param is pretty strict.
const payload = {
rows: [
{
id: 1,
ext_id: 3
},
{
id: 2,
ext_id: 3
},
{
id: 3,
ext_id: 3
} ,
{
id: 4,
ext_id: 3
}
]
};
var result = updateMultiple('t', payload);
console.log(result);
/*
qstring returned is:
UPDATE t AS t SET id = c.id, ext_id = c.ext_id FROM (VALUES (1,3),(2,3),(3,3),(4,3)) AS c(id,ext_id) WHERE c.id = t.id
*/
function updateMultiple(table, recordRows){
var valueSets = new Array();
var cSet = new Set();
var columns = new Array();
for (const [key, value] of Object.entries(recordRows.rows)) {
var groupArray = new Array();
for ( const [key2, value2] of Object.entries(recordRows.rows[key])){
if(!cSet.has(key2)){
cSet.add(`${key2}`);
columns.push(key2);
}
groupArray.push(`${value2}`);
}
valueSets.push(`(${groupArray.toString()})`);
}
var valueSetsString = valueSets.join();
var setMappings = new String();
for(var i = 0; i < columns.length; i++){
var fieldSet = columns[i];
setMappings += `${fieldSet} = c.${fieldSet}`;
if(i < columns.length -1){
setMappings += ', ';
}
}
var qstring = `UPDATE ${table} AS t SET ${setMappings} FROM (VALUES ${valueSetsString}) AS c(${columns}) WHERE c.id = t.id`;
return qstring;
}
I don't think the accepted answer is entirely correct. It is order dependent. Here is an example that will not work correctly with an approach from the answer.
create table xxx (
id varchar(64),
is_enabled boolean
);
insert into xxx (id, is_enabled) values ('1',true);
insert into xxx (id, is_enabled) values ('2',true);
insert into xxx (id, is_enabled) values ('3',true);
UPDATE public.xxx AS pns
SET is_enabled = u.is_enabled
FROM (
VALUES
(
'3',
false
,
'1',
true
,
'2',
false
)
) AS u(id, is_enabled)
WHERE u.id = pns.id;
select * from xxx;
So the question still stands, is there a way to do it in an order independent way?
---- after trying a few things this seems to be order independent
UPDATE public.xxx AS pns
SET is_enabled = u.is_enabled
FROM (
SELECT '3' as id, false as is_enabled UNION
SELECT '1' as id, true as is_enabled UNION
SELECT '2' as id, false as is_enabled
) as u
WHERE u.id = pns.id;

Converting CASE WHEN into IIF

I mainly work with SQL Server, and rarely use Access. I have case statement in SQL server that I need to turn into a nested IIF statement in Access and I am having a hard time getting it to work. The SQL Code is:
(CASE
WHEN (RRDD = '2029'
THEN 'IS'
WHEN RRDD = '2214' OR '2219' OR '2220' OR '2221' OR '2230' OR '2265'
THEN 'AIR'
WHEN RRDD = '2044' OR '2323' OR '2327' OR '2331' OR '2339'
THEN 'LogDist'
WHEN RRDD = '2037'
THEN 'MailInn'
WHEN RRDD = '2213' OR '2307' OR '2311' OR '2332' OR '2334' OR '2338'
OR '2705' OR '2706'
THEN 'GFF'
WHEN RRDD = '2010'
THEN 'Corp'
WHEN RRDD = '2040' OR '2041' OR '2081' OR '2086'
THEN 'Cap'
ELSE NULL
END) AS RegDIs
This case statement is crazy -- consider moving into an external table. It actual won't run as is -- for example, you have an extra parentheses and are using OR incorrectly.
With that said, basically you need to replace WHEN with IIF( and THEN with comma and include your next IIF as the final paramater -- this should be close:
(IIF(RRDD = '2029', 'IS',
IIF(RRDD IN ('2214', '2219', '2220', '2221', '2230', '2265'), 'AIR',
IIF(RRDD IN ('2044', '2323', '2327', '2331', '2339'), 'LogDist',
IIF(RRDD = '2037', 'MailInn',
IIF(RRDD IN ('2213', '2307', '2311', '2332', '2334', '2338', '2705', '2706'), 'GFF',
IIF(RRDD = '2010', 'Corp',
IIF(RRDD IN ('2040', '2041', '2081', '2086'), 'Cap',
NULL)))))))) AS RegDIs
Consider Switch as an alternative to multiple IIf expressions.
Switch
(
RRDD = '2029', 'IS',
RRDD IN ('2214','2219','2220','2221','2230','2265'), 'AIR',
RRDD IN ('2044','2323','2327','2331','2339'), 'LogDist',
RRDD = '2037', 'MailInn',
RRDD IN ('2213','2307','2311','2332','2334','2338','2705','2706'), 'GFF',
RRDD = '2010', 'Corp',
RRDD IN ('2040','2041','2081','2086'), 'Cap'
) AS RegDIs
With Switch, when none of the condition match, the function returns Null.
I find Switch easier to understand especially when the number of IIfs is as large as you need for this.
Still, either the Switch or IIf approach amounts to writing data into the SQL statement. As others mentioned, I think a lookup table would be a better approach.
RRDD RegDIs
2029 IS
2214 AIR
2219 AIR
2220 AIR
It should be easier to edit the table when needed instead of revising a complex query.
Your original code is incorrect. You cannot say RRDD = '2044' OR '2323'. You can say: RRDD = '2044' OR RRDD = '2323'. or, you can use the in.
The access function iif only has the "then" and "else" clause. So, you have to nest the calls.
This makes the code a bit more cumbersome. And, keeping track of the closing parentheses can be a nightmare. As I suggest in my comment, a small reference table would be a much more elegant solution.
Here is the code using iif:
select iif(RRDD = '2029', 'IS',
iif(RRDD in ('2214', '2219', '2220', '2221', '2230', '2265'), 'AIR',
iif(RRDD in ('2044', '2323', '2327', '2331', '2339'), 'LogDist',
iif(RRDD = '2037', 'MailInn',
iif(RRDD in ('2213', '2307', '2311', '2332', '2334', '2338', '2705', '2706'), 'GFF',
iif(RRDD = '2010', 'Corp',
iif(RRDD in ('2040', '2041', '2081', '2086'), 'Cap', NULL
)
)
)
)
)
)
) AS RegDI)