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

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;

Related

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

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)

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)

SQL (TSQL) - Select values in a column where another column is not null?

I will keep this simple- I would like to know if there is a good way to select all the values in a column when it never has a null in another column. For example.
A B
----- -----
1 7
2 7
NULL 7
4 9
1 9
2 9
From the above set I would just want 9 from B and not 7 because 7 has a NULL in A. Obviously I could wrap this as a subquery and USE the IN clause etc. but this is already part of a pretty unique set and am looking to keep this efficient.
I should note that for my purposes this would only be a one-way comparison... I would only be returning values in B and examining A.
I imagine there is an easy way to do this that I am missing, but being in the thick of things I don't see it right now.
You can do something like this:
select *
from t
where t.b not in (select b from t where a is null);
If you want only distinct b values, then you can do:
select b
from t
group by b
having sum(case when a is null then 1 else 0 end) = 0;
And, finally, you could use window functions:
select a, b
from (select t.*,
sum(case when a is null then 1 else 0 end) over (partition by b) as NullCnt
from t
) t
where NullCnt = 0;
The query below will only output one column in the final result. The records are grouped by column B and test if the record is null or not. When the record is null, the value for the group will increment each time by 1. The HAVING clause filters only the group which has a value of 0.
SELECT B
FROM TableName
GROUP BY B
HAVING SUM(CASE WHEN A IS NULL THEN 1 ELSE 0 END) = 0
If you want to get all the rows from the records, you can use join.
SELECT a.*
FROM TableName a
INNER JOIN
(
SELECT B
FROM TableName
GROUP BY B
HAVING SUM(CASE WHEN A IS NULL THEN 1 ELSE 0 END) = 0
) b ON a.b = b.b

Return records that all match and all records where at least one doesn't match

Given a table of exam results, where 1 == PASS and 0 == FAIL
ID Name Test Result
--------------------
1 John MATH 1
2 John ENGL 1
3 Mary MATH 1
4 Mary PSYC 0
EDIT: assume that the name is unique.
I need to get all records for people who
1) passed all tests
2) failed at least one test
So, the 1st query should return John and all his records, and the 2nd query should return Mary and all her records (including the ones with PASS).
I'm trying to do a LEFT OUTER JOIN with itself and compare counts, but don't seem to get a working query.
SELECT * FROM Results R1
LEFT OUTER JOIN Results R2 on R1.ID=R2.ID and R2.Result=1
WHERE ??? count of rows from R1 is compared to count of non-null rows from R2
This is a "poster-child" exercise for the EXISTS clause:
At leasr one failed result:
select * from Results r
where exists (select * from Results rr where rr.Name=r.Name AND Result=0)
All passed:
select * from Results r
where not exists (select * from Results rr where rr.Name=r.Name AND Result=0)
See how these queries work on your data set at sqlfiddle.com.
All passed
SELECT Name FROM Results R1
GROUP BY NAME
HAVING SUM(RESULT) = COUNT(RESULT)
Some failed
SELECT Name FROM Results R1
GROUP BY NAME
HAVING SUM(RESULT) < COUNT(RESULT)
Hope it helps
Edit
All passed
SELECT Name FROM Results R1
GROUP BY NAME
HAVING SUM(1-RESULT) = 0
Some failed
SELECT Name FROM Results R1
GROUP BY NAME
HAVING SUM(1-RESULT) > 0
(This might run faster)
One way
Select Name,
Case failCount When 0 then 'X' Else '' End PassedAll,
Case failCount When 0 then '' Else 'X' End FailedOneOrMore
From (Select name,
Sum(Case Result when 0 Then 1 Else 0 End) failCount
From Results R
Group By Name) Z
to get all the records, just join to this
Select zz.Name, zz.PassedAll, zz.FailedOneOrMore,
r.Test, r.Result
From (Select Name,
Case failCount When 0 then 'X' Else '' End PassedAll,
Case failCount When 0 then '' Else 'X' End FailedOneOrMore
From (Select name,
Sum(Case Result when 0 Then 1 Else 0 End) failCount
From Results R
Group By Name) Z) ZZ
Left Join Results r On r.Name = zz.Name
This query uses a subquery to return all records (pass & fail) for people who have passed at least one of the Tests:
select * from Results where Name in (select Name from Results where Result = '1' group by Name);
Results exclude those who failed to pass any of the tests.

How do I modify this query without increasing the number of rows returned?

I've got a sub-select in a query that looks something like this:
left outer join
(select distinct ID from OTHER_TABLE) as MYJOIN
on BASE_OBJECT.ID = MYJOIN.ID
It's pretty straightforward. Checks to see if a certain relation exists between the main object being queried for and the object represented by OTHER_TABLE by whether or not MYJOIN.ID is null on the row in question.
But now the requirements have changed a little. There's another row in OTHER_TABLE that can have a value of 1 or 0, and the query needs to know whether a relation exists between the primary for a 1-value, and also if it exists for a 0 value. The obvious solutions is to put:
left outer join
(select distinct ID, TYPE_VALUE from OTHER_TABLE) as MYJOIN
on BASE_OBJECT.ID = MYJOIN.ID
But that would be wrong because if 0-type and 1-type objects both exist for the same ID, it will increase the number of rows returned by the query, which isn't acceptable. So what I need is some sort of subselect that will return 1 row for each distinct ID, with a "1-type exists" column and a "0-type exists" column. And I have no idea how to code that in SQL.
For example, for the following table,
ID | TYPE_VALUE
_________________
1 | 1
3 | 0
3 | 1
4 | 0
I'd like to see a result set like this:
ID | HAS_TYPE_0 | HAS_TYPE_1
______________________________
1 | 0 | 1
3 | 1 | 1
4 | 1 | 0
Anyone know how I could set up a query to do this? Hopefully with a minimum of ugly hacks?
In the general case, you would use EXISTS:
SELECT DISTINCT ID,
CASE WHEN EXISTS (
SELECT * FROM Table1 y
WHERE y.TYPE_VALUE = 0 AND ID = x.ID)
THEN 1
ELSE 0 END AS HAS_TYPE_0,
CASE WHEN EXISTS (
SELECT * FROM Table1 y
WHERE y.TYPE_VALUE = 1 AND ID = x.ID)
THEN 1
ELSE 0 END AS HAS_TYPE_1
FROM Table1 x;
If you have a very large number of elements in the table, this won't perform so great - those nested subselects are often a kiss of death when it comes to performance.
For your specific case, you could also use GROUP BY and MAX() and MIN() to speed things up:
SELECT
ID,
CASE WHEN MIN(TYPE_VALUE) = 0 THEN '1' ELSE 0 END AS HAS_TYPE_0,
CASE WHEN MAX(TYPE_VALUE) = 1 THEN '1' ELSE 0 END AS HAS_TYPE_1
FROM Table1
GROUP BY ID;
Instead of select distinct ID, TYPE_VALUE from OTHER_TABLE
use
select ID,
MAX(CASE WHEN TYPE_VALUE =0 THEN 1 END) as has_type_0,
MAX(CASE WHEN TYPE_VALUE =1 THEN 1 END) as has_type_1
from OTHER_TABLE
GROUP BY ID;
You can do the same using PIVOT opearator...