How to sum non-null values from multiple columns (workaround for CASE WHEN limitation) Postgresql - sql

So I essentially want to work around the fact that CASE WHEN stops executing when it finds its first TRUE return.
I'd like to sum every instance of a non-null value between multiple columns, and group these based on my ID. Example table:
id
input1
input2
input3
1
a
null
k
2
null
null
b
3
null
null
null
4
q
null
r
5
x
p
j
6
null
y
q
I would like the output of my function to be:
id
total_inputs
1
2
2
1
3
0
4
2
5
3
6
2
Any work arounds? Is a custom function in order to create a count of unique or non-null entries across multiple columns, grouped by row?
I know I can create a CTE and assign 1's to each non-null column but that seems tedious (my data set has 39 inputs) - and I'd like to have a reusable function I could use again in the future.

You could use a simple aggregation as the following:
Select id,
Count(input1) + Count(input2) + Count(input3) As total_inputs
From table_name
Group By id
Order By id
Noting that Count(inputX) = 0, where inputX is null.
See a demo.

We can simply use:
select ID,
case when input1 is not null then 1 else 0 end
+ case when input2 is not null then 1 else 0 end
+ ...
+ case when input39 is not null then 1 else 0 end as total_inputs
from ...
No need to group by if you want every row (or count, we are not aggregating rows - that is what COUNT()..GROUP BY is for), or CTE.
Also, for some PostgreSQL versions, there is a num_nulls function to count null parameters:
select
, 32-num_nulls(input1, input2, input3, ..., input32)

Related

Add custom bool column where data is calculated based on values from linked entities

I have 2 tables: Entity and EntityItem.
EntityItems table has a Reason column which is nullable enum.
I'm trying to write a view that would return some Entititys columns and additionally a boolean column that states whether all corresponding EntityItem.Reason have a non-null value.
The following query returns somewhat what I want:
SELECT EntityItem.Id, COUNT(EntityItem.Reason) As Test
FROM EntityItem
GROUP BY EntityItem.ParentEntityId
ORDER BY Test DESC
Example output:
Id Test
132189 4
132190 2
132197 1
1 0
2 0
3 0
4 0
5 0
6 0
However, when I try to add this to a final query I get duplicated lines for each EntityItem
SELECT [Entity].[Id],
...
(SELECT CASE WHEN (SELECT COUNT([EntityItem].[Reason]) FROM [EntityItem] WHERE [EntityItem].[ParentEntityId] = [Entity].[Id]) = 0
THEN 0
ELSE 1
END) AS Test
FROM [Entity]
...
LEFT JOIN [EntityItem] ON [Entity].[Id] = [EntityItem].[ParentEntityId]
Example output:
Id Test
1 1
1 1
2 0
2 0
2 0
2 0
3 1
3 1
4 0
Question 1: Is my approach correct?
Question 2: Is there a way to remove duplicated lines without DISTINCT?
For your second query you need to aggregate before joining, for example by using outer apply something like:
select e.Id,
case when i.cnt = 0 then 0 else 1 end as Test
from Entity e
outer apply (
select Count(Reason) cnt
from EntityItem i
where i.ParentEntityId = e.Id
)i;
Saying that, since you are always returning a value of 1 if the count is greater than zero you don't actually need to count anything:
select e.Id,
case when exists (
select * from EntityItem i
where i.ParentEntityId = e.Id
)
then 1 else 0 end as Test
from Entity e;

Show 2 count in 1 result

