cakephp : Sort ASC but with 0 value at end - cakephp

In a find request, how can I sort numeric value in ascending order but having the 0 value at end ?
I want : [2,5,7,7,0,0]
$this->Resuls->find()
->order(['Resuls.sec'=>'asc']);
Thanks

Depending on the DBMS there's various ways to achieve this.
A way that should work on all DBMS supported by CakePHP would probably be to use a CASE statement to first order the rows where Resuls.sec is 0 to the back, and after that order ascending by Resuls.sec regulary, that should get you the results that you are looking for.
$query = $this->Resuls->find();
$zeroLastCase = $query->newExpr()->addCase(
[$query->newExpr()->add(['Resuls.sec' => 0])],
[1, 0],
['integer', 'integer']
);
$query
->orderAsc($zeroLastCase)
->orderAsc('Resuls.sec');
That would generate an ORDER BY clause similar to:
ORDER BY
CASE WHEN Resuls.sec = 0 THEN 1 ELSE 0 END ASC,
Resuls.sec ASC
See also
Cookbook > Database Access & ORM > Query Builder > Case statements
API > \Cake\Database\Query::order()

Related

Why case..when get a table scan ? how to workarround

When I use CASE .. WHEN .. END I get an index scan less efficient than the index seek.
I have complex business rules I need to use the CASE, is there any workaround ?
Query A:
select * from [dbo].[Mobile]
where((
CASE
where ([MobileNumber] = (LTRIM(RTRIM('987654321'))))
END
) = 1)
This query gets an index scan and 199 logical reads.
Query B:
select * from [dbo].[Mobile]
where ([MobileNumber] = (LTRIM(RTRIM('987654321'))))
This query gets an index seek and 122 logical reads.
For the table
CREATE TABLE #T(X CHAR(1) PRIMARY KEY);
And the query
SELECT *
FROM #T
WHERE CASE WHEN X = 'A' THEN 1 ELSE 0 END = 1;
It is apparent without that much thought that the only circumstances in which the CASE expression evaluates to 1 are when X = 'A' and that the query has the same semantics as
SELECT *
FROM #T
WHERE X = 'A';
However the first query will get a scan and the second one a seek.
The SQL Server optimiser will try all sorts of relational transformations on queries but will not even attempt to rearrange expressions such as CASE WHEN X = 'A' THEN 1 ELSE 0 END = 1 to express it as an X = expression so it can perform an index seek on it.
It is up to the query writer to write their queries in such a way that they are sargable.
There is no workaround to get an index seek on column MobileNumber with your existing CASE predicate. You just need to express the condition differently (as in your example B).
Potentially you could create a computed column with the CASE expression and index that - and you could then see an index seek on the new column. However this is unlikely to be useful to you as I assume in reality the mobile number 987654321 is dynamic and not something to be hardcoded into a column used by an index.
After cleaning up and fixing your code, you have a WHERE which is boolean expression based around a CASE.
As mentioned by #MartinSmith, there is simply no way SQL Server will re-arrange this. It does not do the kind of dynamic slicing that would allow it to re-arrange the first query into the second version.
select *
from [dbo].[Mobile]
where
CASE
WHEN [MobileNumber] = LTRIM(RTRIM('987654321'))
THEN 1
END
= 1
You may ask: the second version also has an expression in it, why does this not also get a scan?
select *
from [dbo].[Mobile]
where [MobileNumber] = LTRIM(RTRIM('987654321'))
The reason is that what SQL Server can recognize is that LTRIM(RTRIM('987654321')) is a deterministic constant expression: it does not change depending on runtime settings, nor on the result of in-row calculations.
Therefore, it can optimize by calculating it at compile time. The query therefore becomes this under the hood, which can be used against an index on MobileNumber.
select *
from [dbo].[Mobile]
where [MobileNumber] = '987654321'

How to check if a numeric column value is in ascending or descending order. eg. 12345 or 654321

I need help with a SQL query to check if a numeric column contains a number in ascending or descending order.
eg. 123456 or 654321
This is to avoid people entering some random values in a customer phone number column, users are required to give valid phone number input.
I want to achieve this without using a function.
UPDATE: #LukStorms had kindly answered my question. Many thanks. Thanks to others who looked at my question and left comments. However I would really appreciate if the comment helps solve the problem. My scenario is different, I cannot post the entire use case here. The ask is I must validate the column in the same way.
To check if it's like a sequence of digits?
Then you can simply use LIKE
select num
, cast(case
when '1234567890123456789' like concat('%',num,'%')
then 1
when '9876543210987654321' like concat('%',num,'%')
then 1
else 0 end as bit) as isSequence
from (values
(123456),
(765432),
(797204)
) nums(num)
num
isSequence
123456
True
765432
True
797204
False
Or use CHARINDEX
select num
, cast(case
when 0 < charindex(concat(num,''),'1234567890123456789') then 1
when 0 < charindex(concat(num,''),'9876543210987654321') then 1
else 0 end as bit) as isSequence
from (values
(123456),
(765432),
(797204)
) nums(num)
Demo on db<>fiddle here

How to show #ERROR as Zero or as Nothing in matrix cell when groups and calculated field are based on switch-function in SSRS?

