How can I reuse subquery in condition checking in SQL Server? - c#

I want to retrieve the results from SQL Server 2012.But for checking condition I rewrote the same subquery.Is there any idea to use the subquery just once and retrieve the result?
My query:
sql = "SELECT customer_id,ISNULL(first_name, '') + ' ' + ISNULL(middle_name, ' ') + ' ' + ISNULL(last_name, ' ') AS 'Customer_name', (ISNULL(city, '') + ',' + ISNULL(district, ' ') + ',' + ISNULL(zone, ' ')) as 'Location' FROM customer_detail WHERE 1=1";
if(location != "")
{
sql += " AND (ISNULL(city, '') + ',' + ISNULL(district, ' ') + ',' + ISNULL(zone, ' ')) LIKE '%" + location + "%'"";
}
Query after AND is same as above subquery. Thanks in advance.

It seems you are not looking so much for sub-query than a user defined function (UDF) to merge 3 text column in a prettier way.
If you do not want to use UDF then you could use Common Table Expressions (CTE) to write the expression just once.
Using CTE has also the benefit of encapsulating your reused block right inside query, whereas the UDF would need to be added to your DB before executing your query. This may or may not be a desirable thing depending on reusability needs.
CTE solution would be along the lines of this:
WITH CTE (Id, [Name], [Location]) as
(
SELECT customer_id,
ISNULL(first_name, '') + ' ' + ISNULL(middle_name, ' ') + ' ' + ISNULL(last_name, ' '),
ISNULL(city, '') + ',' + ISNULL(district, ' ') + ',' +ISNULL(zone, ' ')
FROM customer_detail
)
select *
from CTE
where 1=1
AND [Location] LIKE '%' + #location + '%'
Also, generally you can expect CTE to perform generally better than UDFs as the query optimizer can modify the plan to match specific query needs.

I would go with a reusable view and stored procedure for the actual search.
create view vCustomerDetail
as
select [customer_id]
, isnull([first_name] + ' ', '') + isnull([middle_name] + ' ', '') + isnull([last_name], '') as [Customer_name]
, isnull([city] + ', ', '') + isnull([district] + ', ', '') + isnull([zone], '') as [Location]
from [customer_detail]
go
create proc pCustomerDetailByLocation
(
#location nvarchar(200) = ''
)
as
begin
set nocount on;
select *
from [vCustomerDetail]
where [Location] = ''
union all
select *
from [vCustomerDetail]
where [Location] like '%' + #location + '%';
end
go
You would call the stored procedure from your code with and pass along the location in the parameter. That way you can prevent SQL injection and also make use of better execution plan caching than with an ad-hoc query.
You can either use a union like I've done in my example, or combine it with an OR statement, like so:
where [Location] = '' or [Location] like '%' + #location + '%';
I found the union to be beneficial in some cases over because you're helping the optimizer split up his lookup logic.

Related

SQL GroupBy for Stored Procedure caused Error

