Error in SQL Server of join two tables with case (when...end): - sql

Update: I get an error when join two tables with case (when..end):
tmp1:
A B C
---------------
1 1 1
NULL 1 2
NULL 2 1
2 2 2
tmp2:
A B C D
---------------
1 1 1 X
2 1 1 Y
1 1 2 Z
2 1 2 X
1 2 1 Y
2 2 1 Z
1 2 2 X
2 2 2 Z
I want to join two tables and get tmp1.* + tmp2.D. If tmp1.A is not null, use TMP1.A = TMP2.A, TMP1.B = TMP2.B, AND TMP1.C = TMP2.C to join.
If tmp1.A is null, set tmp1.A = [MIN(TMP2.A) OVER (PARTITION BY TMP2.B, TMP2.C)] and then join. Here is the results what I want:
A B C D
-------------------
1 1 1 X
1 1 2 Z
1 2 1 Y
2 2 2 Y
The following is my codes, I got the error
An expression of non-boolean type specified in a context where a condition is expected, near 'END'
Actually I am not sure how to join them correctly? Any suggestion will be appreciate!
SELECT TMP1.*, TMP2.D
FROM TMP1
LEFT JOIN TMP2 ON CASE
WHEN TMP1.A IS NOT NULL
THEN 'TMP1.A = TMP2.A AND TMP1.B = TMP2.B AND TMP1.C = TMP2.C'
WHEN TMP1.A IS NULL
THEN 'TMP1.A = MIN(TMP2.A) OVER (PARTITION BY TMP2.B, TMP2.C)
AND TMP1.B = TMP2.B
AND TMP1.C = TMP2.C'
END

I don't know if the single quotes are typos or not. If they are intentional, then they are just strings and have nothing to do with the logic being processed (and will generate a syntax error without a comparison of some sort after the case expression)
Assuming the strings represent the logic you do want, you can express the logic without a case expression, using just boolean logic:
SELECT TMP1.*, TMP2.D
FROM TMP1 LEFT JOIN
TMP2
ON (TMP1.A IS NOT NULL AND TMP1.A = TMP2.A AND TMP1.B = TMP2.B AND
TMP1.C = TMP2.C
) OR
(TMP1.A IS NULL AND TMP1.A = MIN(TMP2.A) OVER (PARTITION BY TMP2.B, TMP2.C) AND
TMP1.B = TMP2.B AND TMP1.C = TMP2.C
)
I will don't think this will work, because the window function is not allowed in an ON clause. Perhaps this expresses what you intend:
SELECT TMP1.*, TMP2.D
FROM TMP1 LEFT JOIN
(SELECT TMP2.*,
MIN(TMP2.A) OVER (PARTITION BY TMP2.B, TMP2.C) as MIN_A
FROM TMP2
) TMP2
ON (TMP1.A IS NOT NULL AND TMP1.A = TMP2.A AND TMP1.B = TMP2.B AND
TMP1.C = TMP2.C
) OR
(TMP1.A IS NULL AND TMP1.A = TMP2.MIN_A AND
TMP1.B = TMP2.B AND TMP1.C = TMP2.C
)
Of course, the second part of the boolean expression will always be treated as FALSE (technically it evaluates as NULL) because TMP1.A is NULL and yet you are trying to compare it to something else.
These are very arcane conditions. I wonder if they are really needed. Alternative ways to implement the logic are appropriate for another question, with a better explanation of what you are doing, along with sample data and desired results.

Try this query. I found min(a) for each b and c combinations using correlated subquery, then joined with tmp2 to get final result
select
t.a, t.b, t.c, q.d
from (
select
a = isnull(a.a, (select min(b.a) from tmp2 b where a.b = b.b and a.c = b.c))
, a.b, a.c
from
tmp1 a
) t join tmp2 q on t.a = q.a and t.b = q.b and t.c = q.c

Related

SELECT NOT IN with multiple columns in subquery

