Assign null if subquery retrieves multiple records. How can it be done? - sql

I have the following query. I simplified it for demo purpose. I am using SQL Server - t-sql
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID )
from tblMain tm
if the subquery returns multiple records, I like to assign tm.LocID to null else if there is only 1 record returned then assign it to tm.LocID. I am looking for a simple way to do this. Any help would be appreciated.
One way I can see is to have a CASE statement and check if (Count * > 1 ) then assign null else return the value but that would require a select statement within a select statement.

You have the right idea about using a case expression for count(*), but it will not require another subquery:
SELECT tm.LocID = (SELECT CASE COUNT(*) WHEN 1 THEN MAX(LocID) END
FROM tblLoc tl
WHERE tl.LocID = tm.LodID )
FROM tblMain tm

or just use a HAVING clause, like
Select tm.LocID = (select LocID from tblLoc tl
where tl.LocID = tm.LodID
group by locID
having count(*) = 1)
)
from tblMain tm

Your query above (and many of the other answers here) is a correlated subquery which will be very slow since it performs a separate aggregation query on each record. This following will address both your problem and potentially perform a bit better since the count happens in a single pass.
SELECT
CASE
WHEN x.locid IS NOT NULL THEN x.locid
ELSE NULL
END
FROM tblMain m
LEFT JOIN (
SELECT
locid
FROM tblLoc
GROUP BY locid
HAVING COUNT(1) = 1
) x
ON x.locid = m.locid
;
The above is in Postgres syntax (what I'm familiar with) so you would have to make it TSQL compatible.

Related

Using A Count And A Case Statement In One Query

I'm pretty much out of ideas on how to get this to work.I haven't really used SQL in several years so there's a lot I don't remember.
So here is what I would like to happen:
I return the rows where the Code field from table has the value 1208 AND estnumber = 1187216
Run a count on the selection, if 0 run a subquery
If >0 run a different subquery
I didn't get to the subquery part yet because I can't get this to work correctly at all. Right now I just want it to return text.
Here is the latest attempt, I'm actually using db2 but maybe we can ignore that for now and i'll work that part out later because it says the syntax isnt correct, but other validators disagree (if you dont know anything about db2 just use standard sql when giving advice)
SELECT
count(*) AS t
FROM
table
WHERE
(
ESTNUMBER = 1187216
AND CODE = 1208
)
AND CASE WHEN t = 0 THEN 'it is zero' ELSE 'it is not zero' END;
Are you trying to do something like this?
WITH c AS (
SELECT count(*) AS cnt
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
)
SELECT s1.*
FROM subquery1 s1
WHERE (SELECT cnt FROM c) = 0
UNION ALL
SELECT s2.*
FROM subquery2 s2
WHERE (SELECT cnt FROM c) > 0;
This assumes that the columns returned by the subqueries are compatible (same number, same types).
There are better ways to write this query (notably using EXISTS and NOT EXISTS), but this conforms directly to how you asked the question.
The string value should come up in the select clause and not in the where filter.
SELECT
count(*) AS t,
(CASE WHEN count(*) = 0 THEN 'it is zero' ELSE 'it is not zero' END) display_str
FROM
table
WHERE
(
ESTNUMBER = 1187216
AND CODE = 1208
)
You're thinking like an imperative programmer, not a declarative one. That is, SQL doesn't have sequential execution: it's all or nothing.
So, here's the start, the bit that works:
SELECT count(*) AS t
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
Now, to check for the value of count(*), you by now know that WHERE isn't going to work. That's because COUNT is an aggregate function. To look at the result of such of function, you use HAVING.
For your CASE to work, you can move it up into the area that can get count(*) results:
SELECT count(*) AS t
(CASE WHEN count(*) = 0 THEN 'it is zero' ELSE 'it is not zero' END) as msg
FROM table
WHERE ESTNUMBER = 1187216 AND CODE = 1208
Note that "t" is an alias you've given the result of count(*). In most SQL implementations, that alias can't be leveraged in the rest of the statement.
Now, for the either or kind of thing, it would be time to reconsider your approach and what you're really after. You'll probably ultimately have both result sets in your statement and choose how the results are served up.
Something like:
select a.id, a.ct, (case when a.ct=0 then b.amt else c.amt end) as amt
from (select id, count(*) as ct from table1) a
left join (select id, sum(amount) as amt from table2) b on a.id=b.id
left join (select id, sum(amount) as amt from table3) c on a.id=c.id
Hope this helps.

Oracle Query with two 'HAVING' conditions

I have a query and i want to have two HAVING conditions
The first condition is where sum is more than 6000 (Which i have
done)
The second condition is where the COUNT(1) CNT is more than 1 (Which
i need help in)
SELECT SYSDATE,
CUSTOMER.CIF_NO,
CUSTOMER.LONG_NAME_ENG,
TRANSTYPE.short_desc_Eng,
LOCATION.LONG_DESC_ENG ,
COUNT(1) CNT,
SUM(TRANS.AMOUNT) SM
FROM TRANS, CUSTOMER, TRANSTYPE, LOCATION
WHERE TRANS.TRS_AC_CIF = CUSTOMER.CIF_NO
AND TRANS.BRANCH_CODE = LOCATION.BRANCH_CODE
AND TRANS.COMP_CODE = LOCATION.COMP_CODE
AND TRANSTYPE.COMP_CODE = TRANS.COMP_CODE
AND TRANSTYPE.TYPE IN ( 'D' , 'T' )
AND TRANSTYPE.CODE = TRANS.TRX_TYPE
AND TRANS.STATUS = 'P'
AND TRANS.TRS_TYPE = 'R'
AND TRANS.CB_IND = 'C'
GROUP BY CUSTOMER.CIF_NO ,CUSTOMER.LONG_NAME_ENG,
TRANSTYPE.short_desc_Eng, LOCATION.LONG_DESC_ENG
HAVING SUM(TRANS.AMOUNT) > 6000
---------------------------
second having here
----------------------------
ORDER BY CUSTOMER.CIF_NO, CUSTOMER.LONG_NAME_ENG, LOCATION.LONG_DESC_ENG
More than one HAVING clause can not be specified within a SELECT statement, e.g. it's a violation. But add your needed condition such as
HAVING SUM(TRANS.AMOUNT) > 6000 AND COUNT(1) > 1
OR
HAVING SUM(TRANS.AMOUNT) > 6000 OR COUNT(1) > 1
as long as
a GROUP BY clause is present with the SQL statement
aggregations take place within the HAVING clause
P.S. Convert your query syntax to the syntax with explicit JOIN clauses among tables rather than old-style comma-seperated JOINs, and use aliases for the table names

Using Alias in Where Clause

I have a very large case in my select statement, that ends as either 1 or 0, and has an alias name "x". I want to check if "x" = 1 in my WHERE statement, but I know that aliases cannot be used in the where statement. Is my only way of checking for this condition to include the original case statement in the WHERE clause?
You could use CROSS/OUTER APPLY:
SELECT *
FROM tab t
CROSS APPLY (SELECT CASE WHEN t.col ... THEN
-- very complex conditions
END
) sub(c)
WHERE sub.c = ?;
This approach allows you to avoid nested subqueries.
You can put your statement in a cte:
; with CTE as (Select .... as X from ...)
Select *
from CTE
where X = 1
How about even simpler? Your case expression is returning a bit. Seems to me that if you need a where clause there is no need to run the case expression more than once.
select MyReturn = 1
from SomeTable
where case with a whole bunch of logic end = 1
Or if you need it to be parameterized something like this.
select MyReturn = #MyBit
from SomeTable
where case with a whole bunch of logic end = #MyBit
Doesn't a subquery work just fine?
SELECT ST.*
FROM (SELECT TBL.*,
CASE WHEN ComplexCondition THEN 'Something'
ELSE 'SomethingElse'
END AS aliasedColumn
FROM SomeTable
) ST
WHERE ST.aliasedColumn = 'Something';

How to set value to variable in select query in SQL Server 2012?

I want to generate a sequence for a complex query. For that purpose I have used a variable #rowNo.
My logic is :-
If a field - isremoved = 0 then increase row number by 1.
If isremoved = 1 then increase row number by 1 only once till you find next 0.
This is so far I have done, but it gives me syntax error.
DECLARE #rowNo INT;
SET #rowNo = -1;
SELECT
case when sampleTable.isremoved = 0 then #rowNo + 1
else #rowNo end
as rowNumber,
X,
Y,
Z
.
.
FROM tbl_sample sampleTable
INNER JOIN tbl_sample_2 sample2 ON sampleTable.id = sample2.id
.
.
.
This is my desire output :-
So what is the right way to achieve this kind of functionality in sql server 2012 ?
EDIT :-
I do have one solution to use sub query to retrieve row number. But that will hit performance as it is very complex query (more than 20 joins) with huge amount of data.
So please suggest me an alternative.
In SQL Server 2012+, you can do what you want with a cumulative sum:
SELECT sum(case when sampleTable.isremoved = 0 then 1 else 0 end) over
(order by . . .)
. . .
The order by should repeat the order by in the outer query. You can also try using order by (select null)). In my experience, this uses the ordering of the data in the outer query, but this is not documented to always work.
SQL Server does not allow you to set variables in a SELECT and to return values in a result set. One or the other, but not both.
one way to do this is with subquery:
Select (Select count(*) from tbl_sample
where id <= a.id
and a.isRemoved =1) rowNumber,
X, Y, Z
From tbl_sample a
Join tbl_sample_2 b
On b.id = a.id
I assume your rowNumber logic is equivalent to: if previous isremoved = 0 then increment rowNumber by 1, otherwise keep it as it is.
Using a combination of LAG and SUM ... OVER you can easily implement this logic:
SELECT id, isremoved,
SUM(IIF(prevFlag = 0, 1, 0)) OVER (ORDER BY id) AS rowNumber
FROM (
SELECT a.id, isremoved,
COALESCE(LAG(isremoved) OVER (ORDER BY a.id), isremoved) AS prevFlag
FROM tbl_sample AS a
INNER JOIN tbl_sample_2 AS b ON b.id = a.id) AS t
Demo here

