Converting CASE WHEN into IIF - sql

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)

Related

transform SQL to createQueryBuilder

I'm trying to transform this sql
SELECT * FROM user_account
LEFT JOIN brand ON user_account.id = brand.user_id_id
LEFT JOIN influencer ON user_account.id = influencer.user_id_id'
to a createQueryBuilder, I try this but it's not working
$qb = $this->createQueryBuilder('u')
->from(Brand::class, 'brand')
->from(Influencer::class, 'influencer')
->leftJoin('brand', 'b', 'ON', 'u.id = b.user_id_id')
->leftJoin('influencer', 'i', 'ON', 'u.id = i.user_id_id');
$query = $qb->getQuery();
return $query->execute();
And I got this error
[Semantical Error] line 0, col 42 near 'brand b ON u.id': Error: Class
'brand' is not defined.
Someone can help?
Many thanks
I have never used doctrine query builder, but after reading the docs I would suggest the following solution:
$qb = $this->createQueryBuilder()
->from('user_account', 'u')
->leftJoin('u', 'brand', 'b', 'u.id = b.user_id_id')
->leftJoin('u', 'influencer', 'i', 'u.id = i.user_id_id')
$query = $qb->getQuery();
return $query->execute();
NOTE: I left b.user_id_id as it is in your example, even though I think there is a typo. Maybe this should mean b.user_id. The same for influencer.

How count by the first letters in Laravel Query Builder?

I want to make a count by the first letters... I have this column
I would like to count each OE rows and each GICS rows
I'm working with this query
$data4 = DB::table('incidencias')
->select(DB::raw('grupo_asig as grupo_asig'), DB::raw('count(*) as number'))
->whereNotIn('grupo_asig', [''])
->groupBy('grupo_asig')
->orderBy('number', 'desc')
->get();
Use CASE WHEN and count the field like OE and ASIG
$data4 = DB::table('incidencias')
->select(DB::raw("(CASE WHEN grupo_asig LIKE 'OE%' THEN 'OE'
WHEN grupo_asig LIKE 'GICS%' THEN 'GICS'
END) AS grupo_asig_type"),
DB::raw('COUNT(*) as number'))
->whereNotIn('grupo_asig', [''])
->groupBy('grupo_asig_type')
->orderBy('number', 'desc')
->get();
You should try to use the [LIKE][1] function then and add it to your query:
->where('grupo_asig', 'like', 'OE%')
->where('grupo_asig', 'like', 'GICS%')
Edit:
I tried a lot around and came to this solution and made a SQL fiddle: http://sqlfiddle.com/#!9/06a39b/8
Does it help you?
You could use Collections. No real need to change your query much.
$data4 = DB::table('incidencias')
->select('grupo_asig')
->selectRaw('count(*) as number'))
->whereNotIn('grupo_asig', [''])
->groupBy('grupo_asig')
// ->orderBy('number', 'desc') Unless you use this array somewhere, it's not needed.
->get();
use Illuminate\Support\Str;
...
// php >= 7.4.0
$oe_count = $data4->filter(fn($data) => Str::startsWith($data->grupo, 'OE '))->count();
$gigs_count = $data4->filter(fn($data) => Str::startsWith($data->grupo, 'GIGS '))->count();
// php < 7.4.0
$oe_count = $data4->filter(function ($data) {
return Str::startsWith($data->grupo, 'OE ');
})->count();
$gigs_count = $data4->filter(function ($data) {
return Str::startsWith($data->grupo, 'GIGS ');
})->count();
Starting with Laravel 6, you can also use cursor() instead of get() in your query to return a LazyCollection. It's faster for this scenario.
I would suggest using a query for that:
refer to this answer
SELECT
LEFT(grupo_asig, 1) AS first_letter,
COUNT(*) AS total
FROM incidencias
GROUP BY first_letter

ActiveRecord has_many with multiple conditions

