Convert text to number for customer satisfaction survey - sql

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)

Related

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

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

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 query to find count with a difference condition and total count in the same query

Here is a sample table I have
Logs
user_id, session_id, search_query, action
1, 100, dog, A
1, 100, dog, B
2, 101, cat, A
3, 102, ball, A
3, 102, ball, B
3, 102, kite, A
4, 103, ball, A
5, 104, cat, A
where
miss = for the same user_id and same session id , if action A is not followed by action B its termed a miss.
Note: action B can happen only after action A has happened.
I am able to find the count of misses for each unique search_query across all users and sessions.
SELECT l1.search_query, count(l1.*) as misses
FROM logs l1
WHERE NOT EXISTS
(SELECT NULL FROM logs l2
WHERE l1.user_id = l2.user_id
AND l1.session_id = l2.session_id
AND l1.session_id != ''
AND l2.action = 'B'
AND l1.action = 'A')
AND l1.action='A'
AND l1.search_query != ''
GROUP BY v1.search_query
order by misses desc;
I am trying to find the value of miss_percentage=(number of misses/total number of rows)*100 for each unique search_query. I couldn't figure out how to find the count with a condition and count without that condition in the same query. Any help would be great.
expected output:
cat 100
kite 100
ball 50
One way to do it is to move the EXISTS into the count
SELECT l1.search_query, count(case when NOT EXISTS
(SELECT 1 FROM logs l2
WHERE l1.user_id = l2.user_id
AND l1.session_id = l2.session_id
AND l1.search_query = l2.search_query
AND l2.action = 'B'
AND l1.action = 'A') then 1 else null end
)*100.0/count(*) as misses
FROM logs l1
WHERE l1.action='A'
AND l1.search_query != ''
GROUP BY l1.search_query
order by misses desc;
This produces the desired results, but also zeros if no misses were found. This can be removed with a HAVING clause, or postprocessing.
Note I also added the clause l1.search_query = l2.search_query that was missing, since otherwise it was counting kite as succeeded, since there is a row with B in the same session.
I think you just need to use case statements here. If I have understood your problem correctly .. then the solution would be something like this -
WITH summary
AS (
SELECT user_id
,session_id
,search_query
,count(1) AS total_views
,sum(CASE
WHEN action = 'A'
THEN 1
ELSE 0
END) AS action_a
,sum(CASE
WHEN action = 'B'
THEN 1
ELSE 0
END) AS action_b
FROM logs l
GROUP BY user_id
,session_id
,search_query
)
SELECT search_query
,(sum(action_a - action_b) / sum(action_a)) * 100 AS miss_percentage
FROM summary
GROUP BY search_query;
You can allways create two queries, and combine them into one with a join. Then you can do the calculations in the bridging (or joining) SQL statement.
In MS-SQL compatible SQL this would be:
SELECT ActiontypeA,countedA,isNull(countedB,0) as countedB,
(countedA-isNull(countedB,0))*100/CountedA as missed
FROM (SELECT search_query as actionTypeA, count(*) as countedA
FROM logs WHERE Action='A' GROUP BY actionType
) as TpA
LEFT JOIN
(SELECT search_query as actionTypeB, count(*) as countedB
FROM logs WHERE Action='B' GROUP BY actionType
) as TpB
ON TpA.ActionTypeA = TpB.ActiontypeB
The LEFT JOIN is required to select all activities (search_query) from the 'A' results, and join them to only those from the 'B' results where a B is available.
Since this is very basic SQL (and well optimized by SQL engines) I'd suggest to prevent WHERE EXISTS as much as possible. The IsNull() function is an MS-SQL function to force a NULL value into the int(0) value which can be used in a calculation.
Finally you could filter on
WHERE missed>0
to get the final result.

Aggregate to count values equal to a constant

In SQL there is an expressive way to count how many values differ from 'x':
SELECT COUNT(NULLIF(col, 'x')) FROM table
I find it less elegant to count values equal to 'x':
SELECT COUNT(*) - COUNT(NULLIF(col, 'x')) FROM table
SELECT SUM(CASE WHEN col = 'x' THEN 1 ELSE 0 END) FROM table
[oracle] SELECT COUNT(DECODE(col,'x','x',NULL)) FROM table
Is there a more elegant way to do that?
SELECT
COUNT(CASE WHEN col='x' THEN 1 END) AS XCount,
COUNT(CASE WHEN col='y' THEN 1 END) AS YCount,
COUNT(CASE WHEN col='z' THEN 1 END) AS ZCount
FROM table
taste is a quite personal thing, I like to use this syntax in Oracle:
select sum(decode(text,'x',0,1))
from table
The straightforward way is to do the filtration in the WHERE clause:
SELECT COUNT(*) FROM table WHERE col = 'x'
EDIT:
If you cannot use the where clause (because you are performing several counts in the same SELECT), then I think the ways you suggested yourself are the most elegant ones.
My personal preference would be the SUM(CASE WHEN....

Query to get percent of a total using select(count)

I'm calculating the change in pain between day 1 and day 2.
There are two fields, Pain_Admit_Comfort and Pain_48_Hr_Comfort, the options in each is Yes/No.
I need to find everyone that had pain on Admit and is More Comfortable 2 days later.
This is the query. The first two statements return correct numbers. I can't figure out how to divide using the same statements as numerator and denominator.
select
(select COUNT (PAIN_48_HR_COMFORT_C)
FROM CASES WHERE PAIN_48_HR_COMFORT_C='Yes') as Forty_Eight_Hours,
(SELECT COUNT (PAIN_ADMIT_COMFORT_C)
FROM CASES WHERE PAIN_ADMIT_COMFORT_C='YES') as Admit_Uncomfort_Yes,
((select COUNT (PAIN_48_HR_COMFORT_C)
FROM CASES WHERE PAIN_48_HR_COMFORT_C='Yes')
/
(SELECT COUNT (PAIN_ADMIT_COMFORT_C)
FROM CASES WHERE PAIN_ADMIT_COMFORT_C='YES')) AS Percent_Changed
from CASES
Thanks
I don't spot any immediate problems with your statement but following statement should return the correct results and is perhaps a bit easier to read.
SELECT feh.Forty_Eight_Hours
, auy.Admit_Uncomfort_Yes
, Percent_Changed = CAST(feh.Forty_Eight_Hours AS FLOAT) / auy.Admit_Uncomfort_Yes
FROM (
SELECT Forty_Eight_Hours = COUNT(PAIN_48_HR_COMFORT_C)
FROM CASES
WHERE PAIN_48_HR_COMFORT_C = 'Yes'
) feh
CROSS APPLY (
SELECT Admit_Uncomfort_Yes = COUNT (PAIN_ADMIT_COMFORT_C)
FROM CASES
WHERE PAIN_ADMIT_COMFORT_C = 'Yes'
) auy
Your query, and the other answers, are very inefficient (multiple selects).
What you want is called a "pivot", and the most efficient way of coding it using just one select over the table (your query uses 4) is as follows:
select
sum(case when PAIN_48_HR_COMFORT_C = 'Yes' then 1 else 0 end) as Forty_Eight_Hours,
sum(case when PAIN_ADMIT_COMFORT_C = 'Yes' then 1 else 0 end) as Admit_Uncomfort_Yes
sum(case when PAIN_ADMIT_COMFORT_C = 'Yes' AND PAIN_48_HR_COMFORT_C = 'NO' then 1 else 0 end) as Improved_pain
FROM CASES
I'm not sure what the columns mean - you may need to change a 'YES' to 'NO' etc to get the "has"/"has not" pain correct.