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
Related
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 :(
I've got a single column that contains a set of names in it. I didn't design the database so that it contains multiple values in one column, but as it is I've got to extract that information now.
The problem is that in one field I've got multiple values like in this example:
"Jack Tom Larry Stan Kenny"
So the first three should be one group, and the other ones on the far right are another group. (Basically the only thing that separates them in the column is a specific number of whitespace between them, let's say 50 characters.)
How can I split them in pure SQL, so that I can get two columns like this:
column1 "Jack Tom Larry"
column2 "Stan Kenny"
A fairly simplistic answer would be to use a combination of left(), right() and locate(). Something like this (note I've substituted 50 spaces with "XXX" for readability):
declare global temporary table session.x(a varchar(100))
on commit preserve rows with norecovery;
insert into session.x values('Jack Tom LarryXXXStan Kenny');
select left(a,locate(a,'XXX')-1),right(a,length(a)+1-(locate(a,'XXX')+length('XXX'))) from session.x;
If you need a more general method of extracting the nth field from a string with a given separator, a bit like the split_part() function in PostgreSQL, in Ingres your options would be:
Write a user defined function using the Object Management Extension (OME). This isn't entirely straightforward but there is an excellent example in the wiki pages of Actian's community site to get you started:
http://community.actian.com/wiki/OME:_User_Defined_Functions
Create a row-producing procedure. A bit more clunky to use than an OME function, but much easier to implement. Here's my attempt at such a procedure, not terribly well tested but it should serve as an example. You may need to adjust the widths of the input and output strings:
create procedure split
(
inval = varchar(200) not null,
sep = varchar(50) not null,
n = integer not null
)
result row r(x varchar(200)) =
declare tno = integer not null;
srch = integer not null;
ptr = integer not null;
resval = varchar(50);
begin
tno = 1;
srch = 1;
ptr = 1;
while (:srch <= length(:inval))
do
while (substr(:inval, :srch, length(:sep)) != :sep
and :srch <= length(:inval))
do
srch = :srch + 1;
endwhile;
if (:tno = :n)
then
resval=substr(:inval, :ptr, :srch - :ptr);
return row(:resval);
return;
endif;
srch = :srch + length(:sep);
ptr = :srch;
tno = :tno + 1;
endwhile;
return row('');
end;
select s.x from session.x t, split(t.a,'XXX',2) s;
I'm looking to parse a sql column result into separate columns. Here is an example of the column...
Detail - Column name
'TaxID changed from "111" to "333". Address1 changed from "542 Test St." to "333 Test St". State changed from "FL" to "DF". Zip changed from "11111" to "22222". Country changed from "US" to "MX". CurrencyCode changed from "usd" to "mxn". RFC Number changed from "" to "test". WarehouseID changed from "6" to "1". '
I need to take the old TAXID, new TAXID, old country, and new country and put them in separate columns.
The Detail column will always have TAXID and Country, however the challenging part is that they don't always have the rest of data that I listed above. Sometimes it will contain city and other times it won't. This means the order is always different.
I would create a tsql proc, use a case statement.
Do a count of the double quotes. If there are 8 oairs, you know that you old and new values, only 4 pairs you only have new values.
Then using the double quotes as indexes for your substring, you can put the vales into the table.
Good luck!
I was able to come up with something that worked.
In case anyone else gets a situation like this again perhaps posting my code will help.
DECLARE #document varchar(350);
set #document = 'TaxID changed from "111" to "222"'
declare #FIRSTQUOTE int
declare #SECONDQUOTE int
declare #OLDTAXID nvarchar(40)
declare #firstlength int
declare #ThirdQuote int
declare #FourthQuote int
declare #secondlength int
declare #NewTAXID nvarchar(40)
declare #oneplussecondquote int
declare #oneplusthirdquote int
select #FirstQuote = CHARINDEX('"',#document)
set #FIRSTQUOTE = #FIRSTQUOTE + 1
select #SECONDQUOTE = CHARINDEX('"',#document,#FIRSTQUOTE)
set #firstlength = #SECONDQUOTE - #FIRSTQUOTE
select #OLDTAXID = SUBSTRING(#document,#FIRSTQUOTE,#firstlength)
set #oneplussecondquote = #SECONDQUOTE + 1
select #ThirdQuote = CHARINDEX('"',#document,#oneplussecondquote)
set #oneplusthirdquote = #ThirdQuote + 1
select #FourthQuote = CHARINDEX('"',#document,#oneplusthirdquote)
select #secondlength = #FourthQuote - #oneplusthirdquote
select #NewTAXID = SUBSTRING(#document,#oneplusthirdquote,#secondlength)
You can switch out the string for this: 'Country changed from "US" to "MX"'
And it would grab the old country and new country
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
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