What is the best way to equate NULL columns in SQL? - sql

I am performing a MERGE between two tables
MERGE indexdecomp.Constituent targ
USING (SELECT ic.ConstituentName
FROM indexdecomp.IndexConstituents ic) src
ON (((targ.Name = src.ConstituentName) OR (targ.Name IS NULL AND src.ConstituentName IS NULL)))
WHEN NOT MATCHED BY TARGET THEN
UPDATE SET
targ.Name = src.ConstituentName
;
and in my ON clause I have the following predicate:
(targ.Name = src.ConstituentName) OR (targ.Name IS NULL AND src.ConstituentName IS NULL)
I have this predicate since I am considering it a match if both Names are equal or if both names are `null.
Is there a better or more conventional way to handle equality between two null columns? What way would produce the quickest execution?

You can do something like this: (SQL ref)
SET ANSI_NULLS OFF;
MERGE indexdecomp.Constituent targ
USING (SELECT ic.ConstituentName
FROM #IndexConstituents ic) src
ON (((targ.Name = src.ConstituentName)))
WHEN NOT MATCHED BY TARGET THEN
UPDATE SET
targ.Name = src.ConstituentName;
SET ANSI_NULLS ON;
But that seems to be a pretty heavy trade off for lumping off a predicate and neither is very readable. You could actually abstract this mess with a UDF that takes two string arguments and returns a boolean.
Something like:
create function StrNullCompare(#a varchar(max), #b varchar(max))
returns int
as
begin
if ((#a = #b) or (#a is null and #b is null)) return 1;
return 0;
end
-- tests
select dbo.StrNullCompare('wer', 'were');
select dbo.StrNullCompare('wer', 'wer');
select dbo.StrNullCompare('hi', null);
select dbo.StrNullCompare(null, null);
And your predicate becomes:
(dbo.StrNullCompare(targ.Name, src.ConstituentName)=1)

You could try..
ISNULL (targ. Name,'a magic string value') =ISNULL (src.ConstituentName,'a magic string value')
Of course add your own magic string as appropriate for example use newid () to get a guid and use that.
Not really sure if this is "better" than an and or but is a little more human readable; worth benchmarking and testing both approaches though.

Related

How can I replace multiple words of a string in SQL

Is it possible to replace multiple words in a string in sql without using multiple replace functions?
For example I have a string where I need to replace word 'POLYESTER' with 'POLY' , 'COTTON' with 'CTN', 'GRAPHIC' with 'GRPHC' etc in order to keep the string length at a max of say 30 without much loosing the readability of contents in it(can't use substring to limit chars since it can trim the end meaningful parts of string completely). So we decided to short some keywords like above.
Current query I have used :
SELECT
REPLACE(REPLACE('**Some string value **COTTON **Some string value ** POLYESTER', 'POLYESTER', 'POLY'), 'COTTON', 'CTN')
If I have 10 keywords like this, what will be the best way to achieve the result other than using multiple replace function. I am using SQL Server 2012.
considering sql server is your only instrument (not a c# or another application), as a workaroud; use a temp or persistent table to store replacement options.
IF OBJECT_ID('tempdb..#tmp') IS NOT NULL
DROP TABLE #tmp
CREATE TABLE #tmp (
fromText VARCHAR(16),
toText VARCHAR(16)
);
INSERT INTO #tmp (fromText, toText)
VALUES
('POLYESTER', 'POLY'),
('COTTON', 'CTN'),
('GRAPHIC', 'GRPHC')
DECLARE #someValue AS NVARCHAR(MAX) =
'**Some string value **COTTON **Some string value ** POLYESTER';
SELECT #someValue = REPLACE(#someValue, fromText, toText) FROM #tmp;
PRINT #someValue
and the result is:
**Some string value **CTN **Some string value ** POLY.
The answer of mehmetx is actually very nice.
If you need your replacement functionality on a regular basis, you could think about using a normal table instead of a temporary table.
But if you need this logic only once in a while, and performance is not much of an issue, you could avoid the additional replacements table altogether and use a table expression in the FROM clause instead. Something like this:
DECLARE #someValue AS NVARCHAR(MAX) = '**Some string value **COTTON **Some string value ** POLYESTER';
SELECT #someValue = REPLACE(#someValue, fromText, toText)
FROM
(VALUES
('POLYESTER', 'POLY'),
('COTTON', 'CTN'),
('GRAPHIC', 'GRPHC')
) AS S (fromText, toText);
EDIT:
I noticed, that this logic regrettably does not work as expected when used in an UPDATE statement to update existing data in a table.
For that purpose (if needed), I created a user-defined function that performs the replacement logic. I called it MultiReplace. And it does not use the replacement data from a temporary table, but from a "normal" table, which I called Replacements.
The following code demonstrates it. It uses a data table called MyData, which gets updated with all replacements in the Replacements table using the MultiReplace function:
IF OBJECT_ID('MultiReplace') IS NOT NULL
DROP FUNCTION MultiReplace;
IF OBJECT_ID('Replacements') IS NOT NULL
DROP TABLE Replacements;
IF OBJECT_ID('MyData') IS NOT NULL
DROP TABLE MyData;
GO
CREATE TABLE Replacements (
fromText VARCHAR(100),
toText VARCHAR(100)
);
CREATE TABLE MyData (
SomeValue VARCHAR(MAX)
)
GO
CREATE FUNCTION MultiReplace(#someValue AS VARCHAR(MAX))
RETURNS VARCHAR(MAX)
AS
BEGIN
SELECT #someValue = REPLACE(#someValue, fromText, toText) FROM Replacements;
RETURN #someValue;
END;
GO
INSERT INTO MyData (SomeValue)
VALUES
('**Some string value **COTTON **Some string value ** POLYESTER');
INSERT INTO Replacements (fromText, toText)
VALUES
('POLYESTER', 'POLY'),
('COTTON', 'CTN'),
('GRAPHIC', 'GRPHC');
SELECT * FROM MyData;
UPDATE MyData SET SomeValue = dbo.MultiReplace(SomeValue)
SELECT * FROM MyData;
But perhaps using multiple REPLACE statements might be more straightforward after all?...
EDIT 2:
Based on the short conversation in the comments, I could propose a simpler solution that uses multiple REPLACE statements in a clearer way. I have only tested it on SQL Server 2019; I am not sure if it will work correctly on SQL Server 2012.
Again, I use a table called MyData for testing here. But there are no additional database objects anymore.
Regrettably, I did not get it to work with a temporary table containing the replacement values.
-- Preparations:
IF OBJECT_ID('MyData') IS NOT NULL
DROP TABLE MyData;
CREATE TABLE MyData (
SomeValue VARCHAR(MAX)
);
INSERT INTO MyData
VALUES
('**Some string value **COTTON **Some string value ** POLYESTER'),
('**Another string value **GRAPHIC **Another string value ** POLYESTER');
-- Actual work:
SELECT * FROM MyData; -- Show the state before updating
DECLARE #someValue VARCHAR(MAX);
UPDATE MyData
SET
#someValue = SomeValue,
#someValue = REPLACE(#someValue, 'POLYESTER', 'POLY'),
#someValue = REPLACE(#someValue, 'COTTON', 'CTN'),
#someValue = REPLACE(#someValue, 'GRAPHIC', 'GRPHC'),
SomeValue = #someValue;
SELECT * FROM MyData; -- Show the state after updating

Set int var value as wildcard

I've looked into this and I'm just struggling to accept that it isn't possible to do.
I write queries for others to use and normally include some declared variables at the top so the user can "filter" their search at the top of the query and not bother with the rest.
Sometimes the user may not want to specify the values for a particular variable, as they want to return all types. I know this isn't what SQL is for and that there are reporting tools. Any suggestions on how to do this?
DECLARE #foo int, #bar bit
SET #foo = *
SET #bar = *
SELECT *
FROM table
WHERE foo = #foo
AND bar = #bar
* isn't a valid int value and even if an int value could be used as a wildcard, you would need to use LIKE not =.
What you want here is a NULL value and the proper boolean logic.
SELECT {Columns}
FROM [Table]
WHERE (foo = #foo OR #foo IS NULL)
AND (bar = #bar OR #bar IS NULL)
OPTION (RECOMPILE);-- Comment out/remove if you aren't using T-sQL due to incorrect tagging

Using IN with LIKE

OK I have a question to which an answer might be simple but im not sure how to do it:
Question:
How do i use IN with LIKE?
Why Not Duplicate:
Well I know if i have multiple strings i can use OR to check. But this is not the case with what i am trying to do.
Question Explaination:
I have an SP with a parameter #path, now i would like to send multiple paths, separated by delimiter, (to avoid calling sp multiple times). I split the string using a custom function which returns a table with splited values.
Now how would i go about using the values from that splited values table to be used with LIKE operator.
What I have done so far:
declare
#path varchar(max) = 'CompanyRules/Billing/IntegrationServices|CompanyRules/Reports/IntegrationServices',
#default_code varchar(max) = '1'
declare #tempTable TABLE(path varchar(max))
INSERT INTO #tempTable (path)
SELECT split from fn_splitby(#path, '|')
select prg.path, prg.default_code, prmd.optional_property_1, prmd.optional_property_2, prmd.optional_property_3, prmd.optional_property_4, prmd.optional_property_5, prmd.optional_property_6
from pdm_rule_group prg, pdi_rule_master prmd
where prg.path = prmd.path
AND prg.path in (select path from #tempTable)
AND prg.default_code != #default_code
The this will not yield any result.
Possible Solution:
What i though was that i have to loop through the #tempTable and then create separate strings to be used with LIKE. Which im sure is a bad solution, and may have some other solution to it.
Replace this statement
AND prg.path in (select path from #tempTable)
with
AND EXISTS (select 1 from #tempTable where prg.path like "%"+path+"%" )
Just join to the table:
select prg.path, prg.default_code, prmd.optional_property_1, prmd.optional_property_2, prmd.optional_property_3, prmd.optional_property_4, prmd.optional_property_5, prmd.optional_property_6
from
pdm_rule_group prg
inner join
pdi_rule_master prmd
on prg.path = prmd.path
inner join
#tempTable tt
on
prg.path like tt.path
where
prg.default_code != #default_code

Set a parameter in select statement

I am trying to set a parameter in the select statement and then pass it to a user defined function in the same statement. Is this possible? If yes, where is my mistake? If no, then I guess I will have to write a cursor and some temporary tables which I want to avoid.
declare #param varchar(1000)
select Pincode, Name,( #param = AlternateName) as AlternateName
,(select Top(1) part from SDF_SplitString (#param,',')) as NewName from PinCodeTable
You can either get all of the fields out as variables, or get the usual set of rows, but you can't mix and match in a single SELECT. Variables are limited to queries that return no more than one row. (Why do I feel like I'm about to learn something frightening?)
If you are writing a stored procedure and doing something like header/trailer rowsets, you can always return a rowset built from variables:
SELECT #Foo = Foo, #Bar = Bar, ... from Headers where Id = 42
SELECT #Foo as Foo -- Return the first rowset.
SELECT Size, Weight, Color from Trailers where Bar = #Bar -- Return second rowset.

Complex query filter using Like() in T-SQL

I'm writing a SQL script that we want our accounting team to be able to edit, without dealing with engineering.
The general idea is to have a .sql script, which defines some variables at the top of the query, and then has several complex queries below it that use those variables.
The problem we have is that we want the accounting team to be able to specify the filter to use. For example:
DECLARE #year INT
DECLARE #month INT
DECLARE #filter VARCHAR(30);
SET #year = 2010
SET #month = 7
SET #filter = '%test%'
Here the team can change the month and the year that the subsequent queries return. They can also define ONE filter element, in this example, excluding any records where the username has the string 'test' in it.
My question is whether or not there is a way to specify OR's to a LIKE(). Eg, ideally we'd have the #filter variable as something like '%test%, or %other%. Now I know that's not real syntax, but I'm wondering if there is syntax that lets me achieve that. I've scowered MSDN on the LIKE() syntax with no joy. Should I use some different query expression?
Probably the simplest thing to do would be to just have multiple parameters, though it's not pretty:
SET #filter_1 = '%test%'
SET #filter_2 = '%foo%'
SET #filter_3 = '%'
SET #filter_4 = '%'
SELECT *
FROM BAR
WHERE var LIKE #filter_1
OR var LIKE #filter_2
OR var LIKE #filter_3
OR var LIKE #filter_4
OR var LIKE #filter_5
By defaulting them to %, they will always match by default.
You could also use dynamic SQL and a local table variable. Basically, create a local table with one column, allow them to change the INSERT statements into that table, then define a loop that iterates over the contents of that table to dynamically generate the LIKE clauses. It would work, but it would be a bit more code. The example above is quick and dirty, but I'd guess it's probably sufficient for what you need to do.
I'd use a join with a LIKE predicate. You can execute the following code sample in a query window to see how this works:
DECLARE #tblFilter TABLE
(sFilter nvarchar(MAX) NOT NULL);
INSERT #tblFilter
SELECT * FROM (VALUES ('%one%'), ('%two%'), ('%three%')) v(s);
DECLARE #tblData TABLE
(iId int NOT NULL PRIMARY KEY IDENTITY,
sData nvarchar(MAX));
INSERT #tblData(sData)
SELECT * FROM (VALUES ('one'), ('two three'), ('four')) v(s);
SELECT DISTINCT iId
FROM #tblData d
JOIN #tblFilter f ON d.sData LIKE f.sFilter;
I assume that the different query strings are in the #tblFilter table, which could be a TVP, coming from XML values, from comma-separated values, from a temp table or whatever.