How do I compare two columns for equality in SQL Server? - sql

I have two columns that are joined together on certain criteria, but I would also like to check if two other columns are identical and then return a bit field if they are.
Is there a simpler solution than using CASE WHEN?
Ideally I could just use:
SELECT Column1 = Column2 AS MyDesiredResult
FROM Table1
INNER JOIN Table2 ON Table1.PrimaryKey = Table2.ForeignKey

What's wrong with CASE for this? In order to see the result, you'll need at least a byte, and that's what you get with a single character.
CASE WHEN COLUMN1 = COLUMN2 THEN '1' ELSE '0' END AS MyDesiredResult
should work fine, and for all intents and purposes accomplishes the same thing as using a bit field.

CASE WHEN is the better option
SELECT
CASE WHEN COLUMN1 = COLUMN2
THEN '1'
ELSE '0'
END
AS MyDesiredResult
FROM Table1
INNER JOIN Table2 ON Table1.PrimaryKey = Table2.ForeignKey

The use of IIF? And it depends on version of SQL Server.
SELECT
IIF(Column1 = Column2, 1, 0) AS MyDesiredResult
FROM Table;

I'd go with the CASE WHEN also.
Depending on what you actually want to do, there may be other options though, like using an outer join or whatever, but that doesn't seem to be what you need in this case.

Regarding David Elizondo's answer, this can give false positives. It also does not give zeroes where the values don't match.
Code
DECLARE #t1 TABLE (
ColID int IDENTITY,
Col2 int
)
DECLARE #t2 TABLE (
ColID int IDENTITY,
Col2 int
)
INSERT INTO #t1 (Col2) VALUES (123)
INSERT INTO #t1 (Col2) VALUES (234)
INSERT INTO #t1 (Col2) VALUES (456)
INSERT INTO #t1 (Col2) VALUES (1)
INSERT INTO #t2 (Col2) VALUES (123)
INSERT INTO #t2 (Col2) VALUES (345)
INSERT INTO #t2 (Col2) VALUES (456)
INSERT INTO #t2 (Col2) VALUES (2)
SELECT
t1.Col2 AS t1Col2,
t2.Col2 AS t2Col2,
ISNULL(NULLIF(t1.Col2, t2.Col2), 1) AS MyDesiredResult
FROM #t1 AS t1
JOIN #t2 AS t2 ON t1.ColID = t2.ColID
Results
t1Col2 t2Col2 MyDesiredResult
----------- ----------- ---------------
123 123 1
234 345 234 <- Not a zero
456 456 1
1 2 1 <- Not a match

A solution avoiding CASE WHEN is to use COALESCE.
SELECT
t1.Col2 AS t1Col2,
t2.Col2 AS t2Col2,
COALESCE(NULLIF(t1.Col2, t2.Col2),NULLIF(t2.Col2, t1.Col2)) as NULL_IF_SAME
FROM #t1 AS t1
JOIN #t2 AS t2 ON t1.ColID = t2.ColID
NULL_IF_SAME column will give NULL for all rows where t1.col2 = t2.col2 (including NULL).
Though this is not more readable than CASE WHEN expression, it is ANSI SQL.
Just for the sake of fun, if one wants to have boolean bit values of 0 and 1 (though it is not very readable, hence not recommended), one can use (which works for all datatypes):
1/ISNULL(LEN(COALESCE(NULLIF(t1.Col2, t2.Col2),NULLIF(t2.Col2, t1.Col2)))+2,1) as BOOL_BIT_SAME.
Now if you have one of the numeric data types and want bits, in the above LEN function converts to string first which may be problematic,so instead this should work:
1/(CAST(ISNULL(ABS(COALESCE(NULLIF(t1.Col2, t2.Col2),NULLIF(t2.Col2, t1.Col2)))+1,0)as bit)+1) as FAST_BOOL_BIT_SAME_NUMERIC
Above will work for Integers without CAST.
NOTE: also in SQLServer 2012, we have IIF function.

The closest approach I can think of is NULLIF:
SELECT
ISNULL(NULLIF(O.ShipName, C.CompanyName), 1),
O.ShipName,
C.CompanyName,
O.OrderId
FROM [Northwind].[dbo].[Orders] O
INNER JOIN [Northwind].[dbo].[Customers] C
ON C.CustomerId = O.CustomerId
GO
NULLIF returns the first expression if the two expressions are not equal. If the expressions are equal, NULLIF returns a null value of the type of the first expression.
So, above query will return 1 for records in which that columns are equal, the first expression otherwise.

Related

NOT IN vs concatenate columns

