Issues of getting indexes information - SQL Server - c++

Trying to get table indexes information in SQL Server 2012 I identified a strange situation for one scenarion.
I have a table that contains two indexes referenced to some fields: Field_1 and Field_3 mapped over int, null columns (the number means the existing field order into the table designed few years ago...).
I am trying to get information about these indexes like this:
nRetCode = ::SQLStatistics(hstmtAux, NULL, 0, NULL, 0, (TCHAR*)(LPCTSTR)strTempTable, SQL_NTS, SQL_INDEX_ALL, SQL_ENSURE);
if (nRetCode == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO)
{
nRetCode = ::SQLBindCol(hstmtAux, 4, SQL_C_SHORT, &swNonUnique, sizeof(SWORD), &cbNonUnique);
nRetCode = ::SQLBindCol(hstmtAux, 5, SQL_CHAR, szIdxQualif, sizeof(CHAR) * 130, &cbIdxQualif);
nRetCode = ::SQLBindCol(hstmtAux, 6, SQL_C_CHAR, szIdxName, sizeof(CHAR) * 130, &cbIdxName);
nRetCode = ::SQLBindCol(hstmtAux, 7, SQL_C_SHORT, &swType, sizeof(SWORD), &cbType);
nRetCode = ::SQLBindCol(hstmtAux, 8, SQL_C_SHORT, &swSeqInIdx, sizeof(SWORD), &cbSeqInIdx);
nRetCode = ::SQLBindCol(hstmtAux, 9, SQL_C_CHAR, szIdxColName, sizeof(CHAR) * 130, &cbIdxColName);
while (bNoFetch || (nRetCode = ::SQLExtendedFetch(hstmtAux, SQL_FETCH_NEXT, 1, &crow, &rgfRowStatus)) == SQL_SUCCESS || nRetCode == SQL_SUCCESS_WITH_INFO)
{
if (cbIdxName != SQL_NULL_DATA && _tcslen((TCHAR)szIdxName) > 0)
{
// rest of the code
}
// the rest of the code
Because SQLExtendedFetch() is deprecated I used SQLFetchScroll() but the behavior is the same from my interest point of view.
Usually, I get the right information about indexes but in one situation I encounte a strange behavior. It's about having a clustered index into a scenario.
When Field_1 is Non-Unique, Non-Clustered and Field_3 is Clusted index I get the right information.
But if the index Field_1 is Clustered and the Field_3 is Non-Unique, Non-Clustered I get no information about Field_1 index (eg. szIdxName and szIdxColName are "" and their length is -1 that means SQL_NULL_DATA).
So, I have no Index information. Within while loop, with the next iteration I get correct information about the second index Field_3.
I'm not sure whether the problem is with SQLStatistics, the bindings or SQLFetchScroll (they all always return SQL_SUCCESS). It looks like a problem with the driver when the first index is clustered.
Any ideas for fixing this problem or alternative ways for retrieving indexes information?

There are cases documented for SQLStatistics to return NULL for the 'index' name or 'column name':
COLUMN_NAME: Column name. If the column is based on an expression, such as SALARY + BENEFITS, the expression is returned; if the expression cannot be determined, an empty string is returned. NULL is returned if TYPE is SQL_TABLE_STAT.
INDEX_NAME: Index name; NULL is returned if TYPE is SQL_TABLE_STAT.
Are you sure you do not capture an auto-created stats for the table in your results set? What value is swType? You can view the object stats in sys.stats.

I solved it with the workaround: using a query through sys.tables and sys.indexes tables.

Related

WHERE column = value, only work with INTEGER value

I use sqlite on a c++ project, but I have a problem when i use WHERE on a column with TEXT values
I created a sqlite database:
CREATE TABLE User( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(24))
When i try to get the value of the column with VARCHAR values, it doesn't work, and return me a STATUS_CODE 101 just after the sqlite3_step :
int res = 0;
sqlite3_stmt *request;
char *sqlSelection = (char *)"SELECT * FROM User WHERE name='bob' ";
int id = 0;
res = sqlite3_prepare_v2(db, sqlSelection, strlen(sqlSelection), &request, NULL);
if (!res){
while (res == SQLITE_OK || res == SQLITE_ROW){
res = sqlite3_step(request);
if (res == SQLITE_OK || res == SQLITE_ROW ){
id = sqlite3_column_int(request, 0);
printf("User exist %i \n",id);
}
}
sqlite3_finalize(request);
I also tried with LIKE but it also doesn't work
SELECT * FROM User WHERE name LIKE '%bob%'
But when I execute the same code but for an INTERGER value
SELECT * FROM User WHERE id=1
It work fine.
In DB Browser for SQLite all requests work fine.
To solve the problem I searched what status code 101 means.
Here is what they said.
(101) SQLITE_DONE
The SQLITE_DONE result code indicates that an operation has completed.
The SQLITE_DONE result code is most commonly seen as a return value
from sqlite3_step() indicating that the SQL statement has run to
completion. But SQLITE_DONE can also be returned by other multi-step
interfaces such as sqlite3_backup_step().
https://sqlite.org/rescode.html
So, you're getting 101 because there is no more result from SELECT SQL.
The solution was to replace the VARCHAR fields by TEXT.
SQLite for c++ seems to don't manage VARCHAR fields when they are used after the WHERE

C++ SQLite3 prepared delete statement not working

I have a C++ application which loops through a SQLite3 database. Each row contains an ID which is checked against a vector. If the ID in the DB is not present in the vector, it should be deleted with a prepared statement. I use the following code, however the ID's won't get deleted. I Neither can get an error message from the sqlite3_step(stmt2) function.
//SETTINGS["Reference"] CONTAINS THE REFERENCE FOR THE ID's (IT's 1 FOR UNDERNEATH EXAMPLE)
vector<int> IDs; //THIS VECTOR CONTAINS THE ID's IN MY APPLICATION
rc = sqlite3_prepare_v2(db, "SELECT ID FROM Files WHERE Reference=?", -1, &stmt, 0);
sqlite3_bind_text(stmt, 1, Settings["Reference"].c_str(), Settings["Reference"].length(), 0);
CheckDBError(rc);
rc = sqlite3_step(stmt);
sqlite3_stmt* stmt2;
int rc2 = sqlite3_prepare_v2(db, "DELETE FROM Files WHERE ID=? AND Reference=?", -1, &stmt2, 0);
CheckDBError(rc2);
while(rc == SQLITE_ROW) {
string IDToCheck = NumberToString(sqlite3_column_int64(stmt, 0));
if (std::find(IDs.begin(), IDs.end(), IDToCheck) == IDs.end()) { //VERIFY AGAINST VECTOR WORKS AS EXPECTED
//I GET HERE WITH ALL MY ID's I HAVE CHECKED THAT ALREADY :)
sqlite3_bind_text(stmt2, 1, IDToCheck.c_str(), IDToCheck.length(), 0);
sqlite3_bind_text(stmt2, 2, Settings["Reference"].c_str(), Settings["Reference"].length(), 0);
rc2 = sqlite3_step(stmt2);
//CAN'T GET ANY ERROR MESSAGE (SO QUERY IS FINE, WHICH SEEMS LIKE IT?)
}
rc = sqlite3_step(stmt);
}
sqlite3_finalize(stmt);
sqlite3_finalize(stmt2);
You must not call the finalize function before the while block, because that way you finalize your statement before using it. As per SQLite documentation (emphasis mine):
It is a grievous error for the application to try to use a prepared
statement after it has been finalized. Any use of a prepared statement
after it has been finalized can result in undefined and undesirable
behavior such as segfaults and heap corruption.

Junk varchar entries in MSSQL database using ODBC

I'm trying to insert a string-variable into a varchar(100)-field, but if the string is longer than 15 elements only junk is inserted (e.g. "0‰?").
First my setup:
Development: Win7 (64bit) / VS2013 / C++11 / 64bit Application
Database: Win8 (64bit) / Microsoft SQL Server Express 2014 (64bit)
Driver: SQL Server Native Client 11.0
Second the binding of the paramter:
std::string mMessageText;
SQLHANDLE mSqlStatementHandle;
std::string mExecString;
bool initConnection()
{
mExecString = "{? = CALL dbo.InsertTestProcedure(?, ?, ?, ?, ?)}";
(...)
// bind parameters
SQLBindParameter(mSqlStatementHandle, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, 100, 0, (SQLPOINTER)mMessageText.c_str(), mMessageText.length(), NULL);
(...)
// prepare handle with execution string
if (SQL_SUCCESS != SQLPrepare(mSqlStatementHandle, (SQLCHAR*)mExecString.c_str(), (SQLSMALLINT)mExecString.length()))
{
throwError(SQL_HANDLE_STMT, mSqlStatementHandle);
return false;
}
}
Third the query execution:
bool fillDb()
{
(...)
mMessageText = "This text is longer than 15";
// execute SQL statement
if (SQL_SUCCESS != SQLExecute(mSqlStatementHandle))
{
throwError(SQL_HANDLE_STMT, mSqlStatementHandle);
return false;
}
(...)
}
Header of the procedure:
ALTER PROCEDURE [dbo].[InsertTestProcedure]
#MessageComp VARCHAR(20),
#MessageType VARCHAR(20),
#MessageAction VARCHAR(20),
#MessageText VARCHAR(100),
#MessageName VARCHAR(20)
AS
If the string is shorter than 15 elements, it works fine. And calling the procedure from SQL Management Studio with value lengths > 15 works fine too.
One thing that comes to my mind is the procedure you are calling. Maybe you have table with varchar(100) column, but the procedure has only varchar(15) parameter. Could you post header of that procedure?
Thanks to #erg, here is the solution that worked for me:
char mMessageText[100];
bool initConnection()
{
(...)
// bind parameters
SQLBindParameter(mSqlStatementHandle, 5, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_LONGVARCHAR, 100, 0, (SQLPOINTER)mMessageText, 100, NULL);
(...)
}
bool fillDb()
{
(...)
std::string lMessageText = "This text is longer than 15";
strcpy(mMessageText, lMessageText.c_str());
mMessageText[sizeof(mMessageText) - 1] = 0;
(...)
}

mysql returns null when asking for difference between some value and null

in my table i have 3 column
id, somevalue (float), current timestamp
code below searches for the latest value in today's date and subtracts that with the value on monday of the same week. but i dont have any value stored for monday in this week so its NULL at the moment. but result of below code should be some value not null ???? dont understand how is it possible. pls explain.
select(SELECT power FROM newdb.newmeter
where date(dt)=curdate() order by dt desc limit 1)-
(select Power from newdb.newmeter
where date(dt)=(select date(subdate(now(), interval weekday(now()) day))));
as i was reading the similar questions-answers it looks like anything you do with null in mysql is null is it true??
if yes how do i resolve this
update:
i tried this but didnt work
select sum(amount) - coalesce(sum(due),0)
just wanted to add something more to this
i'm calling querydb as following for the mysql in c++
bool Querydb(char *query, double Myarray[1024])
{
//snip//
if (mysql_query(conn, query)) {
fprintf(stderr, "%s\n", mysql_error(conn));
return 0;
}
else {
res = mysql_use_result(conn);
//output table name
//printf("MySQL Tables in mysql database:\n");
//checking for null value in database
while((row = mysql_fetch_row(res))==NULL){
printf("ERROR_____NULL VALUE IN DATABASE ");
return 0;
}
//if not null then ...
while ((row = mysql_fetch_row(res)) != NULL){
printf("rows fetched %s\n", row[0]);
sprintf(buffer,"%s",row[0]);
value1 = atof(buffer);
Myarray[i]=value1;
//printf("Myarray in sql for daybutton = %f\n",Myarray[i]);
i++;
}
i=0;
//for(i=0;i<5;i++){
// printf("mya arr in sqlfunction = %f\n",Myarray[i]);}
return 1;
}
printf("if here then....where??\n");
//close connection
// mysql_free_result(res);
//return 0;
}
the above function works ok with different query when database has null but doesnt work with this query
select(SELECT power FROM newdb.newmeter
where date(dt)=curdate() order by dt desc limit 1)-
(select Power from newdb.newmeter
where date(dt)=(select date(subdate(now(), interval weekday(now()) day))));
it returns 1 even thought the answer is NULL...
Consult the manual on working with NULL values. They are treated specially http://dev.mysql.com/doc/refman/5.0/en/working-with-null.html
What value would you want it to be? This isn't particularly specific to MySQL - operations involving null operands (including comparisons) have null results in most SQL dialects.
You may want to use COALESCE() to provide a "default" value which is used when your real target value is null.
work Around would be
select(SELECT power FROM newdb.newmeter
where date(dt)=curdate() order by dt desc limit 1)-
(select COALESCE(Power,0) from newdb.newmeter
where date(dt)=(select date(subdate(now(), interval weekday(now()) day))));

Changing SQL Provider from SQLOLEDB.1 to SQLNCLI.1 causes app to fail when accessing data via stored procedure

I'm supporting a legacy app written in MFC/C++. The database for the app is in SQL Server 2000. We bolted on some new functionality recently and found that when we change the SQL Provider from SQLOLEDB.1 to SQLNCLI.1 some code that is trying to retrieve data from a table via a stored procedure fails.
The table in question is pretty straightforward and was created via the following script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[UAllergenText](
[TableKey] [int] IDENTITY(1,1) NOT NULL,
[GroupKey] [int] NOT NULL,
[Description] [nvarchar](150) NOT NULL,
[LanguageEnum] [int] NOT NULL,
CONSTRAINT [PK_UAllergenText] PRIMARY KEY CLUSTERED
(
[TableKey] ASC) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF,
IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[UAllergenText] WITH CHECK ADD CONSTRAINT
FK_UAllergenText_UBaseFoodGroupInfo] FOREIGN KEY([GroupKey])
REFERENCES [dbo].[UBaseFoodGroupInfo] ([GroupKey])
GO
ALTER TABLE [dbo].[UAllergenText] CHECK CONSTRAINT
FK_UAllergenText_UBaseFoodGroupInfo]
Bascially four columns, with TableKey being an identity column and everything else is populated via the following script:
INSERT INTO UAllergenText (GroupKey, Description, LanguageEnum)
VALUES (401, 'Egg', 1)
with a long list of other INSERT INTO's that follow the one above. Some of the rows inserted have special characters (like accent marks above letters) in their descriptions. I had originally thought that the inclusion of the special characters was part of the problem but if I completely clear out the table and then repopulate it with just the single INSERT INTO from above that has no special characters, it still fails.
So I moved on...
The data in this table is then accessed via the following code:
std::wstring wSPName = SP_GET_ALLERGEN_DESC;
_variant_t vtEmpty1 (DISP_E_PARAMNOTFOUND, VT_ERROR);
_variant_t vtEmpty2(DISP_E_PARAMNOTFOUND, VT_ERROR);
_CommandPtr pCmd = daxLayer::CDataAccess::GetSPCommand(pConn, wSPName);
pCmd->Parameters->Append(pCmd->CreateParameter("#intGroupKey", adInteger, adParamInput, 0, _variant_t((long)nGroupKey)));
pCmd->Parameters->Append(pCmd->CreateParameter("#intLangaugeEnum", adInteger, adParamInput, 0, _variant_t((int)language)));
_RecordsetPtr pRS = pCmd->Execute(&vtEmpty1, &vtEmpty2, adCmdStoredProc);
//std::wstring wSQL = L"select Description from UAllergenText WHERE GroupKey = 401 AND LanguageEnum = 1";
//_RecordsetPtr pRS = daxLayer::CRecordsetAccess::GetRecordsetPtr(pConn,wSQL);
if (pRS->GetRecordCount() > 0)
{
std::wstring wDescField = L"Description";
daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
}
else
{
nameString = "";
}
The daxLayer is a third party data access library the application is using, though we have the source to it (some of which will be seen below.) SP__GET_ALLERGEN_DESC is the stored proc used to get the data out of the table and it was created via this script:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[spRET_AllergenDescription]
-- Add the parameters for the stored procedure here
#intGroupKey int,
#intLanguageEnum int
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
SELECT Description FROM UAllergenText WHERE GroupKey = #intGroupKey AND LanguageEnum = #intLanguageEnum
END
When the SQL Provider is set to SQLNCLI.1, the app blows up at:
daxLayer::CRecordsetAccess::GetField(pRS, wDescField, nameString);
from the above code snippet. So I stepped into GetField, which looks like the following:
void daxLayer::CRecordsetAccess::GetField(_RecordsetPtr pRS,
const std::wstring wstrFieldName, std::string& sValue, std::string sNullValue)
{
if (pRS == NULL)
{
assert(false);
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Missing recordset pointer."))
}
else
{
try
{
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
if ((tv.vt == VT_EMPTY) || (tv.vt == VT_NULL))
{
sValue = sNullValue;
}
else if (tv.vt != VT_BSTR)
{
// The type in the database is wrong.
assert(false);
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Field type is not string"))
}
else
{
_bstr_t bStr = tv ;//static_cast<_bstr_t>(pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value);
sValue = bStr;
}
}
catch( _com_error &e )
{
RETHROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string"), e.Description())
}
catch(...)
{
THROW_API_EXCEPTION(GetExceptionMessageFieldAccess(L"GetField",
wstrFieldName, L"std::string", L"Unknown error"))
}
}
}
The culprit here is:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
Stepping into Fields->GetItem brings us to:
GetItem
inline FieldPtr Fields15::GetItem ( const _variant_t & Index ) {
struct Field * _result = 0;
HRESULT _hr = get_Item(Index, &_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return FieldPtr(_result, false);
}
Which then takes us to:
GetValue
inline _variant_t Field20::GetValue ( ) {
VARIANT _result;
VariantInit(&_result);
HRESULT _hr = get_Value(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _variant_t(_result, false);
}
If you look at _result while stepping through this at runtime, _result's BSTR value is correct, its value is "Egg" from the "Description" field of the table. Continuing to step through traces back through all the COM release calls, etc. When I finally get back to:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
And step past it to the next line, the contents of tv, which should be BSTR="Egg" are now:
tv BSTR = 0x077b0e1c "ᎀݸﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮﻮ㨼㺛帛᠄"
When the GetField function tries to set its return value to the value in tv.BSTR
_bstr_t bStr = tv;
sValue = bStr;
it unsurprisingly chokes and dies.
So what happened to the value of BSTR and why does it only happen when the provider is set to SQLNCLI.1?
For the heck of it, I commented out using the stored procedure in the topmost code and just hard coded the same SQL SELECT statement that the stored procedure uses and found that it works just fine and the value returned is correct.
Also, it's possible for users to add rows to the table through the application. If the application creates a new row in that table and retrieves that row via stored procedure, it also works correctly unless you include a special character in the description in which case it correctly saves the row but blows up again in the exact same way as above upon retrieval of that row.
So to summarize, if I can, rows put into the table via the INSERT script ALWAYS blow up the app when they are accessed by stored procedure (regardless of whether they contain any special characters). Rows put into the table from within the application by the user at runtime are retrieved correctly via stored procedure UNLESS they contain a special character in the Description, at which point they blow up the app. If you access any of the rows in the table by using SQL from the code at runtime instead of the stored procedure it works whether there is a special character in the Description or not.
Any light that can be shed on this will be greatly appreciated, and I thank you in advance.
This line might be problematic:
tagVARIANT tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
If I read it right, ->Value returns a _variant_t, which is a smart pointer. The smart pointer will release its variant when it goes out of scope, right after this line. However, tagVARIANT is not a smart pointer, so it won't increase the reference count when it is assigned to. So after this line, tv might point to a variant which has effectively been released.
What happens if you write the code like this?
_variant_t tv = pRS->Fields->GetItem(_variant_t(wstrFieldName.c_str()))->Value;
Or alternatively, tell the smart pointer not to release its payload:
_tagVARIANT tv = pRS->Fields->GetItem(
_variant_t(wstrFieldName.c_str()))->Value.Detach();
It's been a long time since I coded in C++, and reading this post, I don't regret moving away!