I'm trying to find an object by checking for several of its relations.
Loan.joins(:credit_memo_attributes)
.where(credit_memo_attributes: {name: 'pr2_gtx1_y', value: '2014'})
.where(credit_memo_attributes: {name: 'pr1_gtx1_y', value: '2013'})
.where(credit_memo_attributes: {name: 'tx1_y', value: '2014'})
Calling to_sql on that gives:
"SELECT `loans`.* FROM `loans` INNER JOIN `credit_memo_attributes`
ON `credit_memo_attributes`.`loan_id` = `loans`.`id`
WHERE `credit_memo_attributes`.`name` = 'pr2_gtx1_y' AND `credit_memo_attributes`.`value` = '2014'
AND `credit_memo_attributes`.`name` = 'pr1_gtx1_y' AND `credit_memo_attributes`.`value` = '2013'
AND `credit_memo_attributes`.`name` = 'tx1_y' AND `credit_memo_attributes`.`value` = '2014'"
So, I'm checking for Loans that have credit_memo_attributes with all of those attributes. I know at least 1 of our 20,000 loans meets this criteria, but this query returns an empty set. If I only use 1 of the where clauses, it returns several, as I'd expect, but once I add even 1 more, it's empty.
Any idea where I'm going wrong?
Update:
Based on comments I believe you want multiple joins in your criteria. You can do that like this:
attr_1 = {name: 'pr2_gtx1_y', value: '2014'}
attr_2 = {name: 'pr1_gtx1_y', value: '2013'}
attr_3 = {name: 'tx1_y', value: '2014'}
Loan.something_cool(attr_1, attr_2, attr_3)
class Loan < ActiveRecord::Base
...
def self.something_cool(attr_1, attr_2, attr_3)
joins(sanitize_sql(["INNER JOIN credit_memo_attributes AS cma1 ON cma1.loan_id = loans.id AND cma1.name = :name AND cma1.value = :value", attr_1]))
.joins(sanitize_sql(["INNER JOIN credit_memo_attributes AS cma2 ON cma2.loan_id = loans.id AND cma2.name = :name AND cma2.value = :value", attr_2]))
.joins(sanitize_sql(["INNER JOIN credit_memo_attributes AS cma3 ON cma3.loan_id = loans.id AND cma3.name = :name AND cma3.value = :value", attr_3]))
end
If you look at the SQL generated (that you included in your question, thank you) you'll see that all those conditions are being ANDed together. There are NO rows for which name = 'pr2_gtx1_y' AND name = 'pr1_gtx1_y' (and so forth). So you are getting the result I would expect (no rows).
You can put all names and values into array like ids and years and pass those into where clause like this. Active Record will query all the values in the array.
Loan.joins(:credit_memo_attributes)
.where(credit_memo_attributes: {name: ids, value: years})
Personally I'm still learning active record, in this concern i don't think active record supports multiple where clauses.
Notice how the SQL version is returning your code: it is joining the requirements with an AND.
"SELECT `loans`.* FROM `loans` INNER JOIN `credit_memo_attributes`
ON `credit_memo_attributes`.`loan_id` = `loans`.`id`
WHERE `credit_memo_attributes`.`name` = 'pr2_gtx1_y' AND `credit_memo_attributes`.`value` = '2014'
AND `credit_memo_attributes`.`name` = 'pr1_gtx1_y' AND `credit_memo_attributes`.`value` = '2013'
AND `credit_memo_attributes`.`name` = 'tx1_y' AND `credit_memo_attributes`.`value` = '2014'"
Now, this is next to impossible. An Object.name can never be all pr2_gtx1_y, pr1_gtx1_y, and tx1_y. Same goes for the value attributes.
What you need here is an OR as opposed to the AND.
To this effect, try to change your query to the following:
Loan.joins(:credit_memo_attributes)
.where(
"credit_memo_attributes.name = ? and credit_memo_attributes.value = ?
OR credit_memo_attributes.names = ? and credit_memo_attributes.value = ?
OR credit_memo_attributes.name = ? and credit_memo_attributes.value = ?",
'pr2_gtx1_y', '2014',
'pr1_gtx1_y', '2013',
'tx1_y', '2014'
)

Issue with quoting of IS, NULL, NOT, !, and other reserved strings in ON conditions of JOIN clauses in Zend Framework 2

