SQL: Get value at index in binary value - sql

Is there a SQL command that could be used in a query, stored procedure, function, that would work against a Binary Type similar to the following C# code?
if (someBinaryArray[index] == 0) {
...
I'm wanting to check if an index of a position in the binary is a certain value instead of pulling the entire array down and doing the comparison?

You can use Substring(), according to the documentation it works with binary columns:
SELECT *
FROM Table
WHERE Substring(column, index, length) = 'blah'
If you really wanted to check for a null (as in your example)... you could do this:
SELECT *
FROM table
WHERE SUBSTRING(column, 3, 1) = CHAR(0)

If you work on MSSQL Server, you can use the READTEXT command
CREATE TABLE #t (b varbinary(1))
DECLARE #ptrval varbinary(16)
SELECT #ptrval = TEXTPTR(mybinarraycolumn)
FROM mytable WHERE pk = #pk
INSERT INTO #t (b)
READTEXT pub_info.pr_info #ptrval #index 1
DECLARE #b varbinary(1)
SELECT #b = b FROM #t

Related

Anything like template literals while writing a search query in SQL?

I am writing a stored procedure to get a particular value.
declare #num int
set #num = (SELECT Id
FROM [sometable]
WHERE Name like '%today%')
-- returns #num = 1
Select Value
FROM [anothertable]
where name like 'days1'
In the last line of the query I want to add "1" or any other number after 'days', depending on the variable #num.
How can I do it, sort of like how we use template literals in Javascript, using the ${} syntax but in SQL?
You can just use the first query as a sub-query of the second:
select [Value]
from anothertable
where [name] = Concat('days', (select Id from sometable where [Name] like '%today%'));

SQL SELECT rows in a table if the given condition is a substring of the column data with a separator ';' between the substrings