Imagine simple BI task:
We have two grouping columns and one calculated field.
like
select group1, group2, sum(sales) Sum_of_sales from dbo.table1
group by group1, group2
When for some combination of grouping columns there's no ROW in dataset - report shows me Empty. It's ok.
Now i'm building matrix with groups depending on parametrs. Some sort of programmatic matrix. User deciedes what to analyze by himself. He chooses X_dimension and Y_dimension and target CalculatedField
Grouping expression for first group now looks like
=Switch(
Parameters!param1.Value = "var1", Fields!groupingColumn1.Value,
Parameters!param1.Value = "var2", Fields!groupingColumn2.Value)
And for second group
=Switch(
Parameters!param2.Value = "var3", Fields!groupingColumn3.Value,
Parameters!param2.Value = "var4", Fields!groupingColumn4.Value)
With target value:
=Switch(
Parameters!param3.Value = "var5", Count(Fields!sales_sum.Value),
Parameters!param3.Value = "var6", Sum(Fields!sales_sum.Value)
)
When there are sales in combinations of groups - it's ok. It shows me exact value - like i would sum it with calculator. But when THERE'S NO DATA for some partition - it just raises error in that cell. And it seems like no way out. Pls help.
P.S. I tried all possible variants such as
iif(sum(...) is Nothing, 0 , sum(...))
sum(iif(value > 0 , value , 0))
and combination of them
i tried comparing with ZERO, Nothing , and checking isNothing
but i still get error in cell

Divide function in SQL Server 2008

I'm trying to use the Divide function in SSAS:
https://msdn.microsoft.com/en-us/library/jj873944(v=sql.110).aspx
But I have to support SQL server 2008 and it looks like this is not available. The initial problem I am having is that when I add a case statement to a measure calculation the performance of the query is VERY poor. It's been suggested to use this divide function.
The statement to create member is:
Create Member CurrentCube.[Measures].[AvgSentiment] As
CASE
WHEN ([Measures].[SentimentCount]) > 0 THEN [Measures].[SentimentSum] / [Measures].[SentimentCount]
WHEN([Measures].[SentimentCount]) = 0 THEN 0
END
, VISIBLE =1
, ASSOCIATED_MEASURE_GROUP = 'vw_CUBE_FACT' ;
Which I tried replacing with:
Create Member CurrentCube.[Measures].[AvgSentiment] As
Divide ([Measures].[SentimentSum], [Measures].[SentimentCount], 0)
, VISIBLE =1
, ASSOCIATED_MEASURE_GROUP = 'vw_CUBE_FACT'
I also tried:
Create Member CurrentCube.[Measures].[AvgSentiment] As
IIF(
[Measures].[SentimentCount] = 0
, 0
, [Measures].[SentimentSum]/[Measures].[SentimentCount]
)
, VISIBLE =1
, ASSOCIATED_MEASURE_GROUP = 'vw_SEAMS_CUBE_FACT'
This also ate up a tonne of CPU / Memory.
Divide won't make a huge difference.
Your third attempt is almost what you want. What you aim to do with cube measures is make them "sparse". My interpretation of this is that you want them to only exist in the parts of the cube where they should exist - everywhere else the cube space should be empty - this is achieved by using null in your measure rather than 0:
CREATE MEMBER CurrentCube.[Measures].[AvgSentiment] As
IIF(
[Measures].[SentimentCount] = 0
, NULL //<<got rid of the 0
, [Measures].[SentimentSum]
/[Measures].[SentimentCount]
)
, VISIBLE =1
, ASSOCIATED_MEASURE_GROUP = 'vw_SEAMS_CUBE_FACT'
IIF generally performs better than CASE so better this than adapting your attempt using CASE.
If the above still performs badly then I suspect you need to investigate the performance of [Measures].[SentimentCount] - how is this calculated?

CakePHP: creating an array field that holds the count of specific values from another array

I'm working with someone else's CakePHP code, with minimal knowledge of PHP, so this may be a very basic question.
Within a controller, the following code begins to create an array that's then passed to a .ctp file:
$prefs_by_user = $this->PanelPref->find('all', array(
'fields' => array(
'UserTimeSlot.day_time_slot_id', 'PanelPref.panel_id', 'Panel.name',
'COUNT(DISTINCT PanelPref.user_id) AS panels_int' ,
Within PanelPref are panel ratings that may equal 1, 2, or 3. I would like to add fields that count how many people have given this particular panel each rating. I attempted this:
'COUNT(PanelPref.panel_rating_id = 3) AS rated_three',
'COUNT(PanelPref.panel_rating_id = 2) AS rated_two',
'COUNT(PanelPref.panel_rating_id = 1) AS rated_one',
but all that does is count PanelPref.panel_rating_id and since there are as many ratings as there are users, all the variables end up with the same value.
I've tried using == and => instead of =, but they return errors.
I've tried COUNT(PanelPref.panel_rating_id WHERE PanelPref.panel_rating_id = 3) AS rated_three and gotten an error.
I've tried using array_count_values but it doesn't seem to work within the array fields (and I'm probably not using it properly anyway).
Any thoughts on how to make this work? It's not vital but I would really like to have it.
Replace those with these three:
'SUM(CASE PanelPref.panel_rating_id WHEN 3 THEN 1 ELSE 0 END) AS rated_three',
'SUM(CASE PanelPref.panel_rating_id WHEN 2 THEN 1 ELSE 0 END) AS rated_two',
'SUM(CASE PanelPref.panel_rating_id WHEN 1 THEN 1 ELSE 0 END) AS rated_one'
Those items are part of the "fields" array, which correspond to the SELECT area of a generic T-SQL code block. What it looks like you want is a count of ratings for the item, and so this sum of case is the sort of hacky workaround for this.
This should work too:
'SUM(PanelPref.panel_rating_id = 3) AS rated_three',
'SUM(PanelPref.panel_rating_id = 2) AS rated_two',
'SUM(PanelPref.panel_rating_id = 1) AS rated_one',

Resources