I have an SQL statement, that selets sport classes/courses (courses) with their trainers (trainers) over an association table (courses_trainers). Since some courses have multiple trainers, I use the GROUP_CONCAT(...) function to get the trainer names into one field. Some trainers rows are empty or NULL, so I add a trainers.name IS NOT NULL and a trainers.name != "" condition to the ON clause of the trainers JOIN:
SQL statement
SELECT
courses.id AS id,
GROUP_CONCAT(DISTINCT trainers.name SEPARATOR "|||") AS trainers
...
FROM
courses
...
LEFT JOIN
courses_trainers ON courses.id = courses_trainers.course_id
LEFT JOIN
trainers ON trainer_id = trainers.id
AND trainers.name IS NOT NULL
AND trainers.name != ""
...
...
WHERE `courses`.`id` = '898'
GROUP BY
courses.id
;
OO variant in the CourseTable class
public function findOnceByID($id) {
$concatDelimiter = self::CONCAT_DELIMITER;
$select = new Select();
...
$select->columns(array(
'id', ...
));
$select->from($this->tableGateway->getTable());
$select
...
->join('courses_trainers', 'courses.id = courses_trainers.course_id', array(), Select::JOIN_LEFT)
->join('trainers', 'trainer_id = trainers.id AND trainers.name IS NOT NULL AND trainers.name != ""', array(
'trainers' => new Expression('GROUP_CONCAT(DISTINCT trainers.name SEPARATOR "' . $concatDelimiter . '")')
), Select::JOIN_LEFT)
...
;
$where
->equalTo('courses.id', $id)
;
$select->where($where, Predicate::OP_AND);
$select->group('courses.id');
$resultSet = $this->tableGateway->selectWith($select);
return $resultSet;
}
The generated JOIN code I get looks like this:
LEFT JOIN
`courses_trainers` ON `courses`.`id` = `courses_trainers`.`course_id`
LEFT JOIN
`trainers` ON `trainer_id` = `trainers`.`id`
AND `trainers`.`name` `IS` `NOT` `NULL`
AND `trainers`.`name` `!`= `"``"`
So, here is to much quoted.
How to "explain" to the ZF, that IS, NOT, " etc. should not be quoted?
The join method accepts an expression as its second parameter for the ON clause
->join('trainers', new Expression('trainer_id = trainers.id AND trainers.name IS NOT NULL AND trainers.name != ""'),
Responsible for quoting is Zend\Db\Adapter\Platform\PlatformInterface#quoteIdentifierInFragment(...) (or more precise its implementations, in this case in Zend\Db\Adapter\Platform\Mysql), that gets as second argument an array of "safe words". Zend\Db\Sql\Select#processJoins(...) passes to it the array('=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>'). As IS, NOT, ! etc. are not in the list, they are quoted.
The solution is to extend the Zend\Db\Sql\Select and overwrire its processJoins(...), adding the additional "safe words" needed to the list of the second argument in the quoteIdentifierInFragment(...) call:
<?php
namespace MyNamespace\Db\Sql;
use Zend\Db\Adapter\Driver\DriverInterface;
use Zend\Db\Adapter\StatementContainerInterface;
use Zend\Db\Adapter\ParameterContainer;
use Zend\Db\Adapter\Platform\PlatformInterface;
use Zend\Db\Sql\Select as ZendSelect;
class Select extends ZendSelect
{
...
protected function processJoins(PlatformInterface $platform, DriverInterface $driver = null, ParameterContainer $parameterContainer = null)
{
...
// process joins
$joinSpecArgArray = array();
foreach ($this->joins as $j => $join) {
...
$joinSpecArgArray[$j][] = ($join['on'] instanceof ExpressionInterface)
? $this->processExpression($join['on'], $platform, $driver, $this->processInfo['paramPrefix'] . 'join' . ($j+1) . 'part')
: $platform->quoteIdentifierInFragment($join['on'], array('=', 'AND', 'OR', '(', ')', 'BETWEEN', '<', '>', '!', 'IS', 'NULL', 'NOT', '"')); // on
...
}
return array($joinSpecArgArray);
}
...
}

Searching with CDbCriteria

Is there any way to make a CDbCriteria search (as in compare()) in the fields I'm selecting, but using the model's search() method instead of having to manually add the compare() conditions?
Note that I'm aiming at a solution that will let me write some fewer lines, nothing more and nothing less. So, if the solution is something really hacky and/or mesy, I'll just go for the "add-a-few-compares()" method.
My current code:
$criteria = new CDbCriteria;
$criteria->with = array('A', 'B', 'C', 'D', 'E');
$criteria->compare("A.field1", "test", false, 'OR');
$criteria->compare("A.field2", "test", false, 'OR');
$criteria->compare("B.field1", "test", false, 'OR');
$criteria->compare("B.field2", "test", false, 'OR');
$dataProvider = new CActiveDataProvider('Z', array(
'criteria'=>$criteria,
//pagination...
//more options...
));
Update: It seems that you are actually looking(from comments below this answer) for partial matches, and for that you will have to pass true to your compare calls:
$criteria->compare("A.field1", "test", true, 'OR');
Even that can be passed to addCondition:
$criteria->addCondition('A.field1 LIKE "%test"','OR');
// or with params as below
$criteria->addCondition('A.field2 LIKE :test','OR');
$criteria->params=array(
':test'=>'%test%',
);
As i have already mentioned in the comments, i don't think it'll be possible to use each model's default search() method. There are other alternatives though, for instance you can use addCondition instead:
$criteria = new CDbCriteria;
$criteria->with = array('A', 'B', 'C', 'D', 'E');
$criteria->together = true; // you'll need together so that the other tables are joined in the same query
$criteria->addCondition('A.field1 = "test"','OR');
$criteria->addCondition('A.field2 = "test"','OR');
// and so on
I would suggest going with the above, because compare (doc-link) should actually be used in cases when you want to "intelligently" determine the operator for comparision, for example: if you are taking the test values from user input and the user is allowed to use operators (<,>,<= etc). After determining the operator to be used in the condition, compare calls other functions accordingly, including addCondition. So using addCondition will atleast avoid those unnecessary checks.
Further if all you have to do is check equality only, i.e if your sql's WHERE is supposed to be:
WHERE A.field1 = "test" OR A.field2 = "test"
then you don't even need addCondition, and you can simply use a more complex condition (doc) :
$criteria->condition='A.field1 = "test" OR A.field2 = "test"';
// or even better if you use params
$criteria->condition='A.field1 =:test1 OR A.field2 =:test2 OR B.field1 =:test3 OR B.field2 =:test3';
$criteria->params=array(
':test1'=>'test',
':test2'=>'anothertest',
'test3'=>'tests' // omitting ':' here for params also works
);