SQL Where with Binary(n) column - sql-server-2012

I have a stored procedure:
ALTER PROCEDURE [dbo].[spUpdateOrInsertNotification]
#ContentJsonHash BINARY(32)
AS
DECLARE #NotificationId INT;
SET #NotificationId = (SELECT #NotificationId
FROM dbo.tblNotifications n
WHERE n.ContentJsonHash = #ContentJsonHash);
IF #NotificationId IS NOT NULL
BEGIN
-- Increment Count
END
ELSE
BEGIN
-- Insert new row.
END
It's supposed to check if the Hash already exists and if it does, increment the count for the row, otherwise insert the row. However, it never finds the Hash and the corresponding NotificationId. NotificationId is always null.
If I run it twice, passing it the same data (a C# array byte[32]). It never finds the same NotificationId and I end up with duplicate entries being put in.
e.g.
NotificationId | ContentJsonHash
9 0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
10 0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
Can I not do comparisons on Binary(n) fields like this WHERE n.ContentJsonHash = #ContentJsonhash ?
The C# code:
using (var conn = new SqlConnection(Sql.ConnectionString))
{
await conn.OpenAsync();
using (var cmd = new SqlCommand(Sql.SqlUpdateOrInsertNotification, conn))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("#Source", notificationMessage.Source);
cmd.Parameters.AddWithValue("#Sender", notificationMessage.Sender);
cmd.Parameters.AddWithValue("#NotificationType", notificationMessage.NotificationType);
cmd.Parameters.AddWithValue("#ReceivedTimestamp", notificationMessage.Timestamp);
cmd.Parameters.AddWithValue("#ContentJSon", notificationMessage.NotificationContent);
cmd.Parameters.AddWithValue("#ContentJsonHash", notificationMessage.ContentHashBytes);
await cmd.ExecuteNonQueryAsync();
}
}
I've also tried calling the stored procedure from SQL like this:
exec dbo.spUpdateOrInsertNotification 'foo', 'bar', 0,
'2017-12-05 15:23:41.207', '{}',
0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A
Calling this twice returns 2 rows :(
I can do this, which works, hard coding the binary field I want to check
select *
from dbo.tblNotifications
where ContentJsonhash = 0xB966C33517993003D789EDF78DA20C4C491617F8F42F76F48E572ACF8EDFAC2A

Binary comparisons can be tricky. If you are using a true binary column, I believe length also comes into play. So even if those bytes are the same, and the lengths differ, the comparison would be false. An easy way is to convert these to strings:
alter procedure [dbo].[spUpdateOrInsertNotification]
#ContentJsonHash BINARY(32)
AS
DECLARE #NotificationId INT;
SET #NotificationId = (SELECT NotificationId
FROM dbo.tblNotifications n
WHERE convert(varchar(32), n.ContentJsonHash, 2) = convert(varchar(32), #ContentJsonHash, 2));
IF #NotificationId IS NOT NULL
BEGIN
-- Increment Count
END
ELSE
BEGIN
-- Insert new row.
END

I had an # where I shouldn't have had an ampersand.
SET #NotificationId = (SELECT #NotificationId
FROM dbo.tblNotifications n
WHERE convert(varchar(32), n.ContentJsonHash, 2) = convert(varchar(32), #ContentJsonHash, 2));
Should be
SET #NotificationId = (SELECT NotificationId
FROM dbo.tblNotifications n
WHERE convert(varchar(32), n.ContentJsonHash, 2) = convert(varchar(32), #ContentJsonHash, 2));
I feel so stupid for not noticing this sooner :(

Related

One-Time Use Randomized Number

Tables(columns) that are important here (there are more in my actual DB but I simplified):
Patients(PatientID, PatientNumber, DOB, Weight, RandomCode)
RandomNumbers(RandomNumberID, Available)
Sites(SiteID, SiteNumber)
Patients does not contain any data and is to be populated via a 3rd party GUI. RandomNumbers contains 50 entries. When a patient's data is first added to Patients, a random number 1-50 is selected, which is tied to RandomNumberID. The "available" column in RandomNumbers is treated as a boolean (T meaning available, F meaning unavailable). After the RandomNumberID is tied to a patient, that RandomNumberID cannot be used again (Available is switched to "F"). If an already used RandomNumber is assigned to a patient upon data entry, another randomly selected RandomNumberID is selected until one is found where Available is T.
Here is the storedproc that I've written so far:
GO
CREATE PROCEDURE uspPatientEntry
#intPatientID AS INTEGER OUTPUT
,#intPatientNumber AS INTEGER
,#intSiteID AS INTEGER
,#intRandomCode AS INTEGER
AS
SET NOCOUNT ON
SET XACT_ABORT ON
BEGIN TRANSACTION
SELECT #intPatientID = MAX (intPatientID) + 1
FROM TPatients TP (TABLOCKX)
SELECT #intPatientID = COALESCE(#intPatientID, 1)
SELECT #intPatientNumber = CONCAT(#intSiteID, #intPatientID)
INSERT INTO TPatients (intPatientID, intPatientNumber, intSiteID, intRandomCodeID)
VALUES (#intPatientID, #intPatientNumber, #intSiteID, #intRandomCode)
COMMIT TRANSACTION
GO
And here is another function I wrote to test it out:
DECLARE #intPatientID AS INTEGER = 0;
DECLARE #intSiteNumber AS INTEGER = 0;
DECLARE #intPatientNumber AS INTEGER = CONCAT(#intSiteNumber, #intPatientID);
DECLARE #intRandomCode AS INTEGER = FLOOR(RAND()*50)+1; -- This just picks a random integer 1-40. <- this is the heart of my question
EXECUTE uspPatientEntry #intPatientID OUTPUT, #intPatientNumber, 2, #intRandomCode
Would something like this work? Ignore syntax, just the general idea:
DECLARE #intRandomCode AS INTEGER;
SELECT #intRandomCode = FLOOR(RAND()*50+1;
FROM TRandomCodes TRC
WHERE TRC.blnAvailable = "T"
UPDATE TRandomCodes TRC
SET TRC.blnAvailable = "F"
WHERE #intRandomCode = TRC.intRandomNumberID;
Thank you

sql loop update with multiple rows returned

I've got to update a column value by decreasing the value in the column by a variable.
There are two conditions:
1. where the row count = 1
2. where the row count is more than 1
I've got it set to do the single row count but need help when the query returns multiple rows.
set #rowsCounted = (select COUNT(QuantityA) from Offers where WID = #wId and ND = #nd)
if(#rowsCounted = 1)
begin
set #QuantityAvailable = (select QuantityA from Offers where WID = #wId and ND = #nd)
set #QuantityAvailable = (select #QuantityAvailable - #QuantityAdjusted)
update Offers
set QuantityA = #QuantityAvailable
where WID = #wId and ND = #nd
end
else
begin
select #rowsCounted as rowsCounted -- example of 4 rows with values of = 287,280,288,288
--begin loop as the QuantityA may contain different values
end
If #QuantityAdjusted is constant for the procedure, then you only need one update statement. Use set-based thought constructs rather than procedural-based ones:
update Offers
set QuantityA = QuantityA - #QuantityAdjusted
where WID = #wId and ND = #nd
This will update in a set-based operation, and there is no need to construct your own loop. This is part of what SQL engines are meant to do.

Using newline character and adding several rows of strings into one

I have in my hands an old app on informix server and I'm migrating data into a different database. Both are informix databases. I have a particular problem with one table. Old app used it to support multiline text.
OldTable:
HeaderID int
LineNum int
Descr nvarchar(50,1)
NewTable:
HeaderID int
Descr lvarchar(max)
So, for each HeaderID I have to read the descriptions ordered by line number and put them all together for insert into a new table. There has to be a newline character between each line for conversion to succeed.
Any tips on how to do this?
If you need to do it from SQL then you can use procedure:
CREATE FUNCTION get_text(aHeaderID int)
RETURNING lvarchar;
DEFINE result lvarchar;
DEFINE vcfld lvarchar;
LET result=NULL;
EXECUTE PROCEDURE IFX_ALLOW_NEWLINE('T');
FOREACH cur1
FOR SELECT Descr INTO vcfld FROM OldTable WHERE HeaderID = aHeaderID ORDER BY LineNum
IF result IS NULL THEN
LET result = vcfld;
ELSE
LET result = result || '
' || vcfld;
END IF;
END FOREACH;
RETURN result;
END FUNCTION;
(notice usage of IFX_ALLOW_NEWLINE and line breaking when updating result)
Then you can fill NewTable using:
UPDATE NewTable SET Descr=get_text(HeaderID);
You can use PreparedStatement. This is example in Jython that uses JDBC Informix driver:
db = DriverManager.getConnection(db_url, usr, passwd)
pstm = db.prepareStatement("SELECT vc FROM src ORDER BY id")
rs = pstm.executeQuery()
lines = []
while (rs.next()):
lines.append(rs.getString(1))
pstm = db.prepareStatement("INSERT INTO dest (lvc) VALUES (?)")
pstm.setString(1, '\n'.join(lines))
rs = pstm.execute()
db.close()

SQL multiple if statements, same result set, different argument

I am writing a stored proc that calculates a WHOLE bunch of different things, but I have a bit in it, that is repeated about 9 times.
eg:
if #argA = 1 (true)
select Count(samples) from dbo.X where type = #argAType
if #argB = 1 (true)
select Count(samples) from dbo.X where type = #argBType
if #argC = 1
select Count(samples) from dbo.X where type = #argCType
and so on...
how can I write a function (or something similar) that I can pass in a bit (true or false), and other argument, and only return the result set if true???
Is this what you're looking for? This is the best I can deduce based on the question as it's currently posted.
SELECT COUNT(samples)
FROM dbo.X
WHERE
(type=#argAType AND #argA=1)
OR
(type=#argBType AND #argB=1)
OR
(type=#argCType AND #argC=1)
In function form, I think this is right:
CREATE FUNCTION GetCount(#n AS BIGINT) RETURNS BIGINT
AS
BEGIN
DECLARE #count BIGINT
SELECT #count = COUNT(samples)
FROM dbo.X
WHERE
(type=#argAType AND #argA=1)
OR
(type=#argBType AND #argB=1)
OR
(type=#argCType AND #argC=1)
RETURN #count
END

Stored procedure to find next and previous row in SQL Server 2005

Right now I have this code to find next and previous rows using SQL Server 2005. intID is the gallery id number using bigint data type:
SQL = "SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec FROM gallery AS p CROSS JOIN gallery AS n where p.galleryid < '"&intID&"' and n.galleryid > '"&intID&"'"
Set rsRec = Server.CreateObject("ADODB.Recordset")
rsRec.Open sql, Conn
strNext = rsRec("nextrec")
strPrevious = rsRec("previousrec")
rsRec.close
set rsRec = nothing
Problem Number 1:
The newest row will return nulls on the 'next record' because there is none. The oldest row will return nulls because there isn't a 'previous record'. So if either the 'next record' or 'previous record' doesn't exist then it returns nulls for both.
Problem Number 2:
I want to create a stored procedure to call from the DB so intid can just be passed to it
TIA
This will yield NULL for previous on the first row, and NULL for next on the last row. Though your ordering seems backwards to me; why is "next" lower than "previous"?
CREATE PROCEDURE dbo.GetGalleryBookends
#GalleryID INT
AS
BEGIN
SET NOCOUNT ON;
;WITH n AS
(
SELECT galleryID, rn = ROW_NUMBER()
OVER (ORDER BY galleryID)
FROM dbo.gallery
)
SELECT
previousrec = MAX(nA.galleryID),
nextrec = MIN(nB.galleryID)
FROM n
LEFT OUTER JOIN n AS nA
ON nA.rn = n.rn - 1
LEFT OUTER JOIN n AS nB
ON nB.rn = n.rn + 1
WHERE n.galleryID = #galleryID;
END
GO
Also, it doesn't make sense to want an empty string instead of NULL. Your ASP code can deal with NULL values just fine, otherwise you'd have to convert the resulting integers to strings every time. If you really want this you can say:
previousrec = COALESCE(CONVERT(VARCHAR(12), MIN(nA.galleryID)), ''),
nextrec = COALESCE(CONVERT(VARCHAR(12), MAX(nB.galleryID)), '')
But this will no longer work well when you move from ASP to ASP.NET because types are much more explicit. Much better to just have the application code be able to deal with, instead of being afraid of, NULL values.
This seems like a lot of work to get the previous and next ID, without retrieving any information about the current ID. Are you implementing paging? If so I highly recommend reviewing this article and this follow-up conversation.
Try this (nb not tested)
SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec
FROM gallery AS p
CROSS JOIN gallery AS n
where (p.galleryid < #intID or p.galleryid is null)
and (n.galleryid > #intID or n.galleryid is null)
I'm assuming you validate that intID is an integer before using this code.
As for a stored procedure -- are you asking how to write a stored procedure? If so there are many tutorials which are quite good on the web.
Since Hogan contributed with the SQL statement, let me contribute with the stored proc part:
CREATE PROCEDURE spGetNextAndPreviousRecords
-- Add the parameters for the stored procedure here
#intID int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
SELECT TOP 1 max(p.galleryID) as previousrec, min(n.galleryID) AS nextrec
FROM gallery AS p
CROSS JOIN gallery AS n
where (p.galleryid < #intID or p.galleryid is null)
and (n.galleryid > #intID or n.galleryid is null)
END
And you call this from code as follows (assuming VB.NET):
Using c As New SqlConnection(ConfigurationManager.ConnectionStrings("ConnectionString").ConnectionString)
c.Open()
Dim command = New SqlCommand("spGetNextAndPreviousRecords")
command.Parameters.AddWithValue("#intID", yourID)
Dim reader as SqlDataReader = command.ExecuteReader()
While(reader.Read())
' read the result here
End While
End Using