How can we remove SQL error code 1405 in C - sql

I have written following piece of code in C:
EXEC SQL begin declare section;
int A;
char B[5];
int C;
int D;
dtime_t E;
char F[13];
EXEC SQL END DECLARE SECTION;
char E_dt[16];
D=0;
memset(F, 0, sizeof(F));
EXEC SQL declare log3 cursor for select A, B, C, D, E, F from tbl WHERE C=123;
if(sqlca.sqlcode)
{
return;
}
EXEC SQL fetch log3
into
:A, :B, :C, :D, :E, :F,
if(sqlca.sqlcode)
{
if (sqlca.sqlcode != DB_NORECORDS)
{
return;
}
break;
}
When we run this piece of code, where the value of F is Null in the table tbl. It gives error code 1405. I have tried memset function for setting its default value as 0. But it did not work. And one more thing can i fetch more than 90 field at a time, when i was trying to do that it gives me Bus Error at the time of compilation. Thanks in advance please help me out.

You have 2 basic options:
Use an indicator to record the presence of the NULL. You probably should use this option when you want a specific action to be taken when a NULL arises.
Use the NVL function to convert the retrieved NULL to another value, such as zero or blank. This is the simplest solution.
Edit:
Example with NVL for log3 cursor. Choose whatever default values you want. In the example I have used ' ' for character fields, 0 for numeric fields and Jan 1st year 1 for dates.
EXEC SQL declare log3 cursor for select NVL(A, 0),
NVL(B, ' '),
NVL(C, 0),
NVL(D, 0),
NVL(E, to_date('0001', 'YYYY')),
NVL(F, ' ')
from tbl
WHERE C=123;

Related

