SubSonic - Generate SQL uses nvarchar instead of varchar which causes Index Scan instead of Seek - subsonic

I am using the SubSonic SimpleRepository template for my application. I have created an ASP .NET WebForms project in VS2010 pointing to a SQL 2000 database.
I am having an issue where SubSonic is always using nvarchar in the parameterized queries instead of varchar. This causes SQL to do an Index Scan instead of an Index Seek. I have taken the SQL from the Profiler and altered it to make the parameters varchar like the table's fields and it executes very quickly (<1 second versus 8 seconds).
SubSonic Query from Profiler
exec sp_executesql N'SELECT [t0].[ADDRESS_L1], [t0].[ADDRESS_L2], [t0].[ADDRESS_L3], [t0].[CITY], [t0].[COUNTRY] FROM [aveadmin].[SAPADD] AS t0 WHERE (([t0].[SITE_ID] = #p0) AND ((([t0].[ADDRESS_TYPE] = #p1) AND 1 <> 0) OR (([t0].[ADDRESS_TYPE] = #p2) AND 0 <> 0)))', N'#p0 nvarchar(16),#p1 nvarchar(2),#p2 nvarchar(2)', #p0 = N'BCF8A0A27E543EE1', #p1 = N'00', #p2 = N'03'
Manually Modified Query
exec sp_executesql N'SELECT [t0].[ADDRESS_L1], [t0].[ADDRESS_L2], [t0].[ADDRESS_L3], [t0].[CITY], [t0].[COUNTRY] FROM [aveadmin].[SAPADD] AS t0 WHERE (([t0].[SITE_ID] = #p0) AND ((([t0].[ADDRESS_TYPE] = #p1) AND 1 <> 0) OR (([t0].[ADDRESS_TYPE] = #p2) AND 0 <> 0)))', N'#p0 varchar(16),#p1 varchar(2),#p2 varchar(2)', #p0 = N'BCF8A0A27E543EE1', #p1 = N'00', #p2 = N'03'
The SITE_ID and ADDRESS_TYPE are varchars. Is there a way to force the query to use varchar instead of nvarchar?

Is there a way to force the query to
use varchar instead of nvarchar?
You will have to modify the source code of SubSonic to change this behavior.

[Previous answer is correct -- further details follow.]
The use of nvarchar is hard-coded in a function called GetNativeType() in Sql2005Schema.cs. it's easy enough to patch and rebuild. Hey, this is OSS! This is what the source code is for!
Here is the code.
case DbType.AnsiString:
case DbType.AnsiStringFixedLength:
case DbType.String:
case DbType.StringFixedLength:
return "nvarchar";
In theory, the generated code (see SQLServer.ttinclude) actually does generate DbType.AnsiString and DbType.String as separate types. It should be possible to split the switch statement and generate "varchar" for one and "nvarchar" for the other. Maybe the author thought it wouldn't matter. I suggest you give it a try (but it may break the unit tests).

Related

I want to use SQL_VARIANT datatype in external table Azure SQL and I get the "Index was out of range error."

