Display default value if query results in no records in BigQuery - sql

A query can return an empty table on BigQuery. An example of such occurrence is if I join a bunch of tables in a query on BigQuery and the result of the joins is an empty table, or if there are no matches based on the where clause.
Here is a dumb sample query that will always return in an empty join:
#standardSQL
WITH query1 AS (
SELECT 1 AS number, "one" AS message
), query2 AS (
SELECT 2 AS number, "two" AS message)
SELECT "query result" AS result, query1.*
FROM query1
JOIN query2 ON query1.number = query2.number;
The query will show this output: Query returned zero records.
If that's the case I want to return either a message or a default row. But I don't know how to do that. I've tried using IFNULL, but that's only usuable for one column, not number of columns. Using an IF statement gave me errors as you can't return row(s) from an if statement.
I think the error it gave me was Scalar subquery cannot have more than one column unless using SELECT AS STRUCT to build STRUCT values.
Another thing that I could think of, but don't know how to implement is to add a UNION at the end that would only trigger if the previous parts didn't return anything. Or wrap the existing query in a WITH statement subquery and if that returns nothing, print a message, else do SELECT * FROM sub_query.
I'd like to either just display a message when an empty table is the result, or return a row with some default values.
I understand the answer is likely to contain a UNION statement and hence displaying just a message won't be possible. In that case I'd like to display a default row instead. For the above sample query a default row would look like: "No results found", NULL, NULL.
When the query returns a non empty table, I want it to look exactly like it did with the original query. So there shouldn't be any added columns or change to the schema of the result.

You would use union all. Something like this:
with t as (
. . . <all your query stuff here>
)
select cast(NULL as string) as msg, t.*
from t
union all
select msg, t.* -- all the `t` columns will be `NULL`
from (select 'No rows returned' as msg) left join
t
on 1 = 0 -- always false
where not exists (select 1 from t);
Note the complications. A query returns a fixed set of columns with a fixed set of names. This version returns an extra column at the beginning of the data to contain the message. In order to get all the rest of the columns, a left join is used, but the on clause is always false.

