Postgresql work with null rows after joins - sql

For example, we have next query:
select A.id, A.field > (case when A.cond1 then
(case when B.field is null then 0 else B.field end) else
(case when C.field is null then 0 else C.field end) end)
from A left join B on B.A_id = A.id left join C on C.A_id = A.id;
Is there way to simplify work with replacing null to 0?

Yes, with COALESCE:
COALESCE(column_that_might_be_null, 0)
It accepts multiple arguments and works left to right returning the first non null:
COALESCE(column_that_might_be_null, another_possible_null, third_maybe_null, 0)
Is the equivalent of
CASE
WHEN column_that_might_be_null IS NOT NULL THEN column_that_might_be_null
WHEN another_possible_null IS NOT NULL THEN another_possible_null
WHEN third_maybe_null IS NOT NULL THEN third_maybe_null
ELSE 0
END
And it is SQL standard so should work on all compliant databases
PG also supports ISNULL and IFNULL which work similarly but I don't usually recommend them over COALESCE because they don't exist in all databases/don't necessarily work equivalently and aren't as flexible because they only accept 2 arguments. For me this isn't enough to justify saving 2 characters.. (And if you forget about COALESCE, and you end up doing ISNULL(ISNULL(ISNULL(first, second), third), 0) the SQL is more messy)

Related

SELECT a column whose value is "if Id exists in another table/view"

This does what I want:
SELECT a.*,
IIF(l.Id IS NOT NULL,
CAST(1 AS BIT),
CAST(0 AS BIT)) AS IsLatest
FROM [A] a
LEFT JOIN [LatestA] l on (l.Id = a.Id)
LatestA is a view that contains only the latest A's.
However, I feel like the IIF(..., TRUE, FALSE) is silly. Is there a better way of testing for the existance of the Id in the LatestA?
Your query is fine. Some RDBMS have shortcuts to turn a the result of a predicate to a boolean value, but not SQL Server.
You can slightly simplify it as follows:
SELECT
a.*,
CASE WHEN l.Id IS NULL THEN 0 ELSE 1 END AS IsLatest
FROM [A] a
LEFT JOIN [LatestA] l on l.Id = a.Id
Note that I used a CASE expression instead of the IIF() function: I prefer it because it is standard SQL. I also find that it is simpler to use IS NULL rather than IS NOT NULL here.
If you really want to return a BIT for some reason, then:
SELECT
a.*,
CAST(CASE WHEN l.Id IS NULL THEN 0 ELSE 1 END AS BIT) AS IsLatest
FROM [A] a
LEFT JOIN [LatestA] l on l.Id = a.Id
T-SQL doesn't allow you to use boolean expressions as integer values (as MySQL does) because it has a third possible boolean value (UNKNOWN) so you are stuck with something like what you have. You can simplify it somewhat:
SELECT a.*,
CAST(IIF(l.Id IS NOT NULL, 1, 0) AS BIT) AS IsLatest
FROM [A] a
LEFT JOIN [LatestA] l on (l.Id = a.Id)
you can use EXISTS operator, it's used to check the existence of a record
if exists ([query for ID])
BEGIN
[Quert from another table]
END

Return true (1), if a joined data entry exists, else false (0) in MS-SQL

I have an SQL statement that joins to two different tables in a 1 to 1 relationship each. For each of those joins, I want to return a BIT value (0 or 1), if the join was successful or not.
Let's say, we have a base table Base and the tables A and B, which are joined together via a LEFT JOIN on a common ID. I want to return the ID, as well as a field IsA and a field IsB.
What would be the best-practice solution to do this in Microsoft SQL Server most efficiently?
By the way, my current approach is this:
CAST(ISNULL(A.ID, 0) AS BIT) AS IsA,
CAST(ISNULL(B.ID, 0) AS BIT) AS IsB
You can use the following, using CASE WHEN instead of ISNULL:
SELECT Base.*, A.id, B.id,
CAST(CASE WHEN A.id IS NULL THEN 0 ELSE 1 END AS BIT) AS IsA,
CAST(CASE WHEN B.id IS NULL THEN 0 ELSE 1 END AS BIT) AS IsB
FROM Base LEFT JOIN A ON Base.id = A.base_id
LEFT JOIN B ON Base.id = B.base_id
demo on dbfiddle.uk
This solution, compared to your current approach, has the same efficiency. But also see this answer (check multiple columns for NULL values). There you can see the ISNULL solution is less efficient. In your case it makes no big difference.
Also be careful: The ISNULL can also return 0 in case the column values is 0. So with your current approach you would get False in such a case.
Personally I would do it this way:
Cast(CASE WHEN A.ID IS NULL THEN 0 ELSE 1 END AS bit) AS IsA

Using select case on a left join?