I have two SQL Azure databases - DatabaseA and DatabaseB on a server hosted in Azure.
I need to access a view on DatabaseA from DatabaseB - namely I need the sys.identity_columns in DatabaseA to be available to me on DatabaseB. So I am creating an external table on DatabaseB that links to this information like this (I didn't include all the columns but I included the one causing the problem)
CREATE EXTERNAL TABLE [SOURCE_SYS].[identity_columns](
[object_id] int not null
,[name] nvarchar(128) null
,[column_id] int not null
,[system_type_id] tinyint not null
,[seed_value] sql_variant null
)
WITH
(
DATA_SOURCE = MyElasticDBQueryDataSrc,
SCHEMA_NAME = 'sys',
OBJECT_NAME = 'identity_columns'
);
When I run this - it works. But when I try to use the result - select * from [SOURCE_SYS].[identity_columns] - I get this error:
Msg 46823, Level 16, State 1, Line 50
Error retrieving data from MyServer.database.windows.net.DatabaseA. The underlying error message received was: 'Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index'.
If I comment out the fields in this table that have the sql_variant datatypes - it works fine but I do need the information in that field and the other two sql_variant fields that exist in the same table. MyElasticDBQueryDataSrc works fine on other similar tables without the sql_variant type.
Can anyone suggest what I might be doing wrong? Or suggest a workaround? I tried using bigints as it is mostly seed values that are either integers or null but that didn't work because it told me it wasn't the same datatype.
Any help much appreciated.
Well - after a weekend of sleep I figured out the answer!
If you use nvarchar(30) in he external table definition - you can then convert it to a bigint in any query you use it in
CREATE EXTERNAL TABLE [SOURCE_SYS].[identity_columns](
[object_id] int not null
,[name] nvarchar(128) null
,[column_id] int not null
,[system_type_id] tinyint not null
,[seed_value] nvarchar(30) null
)
WITH
(
DATA_SOURCE = MyElasticDBQueryDataSrc,
SCHEMA_NAME = 'sys',
OBJECT_NAME = 'identity_columns'
);
Now I can access the value like this:
select cast(isnull([seed_value], 0) as bigint) from SOURCE_SYS.identity_columns
Beware that if you do a select * from - you will need to do the variants separately from the rest of the query - you'll get this error:
Msg 46825, Level 16, State 1, Line 58
The data type of the column 'seed_value' in the external table is different than the column's data type in the underlying standalone or sharded table present on the external source.
Hope this is helpful to someone!

SQL Azure Schema issue error code 208 with temporary table

I have a couple of stored procedures that create different temporary tables.
At the end of the procedure i drop them (know that is not required, but it's good practice).
The stored procedures are executed as a part of a SSIS package. I got 4 different SQL jobs that execute the same SSIS package running in parallel.
When logging into the Azure portal and using the performance recommendation feature, I get a recommendation to fix the schema issues. It states an Sql error code 208. According to documentation that means "object not found".
Temporary tables are valid within the scope of the stored procedure and should get a unique name in the database, so I do not think where are any conflicts.
I have no idea what causes this, and the stored procedures seems to work alright. Anyone know what could be the cause here?
Simplified sample of one of the procedures:
SET NOCOUNT ON;
CREATE TABLE #tmpTransEan
(
Ean_Art_Str_id BIGINT ,
Artikler_id BIGINT
);
INSERT INTO #tmpTransEan
( Ean_Art_Str_id ,
Artikler_id
)
SELECT DISTINCT
eas.Ean_Art_Str_id ,
a.Artikler_id
FROM dbo.Artikkel_Priser ap
JOIN Ean_Art_Str eas ON eas.artikler_id = ap.Artikler_id
JOIN wsKasse_Til_Kasselogg ktk ON eas.Ean_Art_Str_id = ktk.ID_Primary
JOIN dbo.Artikler a ON a.Artikler_id = eas.artikler_id
JOIN dbo.Felles_Butikker b ON b.Butikker_id = ap.butikker_id
WHERE ktk.ID_Table = OBJECT_ID('Ean_Art_Str')
AND LEN(a.Artikkelnr) >= 8
AND ktk.Tidspunkt >= #tidspunkt
AND ( ( ap.butikker_id = #nButikker_id1
AND #Alle_artikler_til_kasse = 'N'
)
OR ( b.Databaser_id = #Databaser_id
AND #Alle_artikler_til_kasse = 'J'
)
)
AND b.Akt_kode = 'A'
AND a.Akt_kode = 'A'
AND a.Databaser_id IN ( -1, #Databaser_id )
SELECT DISTINCT
a.Artikkelnr ,
s.Storrelse ,
eas.* ,
EAN_12 = LEFT(eas.EAN_13, 12)
FROM dbo.Ean_Art_Str eas
JOIN #tmpTransEan t ON t.Artikler_id = eas.artikler_id
JOIN Artikler a ON a.Artikler_id = eas.artikler_id
JOIN dbo.Felles_Storrelser s ON s.Storrelser_id = eas.storrelser_id
DROP TABLE #tmpTransEan;
END;

CQL no viable alternative at input '(' error

I have a issue with my CQL and cassandra is giving me no viable alternative at input '(' (...WHERE id = ? if [(]...) error message. I think there is a problem with my statement.
UPDATE <TABLE> USING TTL 300
SET <attribute1> = 13381990-735b-11e5-9bed-2ae6d3dfc201
WHERE <attribute2> = dfa2efb0-7247-11e5-a9e5-0242ac110003
IF (<attribute1> = null OR <attribute1> = 13381990-735b-11e5-9bed-2ae6d3dfc201) AND <attribute3> = 0;
Any idea were the problem is in the statement about?
It would help to have your complete table structure, so to test your statement I made a couple of educated guesses.
With this table:
CREATE TABLE lwtTest (attribute1 timeuuid, attribute2 timeuuid PRIMARY KEY, attribute3 int);
This statement works, as long as I don't add the lightweight transaction on the end:
UPDATE lwttest USING TTL 300 SET attribute1=13381990-735b-11e5-9bed-2ae6d3dfc201
WHERE attribute2=dfa2efb0-7247-11e5-a9e5-0242ac110003;
Your lightweight transaction...
IF (attribute1=null OR attribute1=13381990-735b-11e5-9bed-2ae6d3dfc201) AND attribute3 = 0;
...has a few issues.
"null" in Cassandra is not similar (at all) to its RDBMS counterpart. Not every row needs to have a value for every column. Those CQL rows without values for certain column values in a table will show "null." But you cannot query by "null" since it isn't really there.
The OR keyword does not exist in CQL.
You cannot use extra parenthesis to separate conditions in your WHERE clause or your lightweight transaction.
Bearing those points in mind, the following UPDATE and lightweight transaction runs without error:
UPDATE lwttest USING TTL 300 SET attribute1=13381990-735b-11e5-9bed-2ae6d3dfc201
WHERE attribute2=dfa2efb0-7247-11e5-a9e5-0242ac110003
IF attribute1=13381990-735b-11e5-9bed-2ae6d3dfc201 AND attribute3=0;
[applied]
-----------
False

Effecientcy of using multiple subselects

I'm trying to gather multiple related pieces of data for a master account and create a view (e.g. overdue balance, account balance, debt recovery status, interest hold). Will this approach be effecient? Database platforms are Informix, Oracle and Sql Server. Doing some statistics on Informix I'm just getting 1 sequential scan of auubmast. I assume the sub-selects are quite effecient because they filter down to the account number immediately. I may need many sub-selects before I'm finished. On top of the question of efficiency are there any other 'tidy' approaches?
Thank you.
select
auubmast.acc_num,
auubmast.cls_cde,
auubmast.acc_typ,
(select
sum(auubtrnh.trn_bal)
from auubtrnh, aualtrcd
where aualtrcd.trn_cde = auubtrnh.trn_cde
and auubtrnh.acc_num = auubmast.acc_num
and (auubtrnh.due_dte < current or aualtrcd.trn_typ = 'I')
) as ovd_bal,
(select
sum(auubytdb.ytd_bal)
from auubytdb, auubsvgr
where auubytdb.acc_num = auubmast.acc_num
and auubsvgr.svc_grp = auubmast.svc_grp
and auubytdb.bil_yer = auubsvgr.bil_yer
) as acc_bal,
(select
max(cur_stu)
from audemast
where mdu_acc = auubmast.acc_num
and mdu_ref = 'UB'
) as drc_stu,
(select
hol_typ
from aualhold
where mdu_acc = auubmast.acc_num
and mdu_ref = 'UB'
and pro_num = 2601
and (hol_til is null or hol_til > current)
) as int_hld
from auubmast
In general, the answer to this is that correlated subqueries should be avoided whenever possible.
Using them will result in a full table scan for your view, which is bad. The only times you want to use subqueries like this is if you can limit the range of the main select to only a few rows, or if there really is no other choice.
When you're running into situations like this, you might want to consider adding columns and precalculating them on an update trigger, rather than using subqueries. This will save your database a thrashing.

Subsonic 3 Simple Query inner join sql syntax

I want to perform a simple join on two tables (BusinessUnit and UserBusinessUnit), so I can get a list of all BusinessUnits allocated to a given user.
The first attempt works, but there's no override of Select which allows me to restrict the columns returned (I get all columns from both tables):
var db = new KensDB();
SqlQuery query = db.Select
.From<BusinessUnit>()
.InnerJoin<UserBusinessUnit>( BusinessUnitTable.IdColumn, UserBusinessUnitTable.BusinessUnitIdColumn )
.Where( BusinessUnitTable.RecordStatusColumn ).IsEqualTo( 1 )
.And( UserBusinessUnitTable.UserIdColumn ).IsEqualTo( userId );
The second attept allows the column name restriction, but the generated sql contains pluralised table names (?)
SqlQuery query = new Select( new string[] { BusinessUnitTable.IdColumn, BusinessUnitTable.NameColumn } )
.From<BusinessUnit>()
.InnerJoin<UserBusinessUnit>( BusinessUnitTable.IdColumn, UserBusinessUnitTable.BusinessUnitIdColumn )
.Where( BusinessUnitTable.RecordStatusColumn ).IsEqualTo( 1 )
.And( UserBusinessUnitTable.UserIdColumn ).IsEqualTo( userId );
Produces...
SELECT [BusinessUnits].[Id], [BusinessUnits].[Name]
FROM [BusinessUnits]
INNER JOIN [UserBusinessUnits]
ON [BusinessUnits].[Id] = [UserBusinessUnits].[BusinessUnitId]
WHERE [BusinessUnits].[RecordStatus] = #0
AND [UserBusinessUnits].[UserId] = #1
So, two questions:
- How do I restrict the columns returned in method 1?
- Why does method 2 pluralise the column names in the generated SQL (and can I get round this?)
I'm using 3.0.0.3...
So far my experience with 3.0.0.3 suggests that this is not possible yet with the query tool, although it is with version 2.
I think the preferred method (so far) with version 3 is to use a linq query with something like:
var busUnits = from b in BusinessUnit.All()
join u in UserBusinessUnit.All() on b.Id equals u.BusinessUnitId
select b;
I ran into the pluralized table names myself, but it was because I'd only re-run one template after making schema changes.
Once I re-ran all the templates, the plural table names went away.
Try re-running all 4 templates and see if that solves it for you.

Resources