SQL Where with Binary(n) column

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 :(

String operation in SQL to reverse a string

In DB2 9.7 I am looking for a way to reverse a string in a SQL query.
I am familiar with SQL Server where the query would be like
SELECT
REVERSE(LEFT_TO_REIGHT) AS RIGHT_TO_LEFT
FROM
TABLE1;
I couldn't find a similar function in DB2. is there a simple way to reverse a string?
Creating a REVERSE(..) function is unnecessary.
DB2 has something called RIGHT(string-expression, length):
The RIGHT function returns the rightmost string of string-expression
of length length, expressed in the specified string unit. If
string-expression is a character string, the result is a character
string. If string-expression is a graphic string, the result is a
graphic string
So if you're interested in the last 8 characters, you can pretty trivially do this via:
SELECT RIGHT(left_to_right, 8) AS right_to_left
FROM Table1
(I'm actually still concerned about the fact that you're splitting off 8 characters consistently, as it implies you have a multi-part key of some sort).
Try something like:
SELECT STRIP(CAST( TRANSLATE('87654321',LEFT_TO_REIGHT, '12345678') AS VARCHAR(8) ))
FROM TABLE1;
Due to the original question this is the first webpage that comes up when one searches for 'How to reverse a string in DB2'.
Here is an answer that doesn't require implementing it in C and shouldn't brake on non-pure-Engilsh strings regardless of their length.
Be warned though, the efficiency is 'meh' at best.
CREATE FUNCTION REVERSE_STRING(STR VARCHAR(100))
RETURNS VARCHAR(100)
LANGUAGE SQL
SPECIFIC REVERSE_STRING
DETERMINISTIC
REVERSE: BEGIN
DECLARE REVERSED_STRING VARCHAR(100);
DECLARE REVERSED_CHARACTERS_INDEX INTEGER;
SET REVERSED_STRING='';
SET REVERSED_CHARACTERS_INDEX=0;
WHILE (REVERSED_CHARACTERS_INDEX < CHARACTER_LENGTH(STR, CODEUNITS16))
DO
SET REVERSED_CHARACTERS_INDEX = REVERSED_CHARACTERS_INDEX + 1;
SET REVERSED_STRING = CONCAT(
REVERSED_STRING,
LEFT(RIGHT(STR, REVERSED_CHARACTERS_INDEX, CODEUNITS16), 1, CODEUNITS16));
END WHILE;
RETURN REVERSED_STRING;
END REVERSE#
The idea is to get a substring which starts from the n-th character from the right till the end of the string, then take the first element of this substring from the left and append it to a reversed string. This operation is conducted n times where n is the length of a string to be reversed.
You can use it like any other function.
SELECT FIRSTNME AS FIRSTNAME, REVERSE_STRING(FIRSTNME) AS REVERSED_FIRSTNAME
FROM SAMPLE.EMPLOYEE#
Example output
Answering the original question of reversing a string there's user defined functions published on the IBM site that will do it that you can find here. There's apparently no built in ability in DB2
https://www.ibm.com/developerworks/community/blogs/SQLTips4DB2LUW/entry/reverse?lang=en
Tortured SQL version:
CREATE OR REPLACE FUNCTION REVERSE(INSTR VARCHAR(4000))
RETURNS VARCHAR(4000) SPECIFIC REVERSE
DETERMINISTIC NO EXTERNAL ACTION CONTAINS SQL
RETURN WITH rec(pos, res) AS (VALUES (1, CAST('' AS VARCHAR(4000)))
UNION ALL
SELECT pos + 1, SUBSTR(INSTR, pos , 1) || res
FROM rec
WHERE pos <= LENGTH(INSTR)
AND pos < 5000)
SELECT res FROM rec WHERE pos > LENGTH(INSTR);
But then you have to do this as well, yuck:
CREATE BUFFERPOOL bp32 PAGESIZE 32K;
CREATE SYSTEM TEMPORARY TABLESPACE tsp32 PAGESIZE 32K BUFFERPOOL bp32;
A saner C implementation
#include <sqludf.h>
#ifdef __cplusplus
extern "C"
#endif
void SQL_API_FN ReverseSBCP(SQLUDF_VARCHAR *inVarchar,
SQLUDF_VARCHAR *outVarchar,
SQLUDF_SMALLINT *inVarcharNullInd,
SQLUDF_SMALLINT *outVarcharNullInd,
SQLUDF_TRAIL_ARGS)
{
int inLen, inPos, outPos;
if (*inVarcharNullInd == -1)
{
*outVarcharNullInd = -1;
}
else
{
inLen = strlen(inVarchar);
for (inPos = 0, outPos = inLen -1; inPos < inLen; inPos++, outPos--)
{
outVarchar[outPos] = inVarchar[inPos];
}
outVarchar[inLen] = '\0';
*outVarcharNullInd = 0;
}
return;
}

SQL Function and Anonymous block

I'm taking a class about databases. I am very new to it, so excuse me if this is an obvious error, but I've been working on this problem for hours and not sure what else to do.
The code is suppose to create a function to that
A.) Outputs the average netwin for a year given by a parameter
(given by the formula (SeasonW-Seasonl) + (PlayoffW - PlayoffL)
B.) Outputs all of the coaches that have a netwin over the average
C.) Returns the number of coaches that fits this criteria.
D.)Have a anonymous block that calls this function, and outputs two different messages depending on the return on the function.
Now I have sucessfully done parts A, C, and D. But for some reason my function will not be created when I insert part B.
create or replace function GOOD_COACHES(season IN INT)
return INT
IS
netwin INT;
CNT INT;
BEGIN
--Calculated netwin
select AVG((SEASON_WIN-SEASON_LOSS) + (PLAYOFF_WIN-PLAYOFF_LOSS)) into netwin from COACHESSEASON where YEAR = season;
--Prints out A
dbms_output.put_line('Average Netwin is: ' || netwin);
--This Line messes up the function, I don't know why
select T.FIRSTNAME, T.LASTNAME from COACHESSEASON T where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>netwin and YEAR = season;
--Calculates the number of teams that satisfy average
select count(T.FIRSTNAME) into CNT from COACHESSEASON T where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>netwin and YEAR = season;
return CNT;
END;
--End of the Function
--Start of the Anonymous Block
DECLARE
x int := GOOD_COACHES(1998);
BEGIN
if x = 0 then dbms_output.put_line('We didn''t find any good_coaches!');
else dbms_output.put_line('The No. of good coaches is ' || x);
end if;
END;
/
That third line in the function messes it up and doesn't allow it to be called. If I comment it out, it works properly.
When I take it out of the function and make it into a regular SQL statement, it works.
select T.FIRSTNAME, T.LASTNAME from COACHESSEASON T where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>0 and YEAR = /*RandYear*/;
If anyone understands why the function can not be created with that line it in, I would appreciate the advice. I also do not know how I would print out the results of the selected row in the function.
You are not selecting the T.FIRSTNAMEs and T.LASTNAMEs into anything so the statement is not valid for inclusion in a PL/SQL block.
There are likely to be multiple "good coaches" so you can't put the values into a single variable and will have to use a cursor or a collection.
Using collections, if you create a table type to collect the first and last names into:
CREATE TYPE VARCHAR2s_Table AS TABLE OF VARCHAR2(30);
/
Then you can use this to collect the first and last names (and as a bonus the collection size will tell you how many good coaches there are and you can skip the last query):
create or replace function GOOD_COACHES(season IN INT)
return INT
IS
netwin INT;
CNT INT;
firstnames VARCHAR2s_Table;
lastnames VARCHAR2s_Table;
BEGIN
--Calculated netwin
select AVG((SEASON_WIN-SEASON_LOSS) + (PLAYOFF_WIN-PLAYOFF_LOSS))
into netwin
from COACHESSEASON
where YEAR = season;
--Prints out A
dbms_output.put_line('Average Netwin is: ' || netwin);
--This Line messes up the function, I don't know why
select T.FIRSTNAME, T.LASTNAME
BULK COLLECT INTO firstnames, lastnames
from COACHESSEASON T
where ((T.SEASON_WIN-T.SEASON_LOSS) + (T.PLAYOFF_WIN-T.PLAYOFF_LOSS))>netwin
and YEAR = season;
FOR i IN 1 .. firstnames.COUNT LOOP
DBMS_OUTPUT.put_line( firstnames(i) || ' ' || lastnames(i) );
END LOOP;
return firstnames.COUNT;
END;
/
SQLFIDDLE