I have used a left join on two of my tables. Now I want to use case to identify the records from my left table who don't have a match in the right table. Such records exist and have a null value in the 'id_zeus' column of my join, however when I execute the case, it is as these fields don't exist. Where am I going wrong ? I get "Present" in all my column Disturbance. I am using Oracle SQL developer.
SELECT
CASE DP.ID_PRB
WHEN NULL
THEN 'Absence'
ELSE 'Present' END as Disturbance,
FROM
FIRE.WSITE WI
LEFT JOIN
(SELECT DISTINCT
DPL.ID_PERT as ID_PRB
FROM FIRE.DEPPLAN DPL
GROUP BY DPL.ID_PERT
) DPL
ON WI.ID_PERT = DP.ID_PERT
What is const? You don't seem to need it. The SELECT DISTINCT and GROUP BY are redundant, so use only one of them. And your alias on the subquery is incorrect.
But your problem is the comparison to NULL. It doesn't even match when doing a comparison as you are doing in CASE. You need to use IS NULL:
SELECT (CASE WHEN DP.ID_PRB IS NULL THEN 'Absence' ELSE 'Present'
END) as Disturbance,
FROM FIRE.WSITE WI LEFT JOIN
(SELECT DISTINCT DPL.ID_PERT as ID_PRB
FROM FIRE.OSI_DEVIATION_PLANS DP
) DP
ON WI.ID_PERT = DP.ID_PERT;
This query would commonly be written as:
SELECT (CASE WHEN NOT EXISTS (SELECT 1
FROM FIRE.OSI_DEVIATION_PLANS DP
WHERE WI.ID_PERT = DP.ID_PERT
)
THEN 'Absence' ELSE 'Present'
END) as Disturbance,
FROM FIRE.WSITE WI ;
This offers more opportunities for optimization.

How to return Yes or No if nested query has result or not in SQL Server?

I have a stored procedure with a nested query that checks whether "category" from the main table matches a "category" in a sub table.
So there can either be one match or none.
How can I return Yes if there is a match and the sub query returns something and No if there is no match and the sub query returns nothing ?
I tried the following which works in general but only if there is a match as otherwise this returns nothing.
My SQL (shortened):
SELECT A.categoryID,
A.category,
A.[description],
(
SELECT 'Yes' AS subscribed
FROM MOC_Categories_Subscribers D
WHERE D.category = A.category
FOR XML PATH(''), ELEMENTS, TYPE
)
FROM MOC_Categories A
If subquery doesn't return any rows then your result will be NULL. Thus you need to check it. In SQL Server you can do this by using functions ISNULL and COALESCE, it depends on version that you're using
SELECT A.categoryID,
A.category,
A.[description],
COALESCE((SELECT TOP 1 'Yes'
FROM MOC_Categories_Subscribers D
WHERE D.category = A.category), 'No') AS Result
FROM MOC_Categories A
SELECT A.categoryID,
A.category,
A.[description],
(
SELECT
case
when count(subscribed) > 0 then 'Yes'
else 'No'
end
FROM MOC_Categories_Subscribers D
WHERE D.category = A.category
)
FROM MOC_Categories A
You can use an outer join, which returns null values if there is no match. Combine with a case to convert to a yes/no value:
SELECT A.categoryID,
A.category,
A.[description],
subscribed = CASE
WHEN D.category IS NOT NULL THEN 'Yes'
ELSE 'No'
END,
FROM MOC_Categories A
LEFT OUTER JOIN MOC_Categories_Subscribers D
ON D.category = A.category

SQL Conditional Cast VARCHAR for FLOAT

When I run the following query I get error "Cannot cast VARCHAR to FLOAT". The problem is some else on my team designed the DB a while back and put Milliseconds as varchar and sSometime the value isn't there so he put "NA" instead.
Now when I cast the value for Milliseconds to take the AVE(), "NA" cannot be casted. Is there a way to define default value for "NA", like:
IF a.Milliseconds == "NA"
0
ELSE
CAST(a.Milliseconds AS FLOAT)
SELECT
b.Date,
AVG(CAST(a.Milliseconds AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
GROUP BY b.Date
Sample Data
CyclesId | Milliseconds
1 | 24.1557
2 | 23.4886
3 | NA
Use a CASE statement around your value
SELECT
b.Date,
AVG(CAST(CASE WHEN a.Milliseconds = 'NA' THEN '0' ELSE a.Milliseconds END AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
GROUP BY b.Date
If you want the NA values excluded all together then you will do the following:
SELECT
b.Date,
AVG(CAST(a.Milliseconds AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
WHERE a.Milliseconds != 'NA'
GROUP BY b.Date
see SQL Fiddle with Demo
use CASE... for example...
SELECT
b.Date,
AVG(CAST( (case when a.Milliseconds = 'NA' then 0 else a.Milliseconds end) AS FLOAT)) AS Milliseconds,
FROM Fields a
INNER JOIN Cycles b
ON a.CyclesId = b.Id
GROUP BY b.Date