SQL: How to get max over column but exclude certain values? - sql

I have a numeric column and all I want is the maximum value in that column that does NOT exceed a certain number. I am doing this along with a group by statement, using the MAX function.
So basically if for each group, the column is 1, 2, 3, 4, 5, and I want the maximum that does not exceed 4, then in this case, the maximum for this group would be 4.
However, if the column equals 5, 6, 7, 8, then since all values exceed 4, I … actually don't care, this won't end up being displayed, so just return anything.
How do I do this? Using SQL/Oracle.

You can use conditional aggregation as follows:
Select case when count(case when col > 4 then 1 end) = count(*)
then max(col)
else max(case when col <= 4 then col end)
end as res_
From your_table t

select max(case when col>4 then 0 else col end) from table1

If that is all that the query needs to do, it is best to filter the rows with col > 4 before aggregation, in a where clause. This will reduce the amount of work done by the aggregation itself, which is the most expensive operation in the whole query.
As a side effect, "groups" where all values in the column are > 4 will not be included at all in the output. For some reporting tasks this would be a problem, but you said in your case you wouldn't show anything in the output for those groups anyway.
So, you could do something like this:
select agg_col1, agg_col2, ..., max(col) as max_col_up_to_4
from your_table
WHERE col <= 4 -- DO THE FILTERING HERE!
group by agg_col1, agg_col2, ...
;
(Here agg_col1, agg_col2, ... are, obviously, the columns by which you group for your aggregation.)

Related

Create a new table with columns with case statements and max function

I have some problems in creating a new table from an old one with new columns defined by case statements.
I need to add to a new table three columns, where I compute the maximum based on different conditions. Specifically,
if time is between 1 and 3, I define a variable max_var_1_3 as max((-1)*var),
if time is between 1 and 6, I define a variable max_var_1_6 as max((-1)*var),
if time is between 1 and 12, I define a variable max_var_1_12 as max((-1)*var),
The max function needs to take the maximum value of the variable var in the window between 1 and 3, 1 and 6, 1 and 12 respectively.
I wrote this
create table new as(
select t1.*,
(case when time between 1 and 3 then MAX((-1)*var)
else var
end) as max_var_1_3,
(case when time between 1 and 6 then MAX((-1)*var)
else var
end) as max_var_1_6,
(case when time between 1 and 12 then MAX((-1)*var)
else var
end) as max_var_1_12
from old_table t1
group by time
) with data primary index time
but unfortunately it is not working. The old_table has already some columns, and I would like to import all of them and then compare the old table with the new one. I got an error that says that should be something between ) and ',', but I cannot understand what. I am using Teradata SQL.
Could you please help me?
Many thanks
The problem is that you have GROUP BY time in your query while trying to return all the other values with your SELECT t1.*. To make your query work as-is, you'd need to add each column from t1.* to your GROUP BY clause.
If you want to find the MAX value within the different time ranges AND also return all the rows, then you can use a window function. Something like this:
CREATE TABLE new AS (
SELECT
t1.*,
CASE
WHEN t1.time BETWEEN 1 AND 3 THEN (
MAX(CASE WHEN t1.time BETWEEN 1 AND 3 THEN (-1 * t1.var) ELSE NULL END) OVER()
)
ELSE t1.var
END AS max_var_1_3,
CASE
WHEN t1.time BETWEEN 1 AND 6 THEN (
MAX(CASE WHEN t1.time BETWEEN 1 AND 6 THEN (-1 * t1.var) ELSE NULL END) OVER()
)
ELSE t1.var
END AS max_var_1_6,
CASE
WHEN t1.time BETWEEN 1 AND 12 THEN (
MAX(CASE WHEN t1.time BETWEEN 1 AND 12 THEN (-1 * t1.var) ELSE NULL END) OVER()
)
ELSE t1.var
END AS max_var_1_12,
FROM old_table t1
) WITH DATA PRIMARY INDEX (time)
;
Here's the logic:
check if a row falls in the range
if it does, return the desired MAX value for rows in that range
otherwise, just return that given row's default value (var)
return all rows along with the three new columns
If you have performance issues, you could also move the max_var calculations to a CTE, since they only need to be calculated once. Also to avoid confusion, you may want to explicitly specify the values in your SELECT instead of using t1.*.
I don't have a TD system to test, but try it out and see if that works.
I cannot help with the CREATE TABLE AS, but the query you want is this:
SELECT
t.*,
(SELECT MAX(-1 * var) FROM old_table WHERE time BETWEEN 1 AND 3) AS max_var_1_3,
(SELECT MAX(-1 * var) FROM old_table WHERE time BETWEEN 1 AND 6) AS max_var_1_6,
(SELECT MAX(-1 * var) FROM old_table WHERE time BETWEEN 1 AND 12) AS max_var_1_12
FROM old_table t;

Convert text to number for customer satisfaction survey