I am trying to get rows from the table Mails if the column [To] has the mail abc#mail.
The simple solution would be Select * from Mails where [To] = 'abc#mail'
But the thing is [To] column has data like 123#mail;abc#mail;aabc#mail etc separated by semicolons
where To is multiple emails sent
I know I could do something like Select * from Mails where [To] like '%abc#mail%' but that won't solve the problem if the given mail is a substring of another mail. I thought of a split string solution
I have a split_string function like this,
CREATE FUNCTION [dbo].[split_string]
(
#string_value NVARCHAR(MAX),
#delimiter_character CHAR(1)
)
RETURNS #result_set TABLE(splited_data NVARCHAR(MAX)
)
BEGIN
DECLARE #start_position INT,
#ending_position INT
SELECT #start_position = 1,
#ending_position = CHARINDEX(#delimiter_character, #string_value)
WHILE #start_position < LEN(#string_value) + 1
BEGIN
IF #ending_position = 0
SET #ending_position = LEN(#string_value) + 1
INSERT INTO #result_set (splited_data)
VALUES(SUBSTRING(#string_value, #start_position, #ending_position - #start_position))
SET #start_position = #ending_position + 1
SET #ending_position = CHARINDEX(#delimiter_character, #string_value, #start_position)
END
RETURN
END
which would return splitted string of a single data in a column and the function is working fine.
I tried executing the query
Select *
from Mails
where 'abc#mail' in (
Select *
from dbo.split_string((SELECT [To] FROM Mails) , ';')
)
which is throwing the error:
Subquery returned more than 1 value. This is not permitted when the
subquery follows =, !=, <, <= , >, >= or when the subquery is used as
an expression.
I need help proceeding from here. I am using Microsoft SQL Server 2014.
TL;DR;
Here is the query that you want
SELECT *
FROM dbo.Mails AS m
WHERE EXISTS (
SELECT *
FROM dbo.split_string(m.[To], ';') s
WHERE s.splited_data = 'abc#mail'
)
I recommend the splitting approach. Any character lookup will have to account the variability of the semi-colons, whereas splitting it out will handle the ambiguity of where the semi-colons are, and then you can do a direct equality check. If you wanted to take it a step further and look for additional [To] addresses you can just add an IN clause like this and SQL Server doesn't have to do much more work and you get the same results.
SELECT *
FROM dbo.Mails AS m
WHERE EXISTS (
SELECT *
FROM dbo.split_string(m.[To], ';') s
WHERE s.splited_data IN ('abc#mail', 'def#mail')
)
My answer is fairly similar to #Kitta answer in that we split the data out, and #Kitta is correct about the IN clause, but while their answer will work it will require you grouping your data back together to get a singular answer. Using the EXISTS clause will bypass all of that for you and only give you the data from the original table. That being said, please mark #Kitta as the answer if their answer works just as well for you.
Here is the test setup that I used
DROP TABLE Mails
GO
CREATE TABLE Mails
([To] VARCHAR(3000))
INSERT INTO dbo.Mails
(
[To]
)
VALUES
('123#mail;abc#mail;aabc#mail')
,('nottheone#mail.com')
,('nottheone#mail.com;Overhere#mail.com')
,('aabc#mail;ewrkljwe#mail')
,('ewrkljwe#mail')
GO
DROP FUNCTION [split_string]
GO
CREATE FUNCTION [dbo].[split_string]
(
#string_value NVARCHAR(MAX),
#delimiter_character CHAR(1)
)
RETURNS #result_set TABLE(splited_data NVARCHAR(MAX)
)
BEGIN
DECLARE #start_position INT,
#ending_position INT
SELECT #start_position = 1,
#ending_position = CHARINDEX(#delimiter_character, #string_value)
WHILE #start_position < LEN(#string_value) + 1
BEGIN
IF #ending_position = 0
SET #ending_position = LEN(#string_value) + 1
INSERT INTO #result_set (splited_data)
VALUES(SUBSTRING(#string_value, #start_position, #ending_position - #start_position))
SET #start_position = #ending_position + 1
SET #ending_position = CHARINDEX(#delimiter_character, #string_value, #start_position)
END
RETURN
END
GO
SELECT *
FROM dbo.Mails AS m
WHERE EXISTS (
SELECT *
FROM dbo.split_string(m.[To], ';') s
WHERE s.splited_data = 'abc#mail'
)
and it returns the correct row of '123#mail;abc#mail;aabc#mail'
You can't pass a multi-row subquery as an argument to the dbo.split_string function. Try to join your table function to the Mails table:
SELECT DISTINCT ms.*
FROM Mails AS ms
CROSS APPLY dbo.split_string(ms.[To], ';') AS s
WHERE s.splited_data LIKE 'abc#mail'
If you can ubgrade your SQL Server up to 2016 (13.x), you can use built-in STRING_SPLIT table function instead of custom dbo.split_string.
Alternatively, you can achieve your goal with brute force and break down the comparison into simple terms such as follows:
SELECT *
FROM Mails
WHERE
[To] LIKE 'abc#mail'
OR [To] LIKE '%;abc#mail;%'
OR [To] LIKE 'abc#mail;%'
OR [To] LIKE '%;abc#mail'
It might not be the best way, but it's pretty simple and doesn't require a split function.
You can use CHARINDEX() function.
select * from Mails where CHARINDEX('abc#mail.com', To) > 0
The CHARINDEX function checks for a substring within a given string and returns a value greater than zero if it is found. Here your string to search (substring) would be 'abc#mail.com' and the main string from where to search would be the "To" column.
More information about CHARINDEX() function can be found at below link
https://www.techonthenet.com/sql_server/functions/charindex.php#:~:text=SQL%20Server%3A%20CHARINDEX%20Function%201%20Description.%20In%20SQL,use%20the%20CHARINDEX%20function%20in%20SQL%20Server%20%28Transact-SQL%29.

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

SQL - "incrementing" a char value causes collation error

I'm dealing with a table in which a bunch of arbitrary settings are stored as VARCHAR(255) values. The particular one I'm tasked with dealing with is a sequence number that needs to be incremented and returned to the caller. (Again, note that the sequence "number" is stored as VARCHAR, which is something I don't have any control over).
Because it's a sequence number, I don't really want to select and update in separate steps. When I've dealt with this sort of thing in the past with actual numeric fields, my method has been something like
UPDATE TABLE SET #SEQ_NUM = VALUE = VALUE + 1
which increments the value and gives me the updated value in one swell foop. I thought in this situation, I'd try the same basic thing with casts:
DECLARE #SEQ_NUM VARCHAR(255)
UPDATE SOME_TABLE
SET #SEQ_NUM = VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
WHERE NAME = 'SOME_NAME'
The actual update works fine so long as I don't try to assign the result to the variable; as soon as I do, I receive the following error:
Msg 549, Level 16, State 1, Line 4 The collation
'SQL_Latin1_General_CP1_CI_AS' of receiving variable is not equal to
the collation 'Latin1_General_BIN' of column 'VALUE'.
I understand what that means, but I don't understand why it's happening, or by extension, how to remedy the issue.
As an aside to fixing the specific error, I'd welcome suggestions for alternative approaches to incrementing a char sequence "number".
From one of the comments, sounds like you may have already hit on this, but here's what I would recommend:
UPDATE TABLE
SET VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
OUTPUT inserted.VALUE
WHERE NAME = 'SOME_NAME'
This will output the new value like a SELECT statement does. You can also cast inserted.VALUE to an int if you wanted to do that in the SQL.
If you wanted to put the value into #SEQ_NUM instead of outputing the value from the statement/stored procedure, you can't use a scalar variable, but you can pump it into a table variable, like so:
DECLARE #SEQ_NUM AS TABLE ( VALUE VARCHAR(255) );
UPDATE TABLE
SET VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
OUTPUT inserted.VALUE INTO #SEQ_NUM ( VALUE )
WHERE NAME = 'SOME_NAME'
SELECT VALUE FROM #SEQ_NUM
Maintaining a sequential number manually is by no means a solution I'd like to work with, but I can understand there might be constraints around this.
If you break it down in to 2 steps, then you can work around the issue. Note I've replaced your WHERE clause for this example code to work:
CREATE TABLE #SOME_TABLE ( [VALUE] VARCHAR(255) )
INSERT INTO #SOME_TABLE
( VALUE )
VALUES ( '12345' )
DECLARE #SEQ_NUM VARCHAR(255)
UPDATE #SOME_TABLE
SET [VALUE] = CAST(( CAST([VALUE] AS INT) + 1 ) AS VARCHAR(255))
WHERE 1 = 1
SELECT *
FROM #SOME_TABLE
SELECT #SEQ_NUM = [VALUE]
FROM #SOME_TABLE
WHERE 1 = 1
SELECT #SEQ_NUM
DROP TABLE #SOME_TABLE
You can continue using the quirky update in OP but you have to split the triple assignment #Variable = Column = Expression in the UPDATE statement to two simple assignments of #Variable = Expression and Column = #Variable like this
CREATE TABLE #SOME_TABLE (
NAME VARCHAR(255)
, VALUE VARCHAR(255) COLLATE Latin1_General_BIN
)
INSERT #SOME_TABLE SELECT 'SOME_NAME', '42'
DECLARE #SEQ_NUM VARCHAR(255)
/*
-- this quirky update fails on COLLATION mismatch or data-type mismatch
UPDATE #SOME_TABLE
SET #SEQ_NUM = VALUE = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
WHERE NAME = 'SOME_NAME'
*/
-- this quirky update works in all cases
UPDATE #SOME_TABLE
SET #SEQ_NUM = CAST((CAST(VALUE AS INT) + 1) AS VARCHAR)
, VALUE = #SEQ_NUM
WHERE NAME = 'SOME_NAME'
SELECT *, #SEQ_NUM FROM #SOME_TABLE
This simple rewrite prevents db-engine complaining on difference in data-type between #Variable and Column too (e.g. VARCHAR vs NVARCHAR) and seems like a more "portable" way of doing quirky updates (if there is such thing)

Can You Assign a Function Call to a Local Var in SQL Server's Where Clause

In SQL Server 2012 (or 2014), is it possible to convert a select statement like
... where S.getAge(Handle) >= #ageMin and S.getAge(Handle) <= #ageMax
into something like (in pseudo-code)
... where {set #a = S.getAge(Handle)} and #a >= #ageMin and #a <= #ageMax
SQL Expert Itzik Ben-Gan describes the Logical Query Processing Phases in his book Inside Microsoft SQL Server 2008: T-SQL Querying.
The correct order is as follows:
1. FROM
2. ON
3. OUTER
4. WHERE
5. GROUP BY
6. CUBE | ROLLUP
7. HAVING
8. SELECT
9. DISTINCT
10. ORDER BY
11. TOP
Assignment of a variable is always done in the SELECT part of the statement before the FROM clause.
However, SQL Server allows you to do stuff that you should not TRY.
Lets do a test case. A simple table that counts from 1 to 10 in tempdb and a simple scalar function that takes the number and multiples it by 10 to get the age.
This has some similarities to your example.
-- Use temp db
USE TEMPDB;
GO
-- Create simple table
CREATE TABLE T1
( MYID INT IDENTITY (1,1) );
GO
-- Add 10 records
INSERT INTO T1 DEFAULT VALUES;
GO 10
-- Create a scalar valued function
CREATE FUNCTION GETAGE (#INDEX INT)
RETURNS INT
AS
BEGIN
RETURN (#INDEX * 10);
END
GO
-- Returns 10
DECLARE #A INT;
SELECT #A = DBO.GETAGE(1);
PRINT #A
Here is an example of what you are trying to do below.
The where clause is step 4 in the logical evaluation. It either filters records in or out of the final result. We do have an assignment in the expressions in the WHERE clause. However, expressions should result in TRUE, FALSE or UNKNOWN (NULL).
What does assignment return?
I can only guess not TRUE since a AND between the expressions shows no records. A OR between the expressions shows the records.
-- OR displays 4 to 8, AND = empty result set
DECLARE #A INT = 0;
SELECT MYID FROM TBL1
WHERE
(#A = DBO.GETAGE(MYID)) OR
(DBO.GETAGE(MYID) > 30 AND DBO.GETAGE(MYID) < 90);
PRINT #A;
Last but not least, what are you trying to do?
This code only returns the last assignment.
-- Only displays last assignment
DECLARE #A INT = 0;
SELECT #A = DBO.GETAGE(MYID) FROM TBL1
WHERE (DBO.GETAGE(MYID) > 30) AND (DBO.GETAGE(MYID) < 90);
PRINT #A;
This code return a comma delimited list.
-- Comma delimited list
DECLARE #A VARCHAR(MAX) = '';
SELECT #A += STR(DBO.GETAGE(MYID), 2, 0) + ', ' FROM TBL1
WHERE (DBO.GETAGE(MYID) > 30 AND DBO.GETAGE(MYID) < 90);
IF LEN(#A) > 0 SET #A = SUBSTRING(#A, 1, LEN(#A) - 1);
PRINT #A;