Regarding the statement below, sltrxid can exist as both ardoccrid and ardocdbid. I'm wanting to know how to include both in the NOT IN subquery.
SELECT *
FROM glsltransaction A
INNER JOIN cocustomer B ON A.acctid = B.customerid
WHERE sltrxstate = 4
AND araccttype = 1
AND sltrxid NOT IN(
SELECT ardoccrid,ardocdbid
FROM arapplyitem)
I would recommend not exists:
SELECT *
FROM glsltransaction t
INNER JOIN cocustomer c ON c.customerid = t.acctid
WHERE
??.sltrxstate = 4
AND ??.araccttype = 1
AND NOT EXISTS (
SELECT 1
FROM arapplyitem a
WHERE ??.sltrxid IN (a.ardoccrid, a.ardocdbid)
)
Note that I changed the table aliases to things that are more meaningful. I would strongly recommend prefixing the column names with the table they belong to, so the query is unambiguous - in absence of any indication, I represented this as ?? in the query.
IN sometimes optimize poorly. There are situations where two subqueries are more efficient:
SELECT *
FROM glsltransaction t
INNER JOIN cocustomer c ON c.customerid = t.acctid
WHERE
??.sltrxstate = 4
AND ??.araccttype = 1
AND NOT EXISTS (
SELECT 1
FROM arapplyitem a
WHERE ??.sltrxid = a.ardoccrid
)
AND NOT EXISTS (
SELECT 1
FROM arapplyitem a
WHERE ??.sltrxid = a.ardocdbid
)

Better way to test SQL logic gates

I wanted to test some logic gates that I wrote for a query. Glad I did because I wrote the code late in the day and it turned out to have a relatively obvious error.
I want to know if there is any way to make the code cleaner or just look better. I'm thinking the 2 areas that could use improvement are -
How I select the results. Right now this is a case statement that returns 1 or 0, but is there a way to just write something like select (x and y)
How I generate the true/false permutations. I've considered populating a single temp table with 1/0 and reusing it with aliases, but that doesn't clean up TOO much.
select x, y, z, case when (x=1 and y=1 or z=1) then 1 else 0 end as ResultOne, case when (x=1 and (y=1 or z=1)) then 1 else 0 end as ResultTwo
from
( select 1 x
union
select 0 x
) as A
inner join
( select 1 y
union
select 0 y
) as B on 1=1
inner join
( select 1 z
union
select 0 z
) as C on 1=1
order by x,y,z
You can use table valued constructors for something like this pretty easily. Much simpler than all those joins to subqueries. Although with such a small amount of data I don't think it makes much difference. And use a cross join instead of 1 = 1. That is what they are for.
select a.x
, b.y
, c.z
, case when (a.x = 1 and b.y = 1 or c.z = 1) then 1 else 0 end as ResultOne
, case when (a.x = 1 and (b.y = 1 or c.z = 1)) then 1 else 0 end as ResultTwo
from (values(0),(1)) a(x)
cross join (values(0),(1)) b(y)
cross join (values(0),(1)) c(z)
Without table valued constructors, with some math.
select a.x
,b.y
,c.z
,case when (a.x + b.y = 2 or c.z = 1) then 1 else 0 end ResultOne
,case when (a.x = 1 and b.y + c.z >= 1) then 1 else 0 end ResultTwo
from (select 1 x union select 0) a
cross join (select 1 y union select 0) b
cross join (select 1 z union select 0) c;

Different JOIN values depending on the value of another column