Isn't both below SQL the same? I mean functionality wise should do the same thing?
I was expecting this first SQL should have got result as well.
SELECT *
FROM #TEST
WHERE COL1 NOT IN (SELECT COL1 FROM #TEST_1)
AND COL2 NOT IN (SELECT COL2 FROM #TEST_1)
--1 record
SELECT *
FROM #TEST
WHERE COL1 + COL2 NOT IN (SELECT COL1 +COL2 FROM #TEST_1)
CREATE TABLE #TEST
(
COL1 VARCHAR(10),
COL2 VARCHAR(10),
COL3 VARCHAR(10)
)
INSERT INTO #TEST VALUES ('123', '321', 'ABC')
INSERT INTO #TEST VALUES ('123', '436', 'ABC')
CREATE TABLE #TEST_1
(
COL1 VARCHAR(10),
COL2 VARCHAR(10),
COL3 VARCHAR(10)
)
INSERT INTO #TEST_1 VALUES ( '123','532','ABC')
INSERT INTO #TEST_1 VALUES ( '123','436','ABC')
--No result
SELECT *
FROM #TEST
WHERE COL1 NOT IN (SELECT COL1 FROM #TEST_1)
AND COL2 NOT IN (SELECT COL2 FROM #TEST_1)
--1 record
SELECT *
FROM #TEST
WHERE COL1 + COL2 NOT IN (SELECT COL1 + COL2 FROM #TEST_1)
Let's put this into a bit more context and look at your 2 WHERE clauses, which I'm going to call "WHERE 1" and "WHERE 2" respectively:
--WHERE 1
WHERE COL1 NOT IN (SELECT COL1 FROM #TEST_1)
AND COL2 NOT IN (SELECT COL2 FROM #TEST_1)
--WHERE 2
WHERE COL1 + COL2 NOT IN (SELECT COL1 + COL2 FROM #TEST_1)
As you might have noticed, this do not behave the same. In fact, from a logic point of view and the way the database engine would handle them they are completely different.
WHERE 2, to start with is not SARGable. This means that any indexes on your tables would not be able to able to be used and the data engine would have to scan the entire table. For WHERE 1, however, it is SARGable, and if you had any indexes, they could be used to perform seeks, likely helping with performance.
From the point of view of logic let's look at WHERE 2 first. This requires that the concatenated value of COL1 and COL2 not match the other concatenated value of COL1 and COL2; which means these values must be on the same row. So '123456' would match only when Col1 has the value '123' and Col2 the value '456'.
For WHERE 1, however, here the value of Col1 needs to be not found in the other table, and Col2 needs to be not found as well, but they can be on different rows. This is where things differ. As '123' in Col1 appears in both tables (and is the only value) then the NOT IN isn't fulfilled and no rows are returned.
In you wanted a SARGable version of WHERE 2, I would suggest using an EXISTS:
--1 row
SELECT T.COL1, --Don't use *, specify your columns
T.COL2, --Qualifying your columns is important!
T.COL3
FROM #TEST T --Aliasing is important!
WHERE NOT EXISTS (SELECT 1
FROM #TEST_1 T1
WHERE T1.COL1 = T.COL1
AND T1.COL2 = T.COL2);
db<>fiddle
When you add strings in this way (using + instead of concatenation) it adds the two strings and gives you numeric value.
At the first query you are not adding strings so what you did is:
Select all rows from #Test that values of Col1 and Col2 are not in Test1
And actually, only first argument is cutting everything out, since you got 123 values in both tables in col1.
Second query sums that strings, but not by concatenation.
It actually convert varchars to numbers behind the scene.
So the second query does:
Select all rows from #test where COL1+COL2 (its 444 at first row, and 559 in second row) are not in #Test 1
And if you add rows at #Test1, values are:
For the first row COL1+COL2= 655
For the second row COL1+COL2= 559
So only the row with the sum of 444 is not at #Test1, thats why you get 1 row as result.
To sum up:
Thats why you see only 1 row at the second query, and you don't see any records at your first query. At the first query only first condition actually works and cuts everything. And at the second query SQL engine is converting varchars to numerics.
So '123' +'321' is not '123321' but '444'.

Join type (inner, left) and data type casting influences query plan, and order of operations

create or replace table test.bugs.table_one as (
select *, random(1337) as cost
from (
values
('', '2010-01-01', 'one')
, ('10', '2010-01-01', 'two')
, ('11', '2010-01-01', 'three')
, ('12', '2010-01-01', 'four')
)
);
create or replace table test.bugs.table_two as (
select *, random(1337) as budget
from (
values
(9, '2010-01-01', 'one')
, (10, '2010-01-01', 'two')
)
);
with
t1 as (
select
column1::int as column1
, column2
, column3
, cost
from table_one
where column1 !=''
),
t2 as (
select
column1
, column2
, column3
, budget
from table_two
)
select *
from t1
inner join t2
on t1.column1 = t2.column1
and t1.column2 = t2.column2
and t1.column3 = t2.column3;
Returns: 3 rows
Changing the join type to INNER results in error: Numeric value '' is not recognized. Instead of ::int I ended up using try_to_number() function, but it took a bit of trial and error to figure out (query above is simplified, mine was more convoluted).
Is this a bug, or am I doing something odd?
Databases do not guarantee the order of evaluation of expressions. In some databases, your code would always work. In others, it might work sometimes and fail other times.
Is this a bug? I consider it a bug, but clearly some database vendors do not. You have found the work around. Another method would be a case expression:
select (case when column1 regexp '^[0-9]+$' then column1::int end)
This should work, because case should guarantee the order of evaluation of its arguments.
When the join because an inner join things done before or after the join are equal. So things like cast can get hoisted.
The WHERE clause is supposed to evaluate before the SELECT section of t1 CTE.
I just retested by bug submition code, and now the broken case works, but the working case (with the correct TRY_TO_NUMBER fails).
I have queries like your that worked, and then once an extra layer of select around the outside was run with an aggregation over the results, the cast was hoisted back to the error state.
But yes, it's a bug, so I would report it.

Cast varchar that holds some strings to integer field in informix

I have 2 rows from 2 tables in a database that I want to compare.
Column1 is on table1 and is an Integer field with entries like the following
column1
147518
187146
169592
Column2 is on table2 and is a Varchar(15) field with various entries but for this example lets use these 3:
column2
169592
00010000089
DummyId
For my query part of it relies on checking if rows from table1 are linked to the rows in table2, but to do this, I need to compare column1 and column2.
SELECT * FROM table1 WHERE column1 IN (SELECT column2 FROM table2)
The result of this using the data above should be 1 row - 169592
Obviously this wont work (A character to numeric conversion process failed) as they cannot be compared as is, but how do I get them to work?
I have tried
SELECT * FROM table1 WHERE column1 IN (SELECT CAST(column2 AS INTEGER) FROM table2)
and
SELECT * FROM table1 WHERE column1 IN (SELECT (column2::INTEGER) column2 FROM table2)
Using Server Studio 9.1 if that helps.
Try casting the int to a string:
SELECT * FROM table1 WHERE cast(column1 as varchar(15)) IN (SELECT column2 FROM table2)
You can try to use ISNUMERIC in following:
SELECT * FROM table1 WHERE column1 IN (SELECT CASE WHEN ISNUMERIC(column2) = 1 THEN CAST(column2 AS INT) END FROM table2)
For this purpose there is no need to create a special function that you'll not find on other environments.
Let's create a test case for your example:
CREATE TABLE tab1 (
col1 INT,
col2 INT
);
CREATE TABLE tab2 (
col1 VARCHAR(15)
);
INSERT INTO tab1 VALUES(147518,1);
INSERT INTO tab1 VALUES(187146,2);
INSERT INTO tab1 VALUES(169592,3);
INSERT INTO tab2 VALUES(169592);
INSERT INTO tab2 VALUES('00010000089');
INSERT INTO tab2 VALUES('DummyId');
The first query you run was like:
SELECT t1.*
FROM tab1 AS t1
WHERE t1.col1 IN (SELECT t2.col1 FROM tab2 AS t2);
This will raise an error because it tries to compare an INT with a VARCHAR
[infx1210#tardis ~]$ finderr 1213
-1213 A character to numeric conversion process failed.
A character value is being converted to numeric form for storage in a
numeric column or variable. However, the character string cannot be
interpreted as a number. It contains some characters other than white
space, digits, a sign, a decimal, or the letter e; or the parts are in
the wrong order, so the number cannot be deciphered.
If you are using NLS, the decimal character or thousands separator
might be wrong for your locale.
[infx1210#tardis ~]$
Then you've tried to cast a VARCHAR into a INT which resulted in the same error, you should tried the other way:
> SELECT t1.*
> FROM tab1 AS t1
> WHERE t1.col1::CHAR(11) IN (SELECT t2.col1 FROM tab2 AS t2);
>
col1 col2
169592 3
1 row(s) retrieved.
>
Check also if you don't get faster results using the EXISTS:
> SELECT t1.*
> FROM tab1 AS t1
> WHERE EXISTS (
> SELECT 1
> FROM tab2 AS t2
> WHERE t1.col1::CHAR(11) = t2.col1
> );
col1 col2
169592 3
1 row(s) retrieved.
>
Another way possible is to just join the tables:
> SELECT t1.*
> FROM tab1 AS t1
> INNER JOIN tab2 AS t2
> ON (t1.col1 = t2.col1);
col1 col2
169592 3
1 row(s) retrieved.
>
Part of this question was answered by #Stanislovas Kalašnikovas where he said to use the following:
SELECT * FROM table1 WHERE column1 IN (SELECT CASE WHEN ISNUMERIC(column2) = 1 THEN CAST(column2 AS INT) END FROM table2)
But informix does not have a built in function for ISNUMERIC, so the following created it:
create function isnumeric2(inputstr varchar(15)) returning integer;
define numeric_var decimal(15,0);
define function_rtn integer;
on exception in (-1213)
let function_rtn = 0;
end exception with resume
let function_rtn = 1;
let numeric_var = inputstr;
return function_rtn;
end function;
And then the first query above worked for me.

Two or more results of one CASE statement in SQL

Is it possible to SELECT value of two or more columns with one shot of CASE statement? I mean instead of:
select
ColumnA = case when CheckColumn='condition' then 'result1' end
,ColumnB = case when CheckColumn='condition' then 'result2' end
Something like:
select case when CheckColumn='condition' then ColumnA='result1', ColumnB='result2' end
UPDATE
Just the same as we can do with the UPDATE statement:
update CTE
set ColumnA='result1', ColumnB='result2'
where CheckColumn='condition'
It is not possible with CASE expression.
For every column you need new CASE
It is not possible, but you could use a table value constructor as a work around to this, to store each value for columna and columnb against your check column:
SELECT t.CheckColumn,
v.ColumnA,
v.ColumnB
FROM dbo.YourTable AS t
LEFT JOIN
(VALUES
('Condition1', 'Result1', 'Result2'),
('Condition2', 'Result3', 'Result4'),
('Condition3', 'Result5', 'Result6')
) AS v (CheckColumn, ColumnA, ColumnB)
ON v.CheckColumn = t.CheckColumn;
If you have more complex conditions, then you can still apply this logic, but just use a pseudo-result for the join:
SELECT t.CheckColumn,
v.ColumnA,
v.ColumnB
FROM dbo.YourTable AS t
LEFT JOIN
(VALUES
(1, 'Result1', 'Result2'),
(2, 'Result3', 'Result4'),
(3, 'Result5', 'Result6')
) AS v (ConditionID, ColumnA, ColumnB)
ON v.ConditionID = CASE WHEN <some long expression> THEN 1
WHEN <some other long expression> THEN 2
ELSE 3
END;
The equivalent select to the update is:
select 'result1', 'result2'
. . .
where CheckColumn = 'condition';
Your select is different because it produces NULL values. There is an arcane way you can essentially do this with outer apply:
select t2.*
from . . . outer apply
(select t.*
from (select 'result1' as col1, 'result2' as col2) t
where CheckColumn = 'condition'
) t2;
This will return NULL values when there is no match. And, you can have as many columns as you would like.
What I understood from your question is that you want to update multiple columns if certain condition is true.
For such situation you have to use MERGE statements.
Example of using MERGE is as given on msdn here.
Code example:
-- MERGE statement for update.
USE [Database Name];
GO
MERGE Inventory ity
USING Order ord
ON ity.ProductID = ord.ProductID
WHEN MATCHED THEN
UPDATE
SET ity.Quantity = ity.Quantity - ord.Quantity;
More MERGE statement example here.
You could solve this maybe with a CTE or a CROSS APPLY, somehting like
DECLARE #tbl2 TABLE(inx INT, val1 VARCHAR(10),val2 VARCHAR(10));
INSERT INTO #tbl2 VALUES(1,'value1a','value1b'),(2,'value2a','value2b'),(3,'value2a','value2b');
UPDATE yourTable SET col1=subTable.val1,col2=subTable.val2
FROM yourTable
CROSS APPLY(
SELECT val1,val2
FROM #tbl2
WHERE inx=1 --YourCondition
) AS subTable

Detect (find) string in another string ( nvarchar (MAX) )

I've got nvarchar(max) column with different values alike 'A2'
And another column from another table with values alike '(A2 AND A3) OR A4'
I need to detect does string from second column contains string from first column.
So then I need to select all columns of second table which contains an string from first column of first table.
something alike ... but that is wrong
SELECT * Cols FROM T2
WHERE (SELECT T1.StringCol FROM T1) IN T2.StringCol
but I more understand it like it (in f# syntax)
for t1.date, t1.StringCol from t1
for t2.StringCol from t2
if t2.StringCol.Contains( t1.StringCol )
yield t2.StringCol, t1.date
This should get what you want...
select t2.*
from t1 cross join t2
where patindex('%' + t1.StringCol + '%', t2.StringCol) > 0