I have a quick question that why my SQL Stored Procedure did not work properly. Can someone explain what is wrong with my Stored Procedure Query?
Error: "Each GROUP BY expression must contain at least one column that is not an outer reference."
SELECT
#TabGroupBy + #TabGroupByName + ','
,SUM(Value) AS Sum
,[Unit]
,[Child_Name]
FROM (
SELECT [model_id]
,[Child_ID]
,[Property_ID]
,[DDate]
,[Hour]
,[Value]
FROM [RP_IRP].[M_PLEXOS].[dat_Generators]
where parent_ID=1 and child_ID in(9, 357,358) and Property_ID in (4,31)
) a
inner join [RP_IRP].[M_PLEXOS].[Child_Object] b on a.child_id=b.child_id
inner join [M_PLEXOS].[Property] d on d.[Property_ID] = a.[Property_ID]
inner join [M_PLEXOS].[Units] e on d.[Unit_ID]=e.[Unit_ID]
inner join [M_PLEXOS].[Model_Config] f on a.[Model_id]=f.[Model_id]
WHERE Child_Name = #SelectedChildValue AND Property = #SelectedPropertyValue
AND Unit = #SelectedUnitValue
GROUP BY Child_Name , #TabGroupBy , Unit HAVING SUM(Value) > #MinValue
It can never work the way you are trying it, because by definition, all column names in the SELECT-part, as well as all column names in the GROUP BY-clause can not come from #-variables. They must be written as plain text because they are identifiers.
The same applies to table names + column names in FROM and JOIN clauses.
The reason for this is that the query compiler is built to check all specified columns (and tables, and schemas, and more) against objects that exist in your database(s), and this needs to succeed before a single line of compiled code runs. You should always keep in mind that at compilation time, #-variables don't yet exist and can't have values (because they don't yet exist).
The solution is to use dynamic SQL. You can achieve what you want by building the actual SQL string that you want to execute in a #SQL variable of type NVARCHAR(max), and then EXEC the contents of that variable. EXEC will invoke the SQL query compiler on the contents of its parameter.
Example code, may not be 100% perfect because I can't run it due to not having your database available, but this should get you on your way:
DECLARE #SQL NVARCHAR(max) =
'SELECT ' +
QUOTENAME(#TabGroupBy) + ' AS ' + QUOTENAME(#TabGroupByName) + ', ' +
'SUM(Value) AS Sum, ' +
'[Unit], ' +
'[Child_Name] ' +
'FROM ( ' +
'SELECT [model_id],[Child_ID],[Property_ID],[DDate],[Hour],[Value] ' +
'FROM [RP_IRP].[M_PLEXOS].[dat_Generators] ' +
'where parent_ID = 1 ' +
' and child_ID in (9, 357, 358) ' +
' and Property_ID in (4, 31) ' +
') a ' +
'inner join [RP_IRP].[M_PLEXOS].[Child_Object] b on a.child_id=b.child_id ' +
'inner join [M_PLEXOS].[Property] d on d.[Property_ID] = a.[Property_ID] ' +
'inner join [M_PLEXOS].[Units] e on d.[Unit_ID]=e.[Unit_ID] ' +
'inner join [M_PLEXOS].[Model_Config] f on a.[Model_id]=f.[Model_id] ' +
'WHERE Child_Name = ''' + REPLACE(#SelectedChildValue , '''', '''''') + ''' ' +
' AND Property = ''' + REPLACE(#SelectedPropertyValue, '''', '''''') + ''' ' +
' AND Unit = ''' + REPLACE(#SelectedUnitValue , '''', '''''') + ''' ' +
'GROUP BY Child_Name , ' + QUOTENAME(#TabGroupBy) + ', Unit ' +
'HAVING SUM(Value) > ' + CAST(#MinValue AS VARCHAR(20)) -- assuming #MinValue is INT or FLOAT
EXEC (#SQL)
This code assumes there may be quotes inside the #-variables. Always use REPLACE to double embedded quotes if the #-variables represent string values, or even better: use dynamic SQL along with #-parameters, see this Q & A for how that can be done.
For cases where the #-variables represent database identifiers (column names etc.), you need to use QUOTENAME(...) as in the example code to make sure that no abuse can take place.
You cannot parameterize a GROUP BY column. Remove the parameter #TabGroupBy from the GROUP BY

Complex query is not working with Entity Framework ";WITH"

I am working with multi-level marketing application, with Entity Framework. I have create a stored procedure but somehow the query shown below is not working - why?
;WITH OrgTree ([GenealogyTreeID], [MemberRegisterId], [ParentMemberRegisterId], Level) AS
(
SELECT
[GenealogyTreeID], [MemberRegisterId],
[ParentMemberRegisterId], 0 AS Level
FROM
GenealogyTree
WHERE
ParentMemberRegisterId IS NULL --= 'c9f8479f-2bad-4a70-9c0c-e5c9c6bb1e26'
UNION ALL
SELECT
g.[GenealogyTreeID], g.[MemberRegisterId],
g.[ParentMemberRegisterId], OrgTree.Level + 1
FROM
GenealogyTree g
JOIN
OrgTree ON g.ParentMemberRegisterId = OrgTree.MemberRegisterId
)
SELECT DISTINCT
g.GenealogyTreeID, g.MemberRegisterId,
CASE
WHEN g.Level = 0 THEN NULL
ELSE g.ParentMemberRegisterId
END AS ParentMemberRegisterId,
g.Level,
ISNULL((mmr.FirstName + ' ' + mmr.MiddleName + ' ' + mmr.LastName),
(mr.FirstName + ' ' + mr.MiddleName + ' ' + mr.LastName)) as ParentFullName,
(mr.FirstName + ' ' + mr.MiddleName + ' ' + mr.LastName) as SelFullName,
mr.photo
FROM
OrgTree g
INNER JOIN
MemberRegister mr on mr.MemberRegisterId = g.MemberRegisterId
INNER JOIN
MemberRegister mmr on mmr.MemberRegisterId = g.ParentMemberRegisterId
WHERE
mr.FirstName LIKE '%'+ #SearchValue +'%'
OR mr.MiddleName LIKE '%'+ #SearchValue +'%'
OR mr.LastName LIKE '%'+ #SearchValue +'%'
OR mmr.FirstName LIKE '%'+ #SearchValue +'%'
OR mmr.MiddleName LIKE '%'+ #SearchValue +'%'
OR mmr.LastName LIKE '%'+ #SearchValue +'%'
ORDER BY
ParentMemberRegisterId
OFFSET #PageSize * (#PageNo - 1) ROWS
FETCH NEXT #PageSize ROWS ONLY
OPTION (RECOMPILE);
You should remove first ;
WITH OrgTree
...
;
Otherwise ORM would get first statement as empty one and ignore the second.
;WITH pattern is used because ; is not mandatory in general in T-SQL but a few statements require it like CTE/MERGE. This is why some developers start WITH with ;

SQL Server column as a parameter when the column is datetime type

I want to use .net/C# to develop a web application. I have a SQL Server stored procedure and I want to pass dynamic datetime parameter.
When I use exec(#sql) here is the stored procedure with parameter I get :
Error:System.Data.SqlClient.SqlException:
Conversion failed when converting datetime from character string.
I even tried passing some actual values (see below) and also tried debug in SQL Server, it doesn't work.
Can anyone help me out? Thanks a lot in advance.
ALTER PROCEDURE [dbo].[production_tb_WorkshopDailyReports_INSERT]
#intDepartmentID int,
#intReportUserID int,
#intProductionLineID int,
#intBatchID int,
#intVINID int,
#columnname nvarchar(255),
#dtOnlineTime datetime,
#strRemark nvarchar(50),
#intCreateUserID int
AS
BEGIN
declare #column nvarchar(255)
declare #sql nvarchar(1000)
declare #intID int
set #columnname = 'dtOnlineTimeChassis'
set #column = #columnname
set #intDepartmentID = 1
set #intReportUserID = 1
set #intProductionLineID = 1
set #intBatchID = 1
set #intVINID = 1
set #dtOnlineTime = '2016-04-26 10:00:00pm'
set #strRemark = 'remark information'
set #intCreateUserID = 1
set #sql = 'Insert Into production_tb_WorkshopDailyReports (intDepartmentID, intReportUserID, intProductionLineID, intBatchID, intVINID,' + #column + ', strRemark, intCreateUserID) values ' + '(' + cast(#intDepartmentID as nvarchar(20)) + ',' + cast(#intReportUserID as nvarchar(20)) + ',' + cast(#intProductionLineID as nvarchar(20)) + ',' + cast(#intBatchID as nvarchar(20)) + ',' + cast(#intVINID as nvarchar(20)) + ',' + #dtOnlineTime + ',' + #strRemark + ',' + cast(#intCreateUserID as nvarchar(20)) + ')'
(#intReportUserID as nvarchar(20))+','+cast(#intProductionLineID as nvarchar(20))+','+cast(#intBatchID as nvarchar(20))+','+cast(#intVINID as nvarchar(20))+','+''+','+cast(#intCreateUserID as nvarchar(20))+')'
exec(#sql)
End
I guess all this:
set #columnname = 'dtOnlineTimeChassis'
set #column = #columnname
set #intDepartmentID = 1
set #intReportUserID = 1
set #intProductionLineID = 1
set #intBatchID = 1
set #intVINID = 1
set #dtOnlineTime = '2016-04-26 10:00:00pm'
set #strRemark = 'remark information'
set #intCreateUserID = 1
Is just for testing the SP, other time there will be something different in #columnname variable. If this statement is false then I suggest using simple INSERT instead of dynamic SQL.
At first change this:
',' + #dtOnlineTime + ',' + #strRemark + ','
To this:
',''' + CONVERT(nvarchar(50),#dtOnlineTime,120)+''',''' + #strRemark + ''','
I suggest using 120 date style (ODBC canonical yyyy-mm-dd hh:mi:ss(24h), for more info read MSDN) and you must put datetime variable and variable with some remark into ''.
Second remove or comment this part (next string after set #sql = ...):
(#intReportUserID as nvarchar(20))+','+cast(#intProductionLineID as nvarchar(20))+','+cast(#intBatchID as nvarchar(20))+','+cast(#intVINID as nvarchar(20))+','+''+','+cast(#intCreateUserID as nvarchar(20))+')'
After PRINTing your query you will see something like this:
Insert Into production_tb_WorkshopDailyReports (intDepartmentID, intReportUserID, intProductionLineID, intBatchID, intVINID,dtOnlineTimeChassis, strRemark, intCreateUserID) values
(1,1,1,1,1,'2016-04-26 22:00:00','remark information',1)
Try to execute it.
+ ',''' + convert(varchar(26), #dtOnlineTime, 121) + ''',''' + #strRemark + ''',' +
Use CONVERT to convert the date value into a string.
Wrap dates and string values in quotes.

Entity Framework Conditional Left Join and Custom Column Select

I have a stored procedure which results in lots of data. and also want to convert this to EF
unable to figure out how to join to the relavent tables when an attribute is present for the system. and also the column selection is very dynamic in nature,
I could take this sql and execute this directly and get things sorted that way but would miss but the grid in the front end wont be able to handle 600mb of data thrown from the database.
so need paging thought can do this better with EF.
for reference purpose I have the following sql below.
Declare #SQL varchar(max);
Declare #SelectColumns VARCHAR(MAX)
SELECT DISTINCT #SelectColumns= STUFF((SELECT ',''' + [PrimaryDataSource] + ''' Golden'
+ ISNULL(CASE WHEN System1 IS NOT NULL THEN ', System1.' + QUOTENAME([System1]) + ' System1' END, '')
+ ISNULL(CASE WHEN System2 IS NOT NULL THEN ', System2.' + QUOTENAME([System2]) + ' System2' END, '')
+ ISNULL(CASE WHEN [System3] IS NOT NULL THEN ', System3.' + QUOTENAME([System3])+ ' System3' END, '')
+ ISNULL(CASE WHEN System4 IS NOT NULL THEN ', System4.' + QUOTENAME(System4)+ ' System4' END, '')
+ ISNULL(CASE WHEN System5 IS NOT NULL THEN ', System5.' + QUOTENAME(System5)+ ' System5' END, '')
+ ISNULL(CASE WHEN System6 IS NOT NULL THEN ', System6.' + QUOTENAME(System6)+ ' System6' END, '')
FROM [dbo].[TBL_Mapping]
where Attribute =#attributeName
FOR XML PATH(''), TYPE
).value('.', 'VARCHAR(MAX)')
,1,1,'')
SET #SQL = '
SELECT distinct
m.ID MappingID,
m.KeyValueUniqueKey,
m.ValueKeyUniqueKey,
' + #SelectColumns + '
FROM [dbo].[TBL_Mapping] M '
IF CHARINDEX('System1.',#SelectColumns) > 0
BEGIN
SET #SQL = #SQL +
'
LEFT OUTER JOIN dbo.VW_System1_ALL System1 ON
System1.System1ID=M.System1ID '
END
IF CHARINDEX('System2.',#SelectColumns) > 0
BEGIN
SET #SQL = #SQL +
'
LEFT OUTER JOIN dbo.TBL_System2 System2 ON
M.System2ID= System2.System2ID '
END
IF CHARINDEX('System4.',#SelectColumns) > 0
BEGIN
SET #SQL = #SQL + '
LEFT OUTER JOIN DBO.tbl_System4 System4 ON
System4.Key1 = M.KeyValueUniqueKey AND
System4.Value1 = ValueKeyUniqueKey '
END
IF CHARINDEX('System5.',#SelectColumns) > 0
BEGIN
SET #SQL = #SQL + '
LEFT OUTER JOIN DBO.tbl_System5 System5 ON
System5.System5Id = M.System5Id'
END
IF CHARINDEX('System6.',#SelectColumns) > 0
BEGIN
SET #SQL = #SQL + '
LEFT OUTER JOIN dbo.tbl_system6 System6 ON
System6.System6Id = M.System6Id'
END
IF CHARINDEX('System3.',#SelectColumns) > 0
BEGIN
SET #SQL = #SQL + '
LEFT OUTER JOIN [dbo].[TBL_System3] System3 ON
System3.System3Id = M.System3Id'
END
SET #SQL = #SQL + '
WHERE m.version=0 and isActive=1
ORDER by m.ID'
print #SQL
exec (#SQL)
I have looked at the Leftjoin2 extn method but that is not helping much.
What is the best possible action to get this on to EF.
or EF itself is a wrong choise for this sort of problems?
You can do dynamic query generating and then in the end do Skip().Take().
Your model for custom object may look like this:
class MappingData
{
//not sure what the data types are.
int MappingId;
int KeyValueUniqueKey;
int ValueKeyUniqueKey;
string System1;
string System2;
...
string System6;
}
Then in the get method map data,
IQueryable<MappingData> sql = db.TBL_Mapping
.Select(m => new MappingData {
MappingId = ID,
KeyValueUniqueKey = KeyValueUniqueKey,
ValueKeyUniqueKey = ValueKeyUniqueKey,
//leave other columns out
//they will be filled in
//dynamically
})
.Distinct();//get distinct
//--------------------
//REPEAT START
bool HasSystem1 = db.TBL_Mapping.Any(m => m.System1 != null);
//left outer join with System1 if it has it in the TBL_Mapping
if (HasSystem1)
{
sql =
from m in sql
join s1 in db.VW_System1_ALL
on m.System1ID equals s1.System1ID into stemp
from st in stemp.DefaultIfEmpty()
select new { MappingId = st.Id,
KeyValueUniqueKey = st.KeyValueUniqueKey,
ValueKeyUniqueKey = st.ValueKeyUniqueKey,
System1 = st.System1 }; //SystemX column.
}
//REPEAT END
//--------------------
// repeat the above for System2 thru System6
//And in the end do paging.
var result = sql
.Skip(currentPageNumber * numberOfObjectsInPage)
.Take(numberOfObjectsInPage);
This is a bad fit for EF. If all you are only trying to add paging -- add your own paging functionality to the stored proc. You can do this by using ROW_NUMBER OVER what every you are sorting by, then use an an outer query to return the page of data you want, for example...
CREATE PROCEDURE [dbo].[PagedSomething]
#pageSize int,
#pageNum int -- assume pages are 0-based
AS
BEGIN
-- outer query does the paging in its where clause,
-- returning the selected "pages" from the raw results of the inner query
SELECT RawResults.SomethingId
FROM
-- inner query where you make your basic data
(SELECT
s.SomethingId
, ROW_NUMBER() OVER(ORDER BY s.SomethingId) RowID
FROM Somethings s) RawResults
WHERE RowID >= #pageNum * #pageSize + 1
AND RowID < (#pageNum + 1) * #pageSize + 1
END

SQL Server - PIVOT

We are working on a C# application, we've been using Linq to SQL or standard ADO (when performance needed) to work with SQL Server.
We have a table layed out like so:
Customer ID, Year/Month, Product Name, Quantity
Each customer has additional columns per product.
We need display this information in a data grid like so:
Customer, Year/Month, Product A Quantity, Product B Quantity, Product C Quantity, etc.
What query could give us these results? And how could it be dynamic no matter what products are added and removed? We will be using a ListView in WPF for displaying the data.
We would just store the information differently, but they can add/remove products all the time.
Will PIVOT work?
(PS - the product names are really in another table for normalization, I changed it a little for simplicity for you guys)
The sql pivot command can be used but it requires the columns to be hard-coded. You could either hard-code them, use dynamic sql to generate the columns, or only get the raw data from sql without a pivot and do the data massaging in c#.
You can use pivot with dynamic SQL. Following T-SQL code is taken from this article on sqlteam.com. I've tried to modify the sample for your needs. Also beware of dangers using dynamic SQL, it might lead to SQL Injection if a product name contains apostrophe.
Create a stored proc first;
CREATE PROCEDURE crosstab
#select varchar(8000),
#sumfunc varchar(100),
#pivot varchar(100),
#table varchar(100)
AS
DECLARE #sql varchar(8000), #delim varchar(1)
SET NOCOUNT ON
SET ANSI_WARNINGS OFF
EXEC ('SELECT ' + #pivot + ' AS pivot INTO ##pivot FROM ' + #table + ' WHERE 1=2')
EXEC ('INSERT INTO ##pivot SELECT DISTINCT ' + #pivot + ' FROM ' + #table + ' WHERE '
+ #pivot + ' Is Not Null')
SELECT #sql='', #sumfunc=stuff(#sumfunc, len(#sumfunc), 1, ' END)' )
SELECT #delim=CASE Sign( CharIndex('char', data_type)+CharIndex('date', data_type) )
WHEN 0 THEN '' ELSE '''' END
FROM tempdb.information_schema.columns
WHERE table_name='##pivot' AND column_name='pivot'
SELECT #sql=#sql + '''' + convert(varchar(100), pivot) + ''' = ' +
stuff(#sumfunc,charindex( '(', #sumfunc )+1, 0, ' CASE ' + #pivot + ' WHEN '
+ #delim + convert(varchar(100), pivot) + #delim + ' THEN ' ) + ', ' FROM ##pivot
DROP TABLE ##pivot
SELECT #sql=left(#sql, len(#sql)-1)
SELECT #select=stuff(#select, charindex(' FROM ', #select)+1, 0, ', ' + #sql + ' ')
EXEC (#select)
SET ANSI_WARNINGS ON
Then try the following (I haven't test it, you might need to add qty to select statement)
EXECUTE crosstab 'select ProductID,CustomerID, YearMonth from sales group by ProductId', 'sum(qty)','ProductId','sales'
If you want to try a method that doesn't involve dynamic SQL, you could go through C#.
This guy ran a test comparing the two: http://weblogs.sqlteam.com/jeffs/jeffs/archive/2005/05/12/5127.aspx

Categories

Resources