I have 2 tables j and c.
Both tables have columns port and sec.
For j.port = ABC, I want to join the 1st 6 characters of c.sec with the 1st 6 characters of j.sec.
For other j.ports, I want to join c.sec = j.sec
How can I do that ?
select c.port,j.port,c.sec,j.sec from j, c
where c.SEC =
CASE WHEN j.port = 'ABC' then SUBSTRING(c.sec,1,6) = SUBSTRING(j.sec,1,6)
--> something like this
else j.sec
Performance wise breaking this into two may be beneficial. The complex join condition will force nested loops otherwise.
SELECT c.port,
j.port,
c.sec,
j.sec
FROM j
JOIN c
ON LEFT(c.sec, 6) = LEFT(j.sec, 6)
WHERE j.port = 'ABC'
UNION ALL
SELECT c.port,
j.port,
c.sec,
j.sec
FROM j
JOIN c
ON c.sec = j.sec
WHERE j.port IS NULL
OR j.port <> 'ABC'
Or in this specific case you could also do
SELECT c.port,
j.port,
c.sec,
j.sec
FROM j
JOIN c
ON LEFT(c.sec, 6) = LEFT(j.sec, 6)
and (j.port = 'ABC' OR c.sec = j.sec)
This allows the main join to be a simple equi join that can use any of the join algorithms with a residual predicate on the result.
For the following example data both of these took about 700ms on my machine whereas I killed the three competing answers after 30 seconds each as none of them completed in that time.
create table c(port varchar(10), sec varchar(10) index ix clustered )
create table j(port varchar(10), sec varchar(10))
INSERT INTO c
SELECT TOP 1000000 LEFT(NEWID(),10) , LEFT(NEWID(),10)
FROM sys.all_objects o1, sys.all_objects o2
INSERT INTO j
SELECT TOP 1000000 LEFT(NEWID(),10) , LEFT(NEWID(),10)
FROM sys.all_objects o1, sys.all_objects o2
You could use:
select c.port,j.port,c.sec,j.sec
from j
join c
on (CASE WHEN j.port = 'ABC' and SUBSTRING(c.sec,1,6) = SUBSTRING(j.sec,1,6) then 1
WHEN c.sec = j.sec THEN 1
END) = 1
The same as:
select c.port,j.port,c.sec,j.sec
from j
join c
on (j.port = 'ABC' and SUBSTRING(c.sec,1,6) = SUBSTRING(j.sec,1,6))
or (c.SEC = j.sec AND (j.port <> 'ABC' or j.port IS NULL))

sql function case returns more than one row

Going to use this query as a subquery, the problem is it returns many rows of duplicates. Tried to use COUNT() instead of exists, but it still returns a multiple answer.
Every table can only contain one record of superRef.
The below query I`ll use in SELECT col_a, [the CASE] From MyTable
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = myTable.sysno AND A_specAttr = 'value')
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo WHERE C_superRef = myTable.sysno AND b_type = 2)
THEN 2
ELSE (SELECT C_intType FROM C
WHERE C_superRef = myTable.sysno)
END
FROM A, B, C
result:
3
3
3
3
3
3...
What if you did this? Because Im guessing you are getting an implicit full outer join A X B X C then running the case statement for each row in that result set.
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM ( SELECT COUNT(*) FROM A ) --This is a hack but should work in ANSI sql.
--Your milage my vary with different RDBMS flavors.
DUAL is what I needed, thanks to Thorsten Kettner
SELECT CASE
WHEN
EXISTS (SELECT 1 FROM A WHERE
A_superRef = 1000001838012)
THEN 3
WHEN EXISTS (SELECT 1 FROM B
INNER JOIN С ON С_ReferenceForB = B_sysNo AND C_superRef = 1000001838012 )
THEN 2
ELSE (SELECT C_type FROM C
WHERE C_superRef = 1000001838012)
END
FROM DUAL

SQL Server Where Clause Case Statement?

I have a Where Clause that checks the existence of rows in a subquery, but I only want to execute that check if a bit is set to 1. So for example:
Select * from Listing l
Where
l.IsDeleted=1
AND CASE WHEN #MustHasPicture = 1 THEN
(
EXISTS
(
SELECT NULL AS [EMPTY]
FROM [dbo].[ListingPictures] AS [lp]
INNER JOIN Listing l ON lp.ListingID=l.ID
)
)
ELSE 1 END = 1
This syntax is wrong, and I'm hoping someone can point me in the right direction. Thanks.
SELECT *
FROM Listing l
WHERE IsDeleted = 1
AND ( #MustHasPicture <> 1 OR
(#MustHasPicture = 1 AND l.id IN (
SELECT listingid
FROM ListingPictures
)
)
)
No need to do a case - if the first part of an and fails, the second part will not be performed.
select
*
from
Listing l
Where
l.IsDeleted = 1
and ((#MustHasPicture = 1 and exists (...)) or 1)
What about this one:
SELECT * FROM Listing l
WHERE l.IsDeleted = 1
AND (#MustHasPicture = 1
AND EXISTS(SELECT * FROM [dbo].[ListingPictures] AS [lp]
WHERE lp.ListingID = l.ID)
OR #MustHasPicture = 0)
But where does the Value #MustHasPicture come from?