Option 1
Below displays row with all nulls in case if there is no result returned for your_query
#standardSQL
WITH your_query AS ( ... )
SELECT * FROM your_query
UNION ALL
SELECT your_query.* REPLACE ("No results found" AS result)
FROM (SELECT 1)
LEFT JOIN your_query ON FALSE
WHERE NOT EXISTS (SELECT 1 FROM your_query)
Row result number message
1 No results found null null
Option 2
If you know in advance output schema - below returns default row (assuming 0 default for number and "none" default for message
#standardSQL
WITH your_query AS ( ... )
SELECT * FROM your_query
UNION ALL
SELECT "No results found", 0, "none" FROM (SELECT 1)
LEFT JOIN your_query ON FALSE
WHERE NOT EXISTS (SELECT 1 FROM your_query)
Row result number message
1 No results found 0 none

Related

Displaying an alternative result when derrived table is empty

I have this sql code where I try to display an alternative value as a result whenever the table is empty or the the single column of the top row when it is not
select top 1 case when count(*)!=0 then derrivedTable.primarykey
else 0 end endCase
from
(
select top 1 m.primarykey
from mytable m
where 0=1
)derrivedTable
The problem is that when I run this, I get the error message "column 'derrivedTable.primarykey' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
But when I put 'derrivedTable.primarykey' in the group by clause, I just get an empty table.
Does anyone hve a solution?
thanks in advance
You can use aggregation:
select coalesce(max(m.primarykey), 0)
from mytable m;
An aggregation query with no group by always returns exactly one row. If the table is empty (or all rows are filtered out), then the aggregation functions -- except for COUNT() -- return NULL -- which can be transformed to a value using COALESCE().
Such a construct makes me worry. If you are using this to set the primary key on an insert, then you should learn about identity columns or sequences. The database will do the work for you.
Can you try this below script-
SELECT
CASE
WHEN COUNT(*) = 1 THEN derrivedTable.primarykey
ELSE 0
END endCase
FROM
(
SELECT TOP 1 m.primarykey
FROM mytable m
WHERE 0 = 1
) derrivedTable
derrivedTable.primarykey;

Why do these two "not in" queries have different results?

We are trying to weed out records that have a duplicate of certain columns. I built this query to show any row that has an 'N' for its Flag, if there is not a matching 'Y' record in the table with the same last/first name.
select * from Table where LName+FName not in
(select LName+FName from Table where FLAG = 'y')
However this comes back with 0 results. The inner query does return expected results. If I run the inner query, and manually paste in the result values like below, it runs with results.
select * from Table where LName+Fname not in ('DoeJohn','AbelAdam')
What exactly is going on here?
I would suggest looking at your data... NOT IN fails when there is any NULL value. You can try either using NOT EXISTS or filtering any null values:
select * from Table where LName+FName not in
(select LName+FName from Table where FLAG = 'y' AND LName+FName IS NOT NULL)

sql server - how to execute the second half of 'or' clause only when first one fails

Suppose I have a table with following records
value text
company/about about Us
company company
company/contactus company contact
I have a very simple query in sql server as below. I am having problem with the 'or' condition. In below query, I am trying to find text for value 'company/about'. If it is not found, then only I want to run the other side of 'or'. The below query returns two records as below
value text
company/about about Us
company company
Query
select
*
from
tbl
where
value='company/about' or
value=substring('company/about',0,charindex('/','company/about'))
How can I modify the query so the result set looks like
value text
company/about about Us
A bit roundabout, but you can check for the existence of results from the first where clause:
select
*
from
tbl
where
value='company/about' or
(
not exists (select * from tbl where value='company/about')
and
value=substring('company/about',0,charindex('/','company/about'))
)
Since your second condition can be re-written as value = 'company' this would work (at least for the data and query you've presented):
select top(1) [value], [text]
from dbo.MyTable
where value in ('company/about', 'company')
order by len(value) desc
The TOP() ignores the second row if both are found, and the ORDER BY ensures that the first row is always the one with 'company/about', if it exists.

Use of CASE statement values in THEN expression

I am attempting to use a case statement but keep getting errors. Here's the statement:
select TABLE1.acct,
CASE
WHEN TABLE1.acct_id in (select acct_id
from TABLE2
group by acct_id
having count(*) = 1 ) THEN
(select name
from TABLE3
where TABLE1.acct_id = TABLE3.acct_id)
ELSE 'All Others'
END as Name
from TABLE1
When I replace the TABLE1.acct_id in the THEN expression with a literal value, the query works. When I try to use TABLE1.acct_id from the WHEN part of the query, I get a error saying the result is more than one row. It seems like the THEN expression is ignoring the single value that the WHEN statement was using. No idea, maybe this isn't even a valid use of the CASE statement.
I am trying to see names for accounts that have one entry in TABLE2.
Any ideas would be appreciated, I'm kind of new at SQL.
First, you are missing a comma after TABLE1.acct. Second, you have aliased TABLE1 as acct, so you should use that.
Select acct.acct
, Case
When acct.acct_id in ( Select acct_id
From TABLE2
Group By acct_id
Having Count(*) = 1 )
Then ( Select name
From TABLE3
Where acct.acct_id = TABLE3.acct_id
Fetch First 1 Rows Only)
Else 'All Others'
End as Name
From TABLE1 As acct
As others have said, you should adjust your THEN clause to ensure that only one value is returned. You can do that by add Fetch First 1 Rows Only to your subquery.
Then ( Select name
From TABLE3
Where acct.acct_id = TABLE3.acct_id
Fetch First 1 Rows Only)
Fetch is not accepting in CASE statement - "Keyword FETCH not expected. Valid tokens: ) UNION EXCEPT. "
select name from TABLE3 where TABLE1.acct_id = TABLE3.acct_id
will give you all the names in Table3, which have a accompanying row in Table 1. The row selected from Table2 in the previous line doesn't enter into it.
Must be getting more than one value.
You can replace the body with...
(select count(name) from TABLE3 where TABLE1.acct_id = TABLE3.acct_id)
... to narrow down which rows are returning multiples.
It may be the case that you just need a DISTINCT or a TOP 1 to reduce your result set.
Good luck!
I think that what is happening here is that your case must return a single value because it will be the value for the "name" column. The subquery (select acct_id from TABLE2 group by acct_id having count(*) = 1 ) is OK because it will only ever return one value. (select name from TABLE3 where TABLE1.acct_id= TABLE3.acct_id) could return multiple values depending on your data. The problem is you trying to shove multiple values into a single field for a single row.
The next thing to do would be to find out what data causes multiple rows to be returned by (select name from TABLE3 where TABLE1.acct_id= TABLE3.acct_id), and see if you can further limit this query to only return one row. If need be, you could even try something like ...AND ROWNUM = 1 (for Oracle - other DBs have similar ways of limiting rows returned).

SQL - NOT IN explained

I am working in a project which needs top performance in SQL results, and was looking to optimize a query, but after some trial and error I am having some trouble with IN.
-- THIS RETURNS NO RESULTS AT ALL.
SELECT sysdba.HISTORY.TICKETID FROM sysdba.HISTORY
where TICKETID = 't6UJ9A002MJC'
-- THIS RETURNS 4 RESULTS.
SELECT sysdba.C_TICKETPROVIDERS.* FROM sysdba.C_TICKETPROVIDERS
where sysdba.C_TICKETPROVIDERS.TICKETID = 't6UJ9A002MJC'
-- THIS RETURNS NO RESULTS AT ALL.
SELECT sysdba.C_TICKETPROVIDERS.* FROM sysdba.C_TICKETPROVIDERS
where sysdba.C_TICKETPROVIDERS.TICKETID NOT IN
(SELECT DISTINCT sysdba.HISTORY.TICKETID FROM sysdba.HISTORY)
Shouldn't the last query result at least the same 4 results as the previous one, since there are no matching results???
"SELECT DISTINCT sysdba.HISTORY.TICKETID FROM sysdba.HISTORY" returns null, hence the last query doesnt return any rows. Cant compare against null result like that. Use "NOT EXISTS".
The piece NOT IN (NULL) in your WHERE clausole returns false.
Try with:
SELECT sysdba.C_TICKETPROVIDERS.* FROM sysdba.C_TICKETPROVIDERS
where NOT EXISTS
(SELECT TOP 1 1
FROM sysdba.HISTORY
WHERE sysdba.HISTORY.TICKETID = sysdba.C_TICKETPROVIDERS.TICKETID)
The subquery seeks for at least one record.
If it finds it, returns 1, and the NOT EXISTS clausole fails, as expected.
If it doesn't find it, returns NULL, and the NOT EXISTS will succeed.
Just as a lark, try this:
SELECT sysdba.C_TICKETPROVIDERS.* FROM sysdba.C_TICKETPROVIDERS
where sysdba.C_TICKETPROVIDERS.TICKETID NOT IN
(SELECT DISTINCT sysdba.HISTORY.TICKETID
FROM sysdba.HISTORY
Union
Select 'xxxxxxxxxxxxxxxx')
If this works, yr problem is that on your platform, Not In will not function properly against an EMpty set...
The query (SELECT DISTINCT sysdba.HISTORY.TICKETID FROM sysdba.HISTORY) return NULL.
You have:
ticked id 1 is NOT IN (NULL)? FALSE
ticked id 2 is NOT IN (NULL)? FALSE
ticked id 3 is NOT IN (NULL)? FALSE
....and so on....
No record can match your where condition NOT IN. So you get NO RESULTS AT ALL.
Try with this query:
SELECT sysdba.C_TICKETPROVIDERS.* FROM sysdba.C_TICKETPROVIDERS
where NOT EXIST
(SELECT TOP 1 sysdba.HISTORY.TICKETID FROM sysdba.HISTORY
WHERE HISTORY.TICKETID=sysdba.C_TICKETPROVIDERS.TICKETID)
Do the sysdba.History.TicketID and sysdba.C_TICKETPROVIDERS.TICKETID match in terms of datatype and length?
I've also found group by's to be quicker than distinct.
Lastly check the collation types for the database, it might be that the collation is case sensitive and for some reason one is uppercased and the other isnt?
Can you provide the table schemas?