Better way to test SQL logic gates - sql

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;

Related

What is a better alternative to procedural approach to produce a string based on columns?

I am working on making string based on column values. Example:
SELECT #Registered = a.hasRegistered
,#Subscribed = a.hasSubscribed
FROM AuthorData a
WHERE a.authorId = 10 --#AUTHORID
IF (#Registered = 1)
SET #ReturnString = #ReturnString + '0,'
IF (#Subscribed = 1)
SET #ReturnString = #ReturnString + '1,'
IF EXISTS (
SELECT TOP 1 name
FROM AUTHORDATA AS A
INNER JOIN AUHTORPROFILE B on A.AUTHORID=B.AUTHORID
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE ISNULL(AUTHORDATA.authorId, 0) = 10 --#AUTHORID
)
BEGIN
SET #ReturnString = #ReturnString + '10,'
END
select CONVERT(NVARCHAR(50), STUFF(#ReturnString, LEN(#ReturnString), 1, ''))
This performs the calculation for 1 author (based on WHERE clause IN 2 places -> authorId=10)
I can make this into a function and then call from a select query as follows:
SELECT *,FN_CALCUALTE_OPTIONS(AUTHORID)
FROM AUTHORDATA
I want to ask if there is any way so I can do the calculations in the above SELECT query rather than create the function?
I have tried:
SELECT *,CASE WHEN A.hasRegistered=1 THEN '0,' ELSE '' END +
CASE WHEN A.hasSubscribed=1 THEN '1,' ELSE '' END
FROM AUTHORDATA as A
How can I have the exists part of select?
Pretty sure you can put them all together into a single query as follows. If you didn't need the final stuff you would just have a regular query, but because you need to use the results twice in the stuff CROSS APPLY is a convenient way to calculate it once and use it twice. Also you can correlate your sub-query since you are using the same AuthorData record.
SELECT CONVERT(NVARCHAR(50), STUFF(X.ReturnString, LEN(X.ReturnString), 1, ''))
FROM AuthorData a
CROSS APPLY (
VALUES
(
CASE WHEN a.hasRegistered = 1 THEN '0,' ELSE '' END
+ CASE WHEN a.hasSubscribed = 1 THEN '1,' ELSE '' END
+ CASE WHEN EXISTS (
SELECT 1
FROM AUHTORPROFILE B
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID = C.AUTHORPROFILEID
WHERE B.AUTHORID = a.AuthorId
)
THEN '10,' else '' end
)
) AS X (ReturnString)
WHERE a.AuthorId = 10 --#AUTHORID
The easiest way IMO is to move your criteria being found via EXISTS to an OUTER APPLY. Like so:
SELECT
CONVERT(NVARCHAR(50), STUFF(calc.ReturnString, LEN(calc.ReturnString), 1, ''))
FROM
AUTHORDATA AS A
OUTER APPLY (SELECT TOP (1)
1 AS Found
FROM
AUHTORPROFILE AS B
INNER JOIN AUTHORHISTORY AS C ON B.AUTHORPROFILEID = C.AUTHORPROFILEID
WHERE
B.authorId = A.authorId) AS lookup_author
/*outer apply here just for readibility in final select*/
OUTER APPLY (SELECT
CONCAT(CASE WHEN A.hasRegistered = 1 THEN '0,' ELSE '' END
,CASE WHEN A.hasRegistered = 1 THEN '1,' ELSE '' END
,CASE WHEN lookup_author.Found = 1 THEN '10,' ELSE '' END) AS ReturnString) AS calc;
Then you can use lookup_author.Found = 1 to determine that it was found in your lookup. From there, you just have to apply the rest of your conditions correctly via CASE statements and then use your final SELECT over the result.
You can put a single CASE statement and get data as given below. Make sure that you are covering every possible condition.
SELECT *, CASE WHEN a.Registered =1 AND a.Subscribed =1
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '0,1,10'
WHEN a.Registered =1 AND a.Subscribed =0
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '0,10'
WHEN a.Registered =0 AND a.Subscribed =1
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '1,10'
WHEN a.Registered =0 AND a.Subscribed =0
AND EXISTS (SELECT TOP 1 1
FROM AUHTORPROFILE B on
INNER JOIN AUTHORHISTORY C ON B.AUTHORPROFILEID=C.AUTHORPROFILEID
WHERE B.AUTHORID =A.AUTHORID) THEN '10'
END AS CalculateOptions
FROM AUTHORDATA AS a

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

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

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

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

Oracle SQL division

I have
SELECT
COUNT(*) AS a,
SUM(CASE WHEN r.hn IS NOT NULL THEN 1 ELSE 0 END) AS b,
SUM(CASE WHEN r.hn IS NULL THEN 1 ELSE 0 END) AS c,
( ____ / ____ ) AS d
FROM
x
LEFT JOIN (SELECT DISTINCT xn FROM yn) r ON x.xn = y.xn;
I need the blanks on line 4 to be the values set to 'a' and 'c', I'm not sure what the correct syntax is though.
You can't refer to column aliases in the same level of the query (except in the order by clause), so you have to either repeat the original expression as in #juergend's answer, or use an inline view:
SELECT a, b, c, a/c AS d
FROM (
SELECT
COUNT(*) AS a,
SUM(CASE WHEN y.hn IS NOT NULL THEN 1 ELSE 0 END) AS b,
SUM(CASE WHEN y.hn IS NULL THEN 1 ELSE 0 END) AS c
FROM x
LEFT JOIN (SELECT DISTINCT xn FROM yn) y ON y.xn = x.xn
);
For complicated expressions this is a bit simpler and easier to maintain - if the expression changes you only have to modify it in one place, reducing the risk of a mistake.
If you're trying to make d the ratio of nulls to the total then you just need the division reversed, as c/a; and if you wanted the percentage then100*c/a, possibly rounded or truncated to a certain precision.
And as Clockwork-Muse mentioned, since count() ignores nulls, you coudl use that instead of the two sum() calls:
SELECT a, b, c, a/c AS d
FROM (
SELECT
COUNT(*) AS a,
COUNT(y.hn) AS b,
COUNT(*) - COUNT(y.hn) AS c
FROM x
LEFT JOIN (SELECT DISTINCT xn FROM yn) y ON y.xn = x.xn
);
... or you could calculate c in the outer query too, as (b - a), though that makes the d calculation messier.
The correct syntax is to rewrite the statements again. You can't re-use alias names in the select clause.
SELECT COUNT(*) AS t,
count(r.hn) AS c,
SUM(case when r.hn IS NULL then 1 end) AS u,
count(r.hn) / SUM(case when r.hn IS NULL then 1 end) AS p
FROM h
LEFT JOIN (SELECT DISTINCT hn FROM r) r ON h.hn = r.hn;