Adjust SQL Query to force a record to appear first?

How can the below query be adjusted to return always the member with MemberID = 'xxx' as the first row
SELECT * FROM Members
select * from Members
order by case when MemberID = XXX then 0 else 1 end
This should work and it will also allow you to order the remaining items by MemberID (Assuming xxx=12 in this example)
SELECT *
FROM Members
ORDER BY CASE WHEN MemberID=12 THEN NULL ELSE isnull(MemberID,0) END
If the memberID column can't contain nulls, you can get away with this which might perform slightly better.
SELECT *
FROM Members
ORDER BY CASE WHEN MemberID=12 THEN NULL ELSE MemberID END
SELECT
CASE WHEN MemberID = 'xxx' AS 1 ELSE 0 END CASE AS magic,
*
FROM Members
ORDER BY magic DESC
The syntax might vary depending on yr db, but I hope you get the idea.
SELECT * FROM `Members` WHERE `MemberID` = '[ID]' LIMIT 1 UNION SELECT * FROM `Members`
This should work. Tested on my database instance. Chosen ID is always first.
A more robust solution, if you have more than one record that has to be floated to the top, or if you have a specific order for multiple records, is to add a ResultsOrder column to your table, or even another table MemberOrder(memberid, resultorder). Fill resultorder with big numbers and ...
Select m.*
From Members m
Left Join MemberOrder mo on m.MemberID=mo.MemberID
Order by coalesce(mo.resultorder, 0) DESC
try this:
SELECT * FROM Members
ORDER BY IF(x.MemberId = XXX, -1, ABS(x.MemberId))