Calling a stored procedure in SQL Server to get a unicode string

I'm trying to get a name by calling a stored procedure.
sql code:
create procedure GetName
#ID int,
#name nvarchar(32) output
as
select #name=name from SalesInfo where ID=#ID
c code
...
SQLRETURN rc;
SQLLEN cbParam = SQL_NTS;
int ID = 1;
wchar_t name[32];
rc = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 0, 0, &ID, 0, NULL);
rc = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT_OUTPUT, SQL_C_WCHAR, SQL_WCHAR, SQL_DESC_LENGTH, 0, name, sizeof(name), &cbParam);
rc = SQLExecDirect(hstmt, TEXT("{call GetName(?,?)}"), SQL_NTS);
I received a string, but there was an error message, 'string data, right truncation'
The string was padded with blanks, like "name "
you need to increase the size of your nvarchar() so that it doesn't truncate the strings that are larger than 32
increase the size of wchar_t name[N];and #name nvarchar(N) output
Well to get rid of the trim simply use RTRIM(col) AS [col], to determin what length of char vars you need then LEN(Originalcol) or DATALENGTH(Originalcol) http://msdn.microsoft.com/en-us/library/ms173486.aspx in your case to create a proc to see waht actual length you are getting and what you need to define. As its a single var, you wont get much a hit by using NVARCHAR(MAX)

How to organize infinite while loop in SQL Server?

I want to use infinite WHILE loop in SQL Server 2005 and use BREAK keyword to exit from it on certain condition.
while true does not work, so I have to use while 1=1.
Is there a better way to organize infinite loop ?
I know that I can use goto, but while 1=1 begin ... end looks better structurally.
In addition to the WHILE 1 = 1 as the other answers suggest, I often add a "timeout" to my SQL "infintie" loops, as in the following example:
DECLARE #startTime datetime2(0) = GETDATE();
-- This will loop until BREAK is called, or until a timeout of 45 seconds.
WHILE (GETDATE() < DATEADD(SECOND, 45, #startTime))
BEGIN
-- Logic goes here: The loop can be broken with the BREAK command.
-- Throttle the loop for 2 seconds.
WAITFOR DELAY '00:00:02';
END
I found the above technique useful within a stored procedure that gets called from a long polling AJAX backend. Having the loop on the database-side frees the application from having to constantly hit the database to check for fresh data.
Using While 1 = 1 with a Break statement is the way to do it. There is no constant in T-SQL for TRUE or FALSE.
If you really have to use an infinite loop than using while 1=1 is the way I'd do it.
The question here is, isn't there some other way to avoid an infinite loop? These things just tend to go wrong ;)
you could use the snippet below to kick a sp after soem condition are rised. I assume that you ahev some sort of CurrentJobStatus table where all the jobs/sp keeps their status...
-- *** reload data on N Support.usp_OverrideMode with checks on Status
/* run
Support.usp_OverrideMode.Number1.sql
and
Support.usp_OverrideMode.Number2.sql
*/
DECLARE #FileNameSet TABLE (FileName VARCHAR(255));
INSERT INTO #FileNameSet
VALUES ('%SomeID1%');
INSERT INTO #FileNameSet
VALUES ('%SomeID2%');
DECLARE #BatchRunID INT;
DECLARE #CounterSuccess INT = 0;
DECLARE #CounterError INT = 0;
-- Loop
WHILE WHILE (#CounterError = 0 AND #CounterSuccess < (select COUNT(1) c from #FileNameSet) )
BEGIN
DECLARE #CurrenstStatus VARCHAR(255)
SELECT #CurrenstStatus = CAST(GETDATE() AS VARCHAR)
-- Logic goes here: The loop can be broken with the BREAK command.
SELECT #CounterSuccess = COUNT(1)
FROM dbo.CurrentJobStatus t
INNER JOIN #FileNameSet fns
ON (t.FileName LIKE fns.FileName)
WHERE LoadStatus = 'Completed Successfully'
SELECT #CounterError = COUNT(1)
FROM dbo.CurrentJobStatus t
INNER JOIN #FileNameSet fns
ON (t.FileName LIKE fns.FileName)
WHERE LoadStatus = 'Completed with Error(s)'
-- Throttle the loop for 3 seconds.
WAITFOR DELAY '00:00:03';
select #CurrenstStatus = #CurrenstStatus +char(9)+ '#CounterSuccess ' + CAST(#CounterSuccess AS VARCHAR(11))
+ char(9)+ 'CounterError ' + CAST(#CounterError AS VARCHAR(11))
RAISERROR (
'Looping... # %s'
,0
,1
,#CurrenstStatus
)
WITH NOWAIT;
END
-- TODO add some codition on #CounterError value
/* run
Support.usp_OverrideMode.WhenAllSuceed.sql
*/
Note the code is flexibile you can add as many condition checks on the #FileNameSet table var
Mario