I need to display a single result so that I can extract it.
This is my table named 'TRY'
id | type |
01 | A |
01 | D |
01 | A |
My query is like this:
select
lpad(id,2,'0')||
lpad(count(case when type = 'A' then 1 end),3,'0')||
lpad(count(case when type = 'D' then 1 end),3,'0')||
lpad(count(case when type in ('A','D') then 1 end),3,'0') then 1 end)
as results
from
TRY
group by
id
,type
I want the result to show like this 01002001003 but instead I got 2 result which are like this
01002000002 and 01000001001. I just want to combine the count result as one.
I think that the main thing that you need to consider is how the GROUP BY clause actually works, as you have actually grouped by id and type meaning that you'll never have a positive count for type A and type D on the same row as they are not grouped together.
In your example I have noticed the following and worked to these parameters:
You want to GROUP BY id (not GROUP BY id, type)
Your returned result is a concatenated string which consists of
the id (padded to 2 characters)
the number of Type A's (padded to 3 characters)
the number of type D (padded to 3 characters)
the total number of A's and D's combined. (padded to 3 characters)
You actually also want to be using the SUM function instead of COUNT so something along the lines of the following should work:
Oracle/PLSQL
SELECT
LPAD(id,2,'0')
|| LPAD(SUM(CASE WHEN type = 'A' THEN 1 ELSE 0 END),3,'0')
|| LPAD(SUM(CASE WHEN type = 'A' THEN 1 ELSE 0 END),3,'0')
|| LPAD(SUM(CASE WHEN type = 'A' OR type = 'D' THEN 1 ELSE 0 END),3,'0')
AS results
FROM
TRY
GROUP BY
id
On the unlikely off-chance you're actually using SQL Server or another Database engine the LPAD function will not actually work so using something along the lines of the following may work instead. (Also added in case some non ORACLE users have a similar quandary and it's what I used to resolve your issue)
SELECT
[results] =
RIGHT('00' + CONVERT(VARCHAR,[id]),2)
+ RIGHT('000' + CONVERT(VARCHAR,SUM(CASE WHEN [type] = 'A' THEN 1 ELSE 0 END)),3)
+ RIGHT('000' + CONVERT(VARCHAR,SUM(CASE WHEN [type] = 'D' THEN 1 ELSE 0 END)),3)
+ RIGHT('000' + CONVERT(VARCHAR,SUM(CASE WHEN [type] = 'A' OR type = 'D' THEN 1 ELSE 0 END)),3)
FROM
[TRY]
GROUP BY
[id]

Even or odd in SQL

This is table structure
id
1
2
3
4
5
6
I need result like this
id even odd
1 0 1
2 1 0
3 0 1
4 1 0
5 0 1
6 1 0
I tried
select id %2=0 then 1 else 0 end or id%2 <>0 then 1 else 0 odd
from table
How about
select
id,
~id & 1,
id & 1
from t
Take a look at the CASE keyword. It works very similarly to what you're trying to do in your SELECT statement. In addition, if you want to select multiple columns, separate them with a comma. The OR keyword is used for combining logical conditions in your query, not for specifying multiple columns.
An example of how you could use CASE in your query would be as follows:
SELECT id,
CASE WHEN id %2=0 THEN 1 ELSE 0 END AS Even,
[column2]
FROM [TableName]
The table structure is just Id?
you could try this!
select *,
case when id %2=0 then 1 else 0 end as even,
case when id %2 <>0 then 1 else 0 end as odd
from table
You have the right idea, but your syntax is a bit off. I'd use a CASE statement to create the even column, and then a calculate odd accordingly:
SELECT id, even, ABS(even - 1) AS odd
FROM (SELECT id, CASE WHEN id % 2 = 0 THEN 1 ELSE 0 END AS even
FROM my_table)

select result set row to columns transformation

I've a table remarks with columns id, story_id, like like can be +1, -1
I want my select query to return the following columns story_id, total, n_like, n_dislike where total = n_like + n_dislike without sub queries.
I am currently doing a group by on like and selecting like as like_t, count(like) as total which is giving me an output like
-- like_t --+ --- total --
-1 | 2
1 | 6
and returning two rows in result set. But what I want is to get 1 row where n_like is 6 and n_dislike is 2 and total is 8
First, LIKE is a reserved word in PostgreSQL, so you have to double-quote it. Maybe a better name should be picked for this column.
CREATE TABLE testbed (id int4, story_id int4, "like" int2);
INSERT INTO testbed VALUES
(1,1,'+1'),(1,1,'+1'),(1,1,'+1'),
(1,1,'+1'),(1,1,'+1'),(1,1,'+1'),
(1,1,'-1'),(1,1,'-1');
SELECT
story_id,
sum(CASE WHEN "like" > 0 THEN abs("like") ELSE 0 END) AS n_like,
sum(CASE WHEN "like" < 0 THEN abs("like") ELSE 0 END) AS n_dislike,
count(story_id) AS total
-- for cases +2 / -3 in the "like" field, use following construct instead
-- sum(abs("like")) AS total
FROM testbed
GROUP BY story_id;
I used abs("like") for cases when you'll have +2 or -3 in your "like" column.

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.