Use a SQL Server stored procedure with unlimited number of multiple criteria instead of a view - asp.net

I have a page where users select multiple search criteria to retrieve data from a SQL Server 2014 view. The view is grabbing data from a table on a linked server (I am not able to put the view directly on that server, and the table I am reading from has over 800 million rows so copying that data onto the local server isn't going to happen).
Of course, I can't index the view either (on linked server) so I'm trying to find a way to stop the timeouts from happening when the query is run. Is it possible to do something like this in a stored procedure?
SELECT
cast(trees as varchar(3)) as Trees
, MIN(fruitnumber) AS FN_Start
, MAX(fruitnumber) AS FN_End
, COUNT(CASE WHEN fruitType = 'apple' THEN 1 ELSE NULL END) AS apple
, COUNT(CASE WHEN fruitType = 'banana' THEN 1 ELSE NULL END) AS banana
FROM
view_fruitReport
WHERE
(orchard = #orchard) and
and here's where it gets wonky. Users select the orchard from a dropdown (not a combobox because we use IE11 and ajaxtoolkit combo box still doesn't work there) so only one selection possible but.
They are able to add criteria to listboxes. Unlimited criteria. And they don't need to select any of the criteria, they can just search by orchard.
So the rest of the WHERE clause is built based on what they have added to the listboxes.
Like this:
' check if items selected in both listboxes'
If trees_Listbox.Items.Count > 0 Then
If fruitminListBox.Items.Count > 0 Then
'cycle through items in fruitnum listbox to create an "in" clause for sql query'
For Each item As ListItem In trees_Listbox.Items
whereString += String.Join(",", item) + ", "
Next
whereString = Left(whereString, Len(whereString) - 2) + ")"
selectQry += "(" + wherecls + whereString + ")"
whereFNcls = "(fruitNumber between "
For Each itemFNmin As ListItem In fruitminListBox.Items
'create a "between" clause for the min and max FN values entered by user.'
whereOEcls += itemFNmin.Value + " and " + fruitmaxListBox.Items(i).ToString + ") or (fruitNumber between " '(fruitnumber between number and number) or '
i += 1
Next
'trim off the last text portion of the whereOEcls'
whereOEcls = Left(whereOEcls, Len(whereFNcls) - 25)
selectQry += " and (" + whereFNcls + ") GROUP BY trees ORDER BY trees"
fruityData.SelectCommand = selectQry
WeeklyGridView.Visible = True
Else
'see if FN is empty but trees is selected'
For Each item As ListItem In trees_Listbox.Items
whereString += String.Join(",", item) + ", "
Next
whereString = Left(whereString, Len(whereString) - 2)
selectQry += wherecls + whereString + ") GROUP BY trees ORDER BY trees"
fruityData.SelectCommand = selectQry
WeeklyGridView.Visible = True
End If
Else
Essentially ending up with a where clause that could look like this:
WHERE (orchard = #orchard)
and trees in (100,200,300,400)
and fruitnumber between (itemFNmin.Value and itemFNmax.Value)
or fruitnumber between (itemFNmin.Value and itemFNmax.Value)
etc etc etc
Which works except it makes things very ugly and I am certain is a poor way of doing this.
I have no clue if/how I can make these lists of variables pass to a stored procedure as multiple arrays or tables etc.
Probably anything is better than having them tied to a view, whose linked server table isn't even an indexed table (not my fault haha)

For your first question: You can return the Count of each fruit type, but it will have performance implications as it requires a subquery for each one. This also requires that you hard code each possible fruit type in the query. I assume that the fruit types can change or have other types added to them, so this isn't the most desirable in terms of maintenance either. You can't dynamically add columns to a query unless you build SQL in your proc and make use of sp_executesql, which is more convoluted than doing in line SQL in your .Net code.
SELECT
cast(trees as varchar(3)) as Trees
, MIN(fruitnumber) AS FN_Start
, MAX(fruitnumber) AS FN_End
, CASE
WHEN fruitType = 'apple' THEN (SELECT COUNT(fruitType) FROM view_fruitReport WHERE fruitType = 'apple') ELSE NULL
END AS [apple]
, CASE
WHEN fruitType = 'banana' THEN (SELECT COUNT(fruitType) FROM view_fruitReport WHERE fruitType = 'banana') ELSE NULL
END AS [banana]
FROM
view_fruitReport
WHERE
(orchard = #orchard)
For your second question, you can pass in lists/tables into a stored procedure. One method is to pass some sort of delimited string and parse it using T-SQL. I recommend a different approach, however, which is
Table Value Parameters. This is a parameter that acts as a table that you can join with in your stored procedure.
Here is an example for implementing a Table Value Parameter for the Trees column.
You will first need to declare a SQL Type:
CREATE TYPE [dbo].[Trees] AS TABLE (Trees INT)
Then you can reference it in your stored procedure as a parameter that acts as a table. Note that you can't use WITH(NOLOCK) with these and must specify READONLY in the paramter:
CREATE PROCEDURE [dbo].[up_getOrchardInfo]
(
#Trees As [dbo].[Trees] READONLY
, #Orchard INT
)
AS
BEGIN
SELECT
cast(trees as varchar(3)) as Trees
, MIN(fruitnumber) AS FN_Start
, MAX(fruitnumber) AS FN_End
, COUNT(CASE WHEN fruitType = 'apple' THEN 1 ELSE NULL END) AS apple
, COUNT(CASE WHEN fruitType = 'banana' THEN 1 ELSE NULL END) AS banana
FROM
view_fruitReport AS F
INNER JOIN #Trees AS T
ON F.Trees = T.Trees
WHERE
(orchard = #orchard)
END
GO
The above example will filter by the Trees passed in. Note that if want to return everything for the Orchard if #Trees is Null or the count is 0 you will need to include that conditional logic in your stored procedure.
IF (#Trees IS NULL OR (SELECT COUNT(1) FROM #Trees = 0))
BEGIN
--No Join to #Trees
END
ELSE
BEGIN
--Query from above.
END
Finally, on the .Net side you will need to pass in a DataTable object as a Parameter on the SqlCommand with the Type of Structured:
Dim sqlCommand As New SqlCommand("up_getOrchardInfo", sqlConnection.SqlConnection)
sqlCommand.CommandType = CommandType.StoredProcedure
Dim sqlTreesParameter As New SqlParameter("#Trees", SqlDbType.Structured)
sqlOrchardParameter.Direction = ParameterDirection.Input
Dim tblExample As New DataTable
tblExample.Columns.Add("Trees", New Integer().GetType())
Dim drExample As DataRow = tblExample.NewRow()
drExample.Item("Trees") = 100
tblExample.Rows.Add(drExample)
'Adjust if Orchard is a VarChar/String'
Dim sqlOrchardParameter As New SqlParameter("#Orchard", SqlDbType.Int)
sqlOrchardParameter.Direction = ParameterDirection.Input
sqlOrchardParameter.Value = intYourOrchardValue
sqlCommand.Parameters.Add(sqlTreesParameter)
sqlCommand.Parameters.Add(sqlOrchardParameter)
'Execute Dataset
All of this said, you may want to consider making multiple stored procedures. One stored procedure could be optimized for returning everything when only an Orchard is passed and another for when Trees are also passed. This depends on how many parameters you're dealing with.

Maybe there's a UX answer. When you know it's not going to come back for a long time give the user a heads up and confirm if they want to wait or not. (With check box on the confirmation that says, "don't show me this again.")
But don't re-event the godawful estimated file transfer time from Windows Explorer.

Related

SQL Server stored procedure dynamic Where condition in temporary table

I am using ASP.NET and a SQL Server database.
I have options to search values from gridview base on some criteria. The criteria are mostly independent from each other.
For example:
"where ProductType = " + Convert.ToInt32(recordType.persoRecord) +
" and AccountNumber like '%" + SearchValue + "%'";
or
"where fileid=" + File_ID + ShowSearch +
" and lower(j.CardHolderName) like '%" + SearchValue.Trim().ToLower() + "%'
There are a lot of options to search by user. I have millions of rows of data in this table, in order to fetch the data and bind it fast to gridview, I have created a stored procedure.
It works fine for fetching and binding but for searching, it's hard to manage. Due to I don't have much time, i want to configure the stored procedure to 'if there's a searching' fetch the searched data only.
Here is my stored procedure:
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[SetJobsGrid]
(#FileID varchar(50),
#PageIndex int = 1,
#PageSize int = 1,
#DynamicQuery NVARCHAR(MAX),
#SearchFlag bit,
#RecordCount int output)
AS
BEGIN
DECLARE #SearchQuerySQL as nvarchar(MAX)
SET NOCOUNT ON;
select ROW_NUMBER()OVER (
order by j.creationdate asc,
j.id desc
) AS RowNumber
, j.id as 'Serial #', j.jobname as 'File Record Name',
j.RecordNumber as 'Record Number',i.issuername as'Issuer Name',p.ProductName as 'Product Name',p.productNumber as 'Product Number',
(j.AccountNumber) as AccountNumber ,j.CardHolderName as 'Card Holder Name', j.CardholdersBranchName as 'Card Holder Branch Name',
j.ShipmentBranchName as 'Shipment Branch Name', j.EmbossingCardholderName as 'Embossing Card Holder Name', j.MaskedPAN as 'PAN',
j.creationdate as 'File Record Creation Date', j.status as 'File Record Status', j.chipdatastatuserror as 'ChipDataStatus Erro',
j.chipdatastatuserrormessage as 'Error Message', j.chipdatastatus as 'Data Prepared',j.isduplicaterecord as 'isduplicate',j.isduplicatefromsamefile as 'IsDuplicateFromSameFile',
j.validationerrors , j.isworkordercreatedForCard,j.isworkordercreatedForPin,j.isworkordercreatedForCarrier,j.PersoMachineId,j.PinMachineId,j.CarrierMachineId
INTO #Results
FROM jobs j join issuer i on j.issuerid=i.id join Product p on p.id=j.productid WHERE fileid = #FileID
IF(#SearchFlag = 1)
begin
select #SearchQuerySQL = 'SELECT ' + #RecordCount + ' = COUNT(*) FROM #Results ' + #DynamicQuery
EXEC(#SearchQuerySQL)
select #SearchQuerySQL = 'SELECT * FROM #Results ' + #DynamicQuery + ' and RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1'
EXEC(#SearchQuerySQL)
end
ELSE
begin
SELECT #RecordCount = COUNT(*)
FROM #Results
SELECT * FROM #Results
WHERE RowNumber BETWEEN(#PageIndex -1) * #PageSize + 1 AND(((#PageIndex -1) * #PageSize + 1) + #PageSize) - 1
end
DROP TABLE #Results
END
When the SearchFlag is set to true from ASP.NET, I want to fetch only searched value. #DynamicQuery set from asp for example:
WHERE AccountNumber LIKE '%" + SearchValue + "%'"
or with many different case.
When I run this stored procedure as in the above, I get an exception:
Conversion failed when converting the varchar value 'select ' to data type int
Regards
To make the stored procedure use your dynamic where statement, it is better to use sp_executesql
example:
EXEC sp_executesql
N'select * from Employees where Id = #param1',
N'#param1 int'
,#param1 = 1
for more information about dynamic query refer to the following site
SQL Server Dynamic SQL
Too long for a comment but you need to understand why the error occurs to fix it. You have this snippet in your code
select #SearchQuerySQL = 'SELECT ' + #RecordCount +
First, if you intend to assign a scalar value (e.g., #SearchQuerySQL) then you should use SET, not SELECT. That's another topic you can research at your leisure. The assignment expression that follows is where you intended to do string concatenation. Unfortunately, the interpretation of the plus sign operator varies according to datatypes involved.
What happens when the database engine encounters an operator that involves 2 different datatypes. Like any other language, one (or both) must be converted to the same type in order to perform the expressed operation. How does the engine do that? There are rules for datatype precedence. In this case, your int parameter has higher precedence and so those strings in that expression are converted to int. That fails with the error that you encountered.
If you want to write dynamic sql, you need to have an advanced understanding of tsql. You should also consider searching the internet first before trying to reinvent the wheel. Maybe Aaron's article on dynamic pagination might help - but it might be a bit much for you at this point.
And while you're mucking about with things, add some comments within the procedure declaration about the usage you intend to support. No one should have to read your code to understand what it does and how it should be used.

Forms 6i if record exists then update

I need to create select statement in post_insert trigger. Is it possible if yes then how?
I want to check another table records if it exists then it will update it otherwise insert as new record. Please help.
My block code is that i want to run
DECLARE
EXSIST_TYPE varchar2(50);
EXSIST_NAME varchar2(50);
EXSIST_COMPANY VARCHAR2(100);
BEGIN
SELECT PRO_TYPE, PRO_NAME, COMPANY_NAME INTO EXSIST_TYPE, EXSIST_NAME ,EXSIST_COMPANY FROM STOCK;
IF
:PURCHASE_DETAIL.PRO_TYPE <> EXSIST_TYPE AND
:PURCHASE_DETAIL.PRO_NAME <> EXSIST_NAME AND
:PURCHASE_DETAIL.COMPANY_NAME <> EXSIST_COMPANY THEN*/
IF
:PURCHASE.RADIO_TYPE = 'PURCHASE' THEN
INSERT INTO STOCK(
PRO_TYPE ,
PRO_NAME ,
COMPANY_NAME ,
QUANTITY ,
PURCHASE_RATE,
SALE_RATE ,
RACK_NUM
)
VALUES
(
:PURCHASE_DETAIL.PRO_TYPE,
:PURCHASE_DETAIL.PRO_NAME,
:PURCHASE_DETAIL.COMPANY_NAME,
:PURCHASE_DETAIL.QUANTITY,
:PURCHASE_DETAIL.PRICE,
:PURCHASE_DETAIL.SALE_PRICE,
:PURCHASE_DETAIL.RACK_NUM
);
END IF;
ELSIF
:PURCHASE_DETAIL.PRO_TYPE = EXSIST_TYPE AND
:PURCHASE_DETAIL.PRO_NAME = EXSIST_NAME AND
:PURCHASE_DETAIL.COMPANY_NAME = EXSIST_NAME THEN
IF
:PURCHASE.RADIO_TYPE = 'PURCHASE' THEN
UPDATE STOCK SET
STOCK.QUANTITY = STOCK.QUANTITY+:PURCHASE_DETAIL.QUANTITY
WHERE
STOCK.PRO_TYPE = :PURCHASE_DETAIL.PRO_TYPE AND
STOCK.PRO_NAME = :PURCHASE_DETAIL.PRO_NAME AND
STOCK.COMPANY_NAME= :PURCHASE_DETAIL.COMPANY_NAME;
ELSIF
:PURCHASE.RADIO_TYPE = 'PRCH_RETURN' THEN
UPDATE STOCK SET
STOCK.QUANTITY = STOCK.QUANTITY-:PURCHASE_DETAIL.QUANTITY
WHERE
STOCK.PRO_TYPE = :PURCHASE_DETAIL.PRO_TYPE AND
STOCK.PRO_NAME = :PURCHASE_DETAIL.PRO_NAME AND
STOCK.COMPANY_NAME = :PURCHASE_DETAIL.COMPANY_NAME;
END IF;
END IF;
END;
You never said what happened when you ran that code.
Anyway: requirement you mentioned ("if it exists then it will update it otherwise insert") looks like an excellent candidate for a MERGE statement (also called upsert, as a combination of UPdate and inSERT).
As Forms 6i is an old piece of software, I'm pretty much sure that MERGE can't directly be used there. However, if the underlying database is at least 9i, MERGE will work - create a stored procedure that contains MERGE, and pass form items' values as parameters.
Here's an example (taken from here; have a look for more examples. I'm lazy to create my own code):
MERGE INTO employees e
USING hr_records h
ON (e.id = h.emp_id)
WHEN MATCHED THEN
UPDATE SET e.address = h.address
WHEN NOT MATCHED THEN
INSERT (id, address)
VALUES (h.emp_id, h.address);

SELECT CASE - THEN - WHEN query throws an error [duplicate]

My code follows:
SELECT COUNT(_id) AS count FROM general WHERE _id = 1 CASE WHEN count > 0 THEN UPDATE general SET userGivenId = 'xxx' WHERE _id = 1 ELSE INSERT INTO general (userGivenId) VALUES ('xxx' ) END
With the error:
android.database.sqlite.SQLiteException: near "CASE": syntax error: , while compiling: SELECT COUNT(_id) AS count FROM general WHERE _id = 1 CASE WHEN count > 0 THEN UPDATE general SET userGivenId = 'xxx' WHERE _id = 1 ELSE INSERT INTO general (userGivenId) VALUES ('xxx' ) END
This is the shortest query I will use. Why I do this is because my other queries will have rows that needs to be updated but some may not be touched. Using replace will replace all the data (at least that is how it works for me on my Android phone). For instance my File class will have a filePath, but sometimes the response from the server will return null and I am only to update the File IF the server returns a new File.
Did I forget to write anything?
SQLite does not have any control logic because this would not make sense in an embedded database (there is no separate server machine/process whose asynchronous execution could improve performance).
CASE can be used only for expressions, not for entire commands.
Handle the control logic in your app:
Cursor c = db.rawQuery("SELECT 1 FROM general WHERE _id = 1", null);
if (c.moveToFirst())
db.execSQL("UPDATE general SET userGivenId = 'xxx' WHERE _id = 1");
else
db.execSQL("INSERT INTO general (userGivenId) VALUES ('xxx')");
For these particular commands, if you have a unique constraint on the _id column (and a primary key is constrained to be unique), you can use the INSERT OR REPLACE command:
INSERT OR REPLACE INTO general(_id, userGivenId) VALUES(1, 'xxx')

SQL server conditional select statement

Ok, this might be an easy one, but I just can't get it.
I am creating a page which will query a table with many columns and most items are not unique. I need to be able to get a list of records that match as many of the (up to 4) search criteria as possible.
Example:
I am user searching for the following items, I enter at least one and up to 4 of the items below in a text box:
Name, age, gender, weight (user may or may not fill in all of them).
If he just enters "F" for gender, then he will get a list of thousands of females with their name, age, gender and weight.
However if he enters "F" for gender and "300" for weight, he will get a much smaller list of returned records.
I need to be able to create a sql statement that can perform that search with that functionality.
advTHANKSance
I've used similar to the one below to do what you are trying:
DECLARE #Gender varchar(1)
DECLARE #Age int
DECLARE #Weight int
DECLARE #Name varchar(64)
SELECT * FROM MyTable
WHERE
(#Gender is null OR Gender = #gender)
AND (#weight is null OR Weight = #weight)
AND (#Age is null OR age = #Age)
and (#Name is null OR Name = #Name)
if you were to create a stored procedure (which i would recommend) it would look like this:
CREATE PROCEDURE SelectRecords
#Gender varchar(1),
#Age int,
#Weight int,
#Name varchar(64)
AS
SELECT * FROM MyTable
WHERE
(#Gender is null OR Gender = #gender)
AND (#weight is null OR Weight = #weight)
AND (#Age is null OR age = #Age)
and (#Name is null OR Name = #Name)
What this stored procedure is doing is checking to see if you passed a value in for the specific parameter. If you DID NOT then it will be null and the condition will be true. if you DID then it will not be null and the second condition must evaluate to true for the record to be returned.
I've often seen this done with the following SQL statement (where #gender, #weight, #age, and #name are filled in with data from the user, and gender, weight, age, and name are table fields):
SELECT * FROM MyTable
WHERE
gender = COALESCE(#gender, gender)
AND weight = COALESCE(#weight, weight)
AND age = COALESCE(#age, age)
and name= COALESCE(#name, name)
(Edit: I just wanted to add a short explanation of why this works for anyone not familiar with coalesce. The coalesce function takes the first not-null value of the 2 passed to it. So if there is something in the # parameter, which means the user entered data for that field, it will check if the field equals that user-entered value; if the user didn't enter anything and the # parameter is null, it will test against the second value, which is the field itself - and as the field is always equal to itself, this will return all records - it won't filter based on this field at all.)
I did stuff like this by combining a null check with the parameter. If it was null then everything got included, if not then the other part actually mattered
CREATE myFunnyProc ( #p1 nvarchar(10), #p2 nvarchar(10) ) AS BEGIN
SELECT * FROM dbo.myReallyLongTable table
WHERE
(#p1 is null or table.name LIKE #p1)
AND (#p2 is null or table.age = #p2)
END
#Abe - Your solution will work IF Age and Weight are not between Name and Gender in the SELECT statement. I know cuz I tried it :) on SQL Server 2008. I made a table with many records with NULLs scattered throughout. I also made a proc, and running it as you wrote it would not return rows when age or weight were entered as parameters. I moved the int based params in the SELECT statement to the top or bottom of list, both worked. But nesting the int params between the string params in the select statement and then specifying the int params made it fail...
So why did the original not work?? :) Anyone?
BTW, the COALESCE will not work at all (yes, I tested it and reordered the SELECT statement in the proc). It LOOKED like it should work...
Seeing as you are using ASP.NET, you could take a look at LINQ-to-SQL which solves this in a very elegant way:
var query = db.T_Persons;
if (txtGender.Text != string.Empty)
query = query.Where(x => x.Gender == txtGender.Text);
if (txtWeigth.Text != string.Empty)
query = query.Where(x => x.Weight == int.Parse(txtWeight.Text));
...
Of course, you'll need to be using .NET 3.5 or newer.
The answer is something most people try to stay away from it is dynamic sql.
I would suggest creating a stored procedure you can call for this, but here is the code. You need to put the name of your table in place of tablename.
Declare
#gender varchar(1),
#weight int,
#age int,
#name varchar(100),
#sql varchar(200),
#variableCount int
set #variableCount = 0
set #sql = 'select * from tablename'
if(#gender is not null)
Begin
#sql += ' where gender = #gender'
#vaiableCount = #VariableCount+1
End
if(#weight is not null)
Begin
if(#variableCount = 0)
Begin
#sql += ' Where '
End
else
#sql += ' And '
#sql += 'weight = #weight'
End
if(#age is not null)
Begin
if(#VariableCount = 0)
Begin
#sql += ' where '
End
else
#sql += ' And '
#sql += 'age = #age'
End
if(#name is not null)
Begin
if(#VariableCount = 0)
Begin
#sql += ' where '
End
else
#sql += ' And '
#sql += 'name = #name'
End
execute #sql

Linq to Sql - Only select certain information (w/ Predicate Builder)

I am using Linq to Sql with Predicate Builder and am trying to optimize how much information is retrieved from the database. I would like to select only certain fields to display them in a gridview. When I select only what I want, the search parameters I add (see below) don't work, and neither does PredicateBuilder. Here's what I'm currently doing (that works, but gets EVERYTHING which is way too much info)
' Initial Setup '
Dim db As New MyDataContext()
Dim results = From p In db.Products _
Select p
' Search '
If (testCase) Then
results = results.Where(Function(p) p.SomeAttribute = 123)
End If
If I change that to only select what I need, like this:
Dim results = From p In db.Products _
Select p.Name, p.SomethingElse
then I've noticed if the information is selected (ie I select p.SomeAttribute) then I can search (add the where clause) on that attribute, but if its not, I can't. And with predicate builder it only works if I select the entire item (ie select p). All this should be doing is creating SQL statements which don't have to select the attribute to search by it. How can I get this to work and select only what I need, but search by anything and keep prediate builder working? Any help MUCH APPRECIATED! Thanks
You could try to initially do a "select p" at the beginning, then add all your where clauses, and at the very end, select just what you need from it.
' Initial Setup '
Dim db As New MyDataContext()
Dim results = From p In db.Products _
Select p
' Search '
If (testCase) Then
results = results.Where(Function(p) p.SomeAttribute = 123)
End If
' trim down the columns after you've added the wheres...
Dim results2 = from p in results
Select p.Name, p.SomethingElse
You can't modify the "select list" (this is how I understood your question. I might have misunderstood it) with predicate builder (which builds boolean expressions). You should manually use stuff in System.Linq.Expressions namespace to do that but I suggest using Dynamic LINQ instead.
Sounds like you are doing the where on the projection and not the original Product. Do the projection Select p.Name, p.SomethingElse at the end after all the search criteria has been applied.

Resources