I have customer satisfaction feedback like Good, bad, excellent etc, I would like to assign a numerical value like Excellent = 5, Good = 4, Neutral = 2, Unsatisfied = 0
Then sum the value by category
This query I need to start with "Select" only
Grouping by month
Excellent 400
Good 500
use case when
select sum(case when satisfaction ='Excellent' then 5
when satisfaction ='Good' then 4
when satisfaction ='Neutral' then 2
when satisfaction ='Dissatisfied' then 0 end) as satisfaction_val
,category from table_name group by category
I think you should have a reference table for this purpose. If you don't have one, you can put one together in the query itself:
select t.category, sum(v.satisfaction_val)
from t cross join
(values ('Excellent', 5),
('Good', 4),
('Neutral', 2),
('Dissatisfied', 0)
) v(satisfaction, satisfaction_val)
on t.satisfaction = v.satisfaction
group by t.category;
This is a better solution that just using a case expression in-line, because the values can be re-used. For instance, if you want the total of number of positive responses, you might do:
select t.category, sum(v.satisfaction_val),
sum(case when v.satisfaction_value > 3 then 1 else 0 end)

GROUP BY with COUNT condition

I have a result set such as:
Code No
1 *
1 -
1 4
1
1
Now i basically want a query that has 2 columns, a count for the total amount and a count for those that dont have numbers.
Code No_Number Total
1 4 5
Im assuming this needs a group by and a count but how can i do the 2 different counts in a query like this?
This is what i had so far, but i am a bit stuck with the rest of it
SELECT CODE,NO
Sum(Case when No IN ('*', '-', '') then 1 else 0 end) as Count
I think you basically just need GROUP BY:
SELECT CODE,
SUM(Case when No IN ('*', '-', '') then 1 else 0 end) as Count,
COUNT(*) as total
FROM t
GROUP BY CODE;
Well, this took a moment :-), however here it is...I have used a CASE statement to create and populate the No_Number column; the database gives the row in the original table a value of 1 if the original table value is a number or gives it a NULL and discards it from the COUNT if not. Then when it makes the count it is only recognising values which were originally numbers and ignoring everything else..
If the result set is in a table or temp table:
SELECT Code,
COUNT(CASE WHEN [No] NOT LIKE '[0-9]' THEN 1 ELSE NULL END) AS No_Number,
COUNT(Code) AS Total
FROM <tablename>
GROUP BY Code
If the result set is the product of a previous query you can use a CTE (Common Table Expression) to arrive at the required result or you could include parts of this code in the earlier query.

SQL statement - use avg and count together but in different conditions

Assume we have the table as follows,
id Col-1 Col-2
A 1 some text
B 0 some other text
C 3
...
Take the table above as example, I want to build one SQL statement which would output the result: 2, 2.
The first value is the avg of all col-1 values except for 0, that is (1+3)/2 = 2. (If 0 is counted, then the result would be (1+0+3)/3 = 1, which is not what I want.)
The second value is the total number of all col-2 that is not empty. So the value is 2.
P.S, I know how to create them separately. What I prefer is to create only 1 statement to get both results.
For the first you can use NULLIF as null values are ignored in aggregations such as AVG.
For the second I assume you want to only count values not NULL or empty string.
SELECT AVG(NULLIF(Col1, 0)),
COUNT(CASE WHEN Col2 <> '' THEN 1 END)
FROM T
You want conditional aggregation:
select avg(case when col1 <> 0 then col1 end) as avg_not_zero,
count(col2) as num_not_empty
from table t;
As a note: 0 does not mean that the value is empty. Often NULL is used for this purpose in SQL, although strictly speaking, NULL means an unknown value.
Note: If "empty" could mean the empty string instead of NULL:
select avg(case when col1 <> 0 then col1 end) as avg_not_zero,
count(nullif(col2, '')) as num_not_empty
from table t;

Ordering Select clause result in specific way

I need help with writing a select clause query.
For example, lets say I have a query like that:
select value from some_table order by value asc;
as a result I get this:
1
2
3
4
5
6
7
8
9
10
but a special query I want to write, is the one which still will give me sorted values, but will put 5 after 8.
this means I need one value to be out of regular order.
it can be described in other way. lets say I have two groups of numbers (example):
A={ a | 1<=a<=118, a!=78 } B={ b | b>118 }
I have a group C=A U B U {78}
and I need all these values sorted like "A,78,B"
Assuming value is integer, you could do this:
SELECT *
FROM tbl
ORDER BY
CASE
WHEN value = 5 THEN 8.5
ELSE value
END
Or to expand upon DCP's answer...
SELECT *
FROM tbl
ORDER BY
CASE
WHEN (Condition for first grouping) THEN 1
WHEN (Condition for second grouping) THEN 2
WHEN (Condition for third grouping) THEN 3
ELSE 4
END
You can use multiple conditions in your order by:
ORDER BY (value BETWEEN 1 AND 118) AND value != 78 DESC,
value > 118 DESC,
value
This will ensure that values which match the first predicate come first, then values matching the second predicate, and finally values matching none of the predicates. If there is a tie (two numbers matching the same predicate) then these numbers are sorted in ascending order.
Note that I haven't tested this in Oracle. It might be necessary to wrap the predicate in a CASE expression (CASE WHEN predicate THEN 1 ELSE 0 END) to get the sorting to work in Oracle.
ORDER BY
(CASE WHEN ((value BETWEEN 1 AND 118) AND value <> 78) THEN 1 ELSE 0 END) DESC,
(CASE WHEN (value > 118) THEN 1 ELSE 0 END) DESC,
value
Order by some CASE-expression to remap your values.