Use of CASE statement values in THEN expression - sql

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

Related

Display default value if query results in no records in BigQuery

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

Associated records

I'm pretty new to SQL. I'm trying to write a query that will grab records associated that share an value with the sought after record in a single query.
For example below, if one record has a 'No' in it, I want it to to then return all records that share a common 'Letter'
Letter;Present
A;Yes
A;No
A;Yes
B;Yes
B;Yes
B;Yes
Returning:
Letter;Present
A;Yes
A;No
A;Yes
Use a sub-query to find those No letters. Then search for rows where values first character is in the found No letters.
select * from table
where letter in (select letter from table where present = 'No');
You will need to use a subquery for that..
SELECT a.letter, a.present
FROM yourTable a
WHERE a.letter IN (SELECT letter
FROM yourTable
WHERE present = 'No');
What you do here is select all letters where present = 'NO' then you simply re-select from that group where your letter is in the list of letter that contains a No.
In other words, the subquery is a filter.
You can use the EXISTS clause:
SELECT *
FROM yourTable t1
WHERE EXISTS (SELECT 1
FROM yourTable t2
WHERE t1.Letter = t2.Letter
AND t2.Present = 'No')
The subquery will get every record with Present = No.
The main query will get every record that share the Letter value with a result from the subquery.

compare data between 2 table

Hey i have a requirement to compare two tables of same structure.
Table1
EmpNO - Pkey
EmpName
DeptName
FatherName
IssueDate
ValidDate
I need to pass the EMPNO as parameter and I need to compare whether any of the column get changes? and return YES OR NO value.
can I able to do that using a PL/SQL Funcation? I was thinking of using the CONCAT in-build function to do that.
I'm trying the below one
Table1Concat = Select CONCAT(Column1.....6) from tbale1 where emp_no= in_empno;
Table2Concat = Select CONCAT(Column1.....6) from tbale2 where emp_no= in_empno;
IF(Table1Concat<>Table2Concat ) THEN return data_changed :='YES';
else data_changed :='NO';
END;
If you only want to detect whether any value is different then ...
select count(*)
from (select * from table1 where emp_no = my_emp_no
union
select * from table2 where emp_no = my_emp_no
)
If it returns 1 then the rows are the same, if it returns 2 then there is a difference.
The columns must be in the same order for this to work, or you'll have to list out all the column names in the order in which they match.
If you wanted to do this in bulk for a great many rows then you'd most likely use a different solution, s do not loop through every emp_no running this code for each one.
For bulk data where all emp_id's are present in both tables, use a query of the form:
select table1.emp_no,
case when table1.column1 = table2.column1 and
table2.column2 = table2.column2 and
table2.column3 = table2.column3 and
...
then 'Yes'
else 'No
end columns_match
from table1
join table2 on table1.emp_no = table2.emp_no
You can insert this result directly into a logging table.
Take care of null values though. "any_value = null" is never true, and "any_value != Null" is also never true, so you might need to add logic to take care of cases where one or both values are null.

What is the difference between the IN operator and = operator in SQL?

I am just learning SQL, and I'm wondering what the difference is between the following lines:
WHERE s.parent IN (SELECT l.parent .....)
versus
WHERE s.parent = (SELECT l.parent .....)
IN
will not generate an error if you have multiple results on the subquery. Allows to have more than one value in the result returned by the subquery.
=
will generate an error if you have more than one result on the subquery.
SQLFiddle Demo (IN vs =)
when you are using 'IN' it can compare multiple values....like
select * from tablename where student_name in('mari','sruthi','takudu')
but when you are using '=' you can't compare multiple values
select * from tablenamewhere student_name = 'sruthi'
i hope this is the right answer
The "IN" clause is also much much much much slower. If you have many results in the select portion of
IN (SELECT l.parent .....),
it will be extremely inefficient as it actually generates a separate select sql statement for each and every result within the select statement ... so if you return 'Cat', 'Dog', 'Cow'
it will essentially create a sql statement for each result... if you have 200 results... you get the full sql statement 200 times...takes forever... (This was as of a few years ago... maybe imporved by now... but it was horribly slow on big result sets.)
Much more efficient to do an inner join such as:
Select id, parent
from table1 as T
inner join (Select parent from table2) as T2 on T.parent = T2.parent
For future visitors.
Basically in case of equals (just remember that here we are talking like where a.name = b.name), each cell value from table 1 will be compared one by one to each cell value of all the rows from table 2, if it matches then that row will be selected (here that row will be selected means that row from table 1 and table 2) for the overall result set otherwise will not be selected.
Now, in case of IN, complete result set on the right side of the IN will be used for comparison, so its like each value from table 1 will be checked on whether this cell value is present in the complete result set of the IN, if it is present then that value will be shown for all the rows of the IN’s result set, so let say IN result set has 20 rows, so that cell value from table 1 will be present in overall result set 20 times (i.e. that particular cell value will have 20 rows).
For more clarity see below screen shot, notice below that how complete result set from the right of the IN (and NOT IN) is considered in the overall result set; whole emphasis is on the fact that in case comparison using =, matching row from second table is selected, while in case of IN complete result from the second table is selected.
In can match a value with more than one values, in other words it checks if a value is in the list of values so for e.g.
x in ('a', 'b', 'x') will return true result as x is in the the list of values
while = expects only one value, its as simple as
x = y returns false
and
x = x returns true
The general rule of thumb is:
The = expects a single value to compare with. Like this:
WHERE s.parent = 'father_name'
IN is extremely useful in scenarios where = cannot work i.e. scenarios where you need the comparison with multiple values.
WHERE s.parent IN ('father_name', 'mother_name', 'brother_name', 'sister_name')
Hope this is useful!!!
IN
This helps when a subquery returns more than one result.
=
This operator cannot handle more than one result.
Like in this example:
SQL>
Select LOC from dept where DEPTNO = (select DEPTNO from emp where
JOB='MANAGER');
Gives ERROR ORA-01427: single-row subquery returns more than one row
Instead use
SQL>
Select LOC from dept where DEPTNO in (select DEPTNO from emp
where JOB='MANAGER');
1) Sometimes = also used as comparison operator in case of joins which IN doesn't.
2) You can pass multiple values in the IN block which you can't do with =. For example,
SELECT * FROM [Products] where ProductID IN((select max(ProductID) from Products),
(select min(ProductID) from Products))
would work and provide you expected number of rows.However,
SELECT * FROM [Products] where ProductID = (select max(ProductID) from Products)
and ProductID =(select min(ProductID) from Products)
will provide you 'no result'. That means, in case subquery supposed to return multiple number of rows , in that case '=' isn't useful.

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.