How to add logic to my SQL query to include/exclude search parameters? - tsql

Using SQL Server 2008R2, I have here is a simple query:
SELECT *
FROM ItemData
WHERE FREETEXT(Title, '"' + #OriginalSearchTerm + '"')
AND ( WebsiteID=#WebsiteID AND GeoCity = #GeoCity AND GeoState = #GeoState )
ORDER BY ItemListID DESC
This is all fine when there is a valid value for #GeoCity and #GeoState. However there will be scenarios where #GeoCity = -1 and/or #GeoState = -1.
I would rather not write entire separate queries for these cases, although this would work just fine.
How can I optimize the current query to do just this?
Thanks.

-- This might be more efficient as OR statement executes block for each OR statement.
SELECT *
FROM ItemData
WHERE FREETEXT(Title, '"' + #OriginalSearchTerm + '"')
AND ( WebsiteID=#WebsiteID AND
(GeoCity = ISNULL(#GeoCity,-1)) AND
(GeoState = ISNULL(GeoState,-1))
ORDER BY ItemListID DESC

I'm not sure what you want the query to do when #GeoCity = -1 and/or #GeoState = -1. Assuming you want to exclude the GeoCity = #GeoCity and/or GeoState = #GeoState conditions in that case, here's the query:
SELECT *
FROM ItemData
WHERE FREETEXT(Title, '"' + #OriginalSearchTerm + '"')
AND ( WebsiteID=#WebsiteID AND
(#GeoCity = -1 OR GeoCity = #GeoCity) AND
(#GeoState = -1 OR GeoState = #GeoState) )
ORDER BY ItemListID DESC

Related

converting sql statement back to lambda expression

I have the query below, and its sql code. It's running really slow, so it was re written in sql, now I'm just not sure how to convert the sql back to a lambda expression.
This is the part of the expression giving me the problems, somewhere in
r.RecordProducts.Any()
records = records
.Include(r => r.Employer)
.Include(r => r.Contractor)
.Include(r => r.RecordProducts)
.ThenInclude(rp => rp.ProductDefendant.Defendant)
.Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
|| EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
|| r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));
the any clause does an exist and some funky stuff in the sql where clause below
SELECT [t].[Id], [t].[StartDate], [t].[EndDate], [t].[WitnessName], [t].[SourceCode], [t].[JobsiteName], [t].[ShipName], [t].[EmployerCode]
FROM (
SELECT DISTINCT [r].[RecordID] AS [Id], [r].[StartDate], [r].[EndDate], [r.Witness].[FullName] AS [WitnessName], CASE
WHEN [r].[SourceID] IS NOT NULL
THEN [r.Source].[SourceCode] ELSE N'zzzzz'
END AS [SourceCode], CASE
WHEN [r].[JobsiteID] IS NOT NULL
THEN [r.Jobsite].[JobsiteName] ELSE N'zzzzz'
END AS [JobsiteName], CASE
WHEN [r].[ShipID] IS NOT NULL
THEN [r.Ship].[ShipName] ELSE N'zzzzz'
END AS [ShipName], CASE
WHEN [r].[EmployerID] IS NOT NULL
THEN [r.Employer].[DefendantCode] ELSE N'zzzzz'
END AS [EmployerCode]
FROM [Records] AS [r]
LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
WHERE ([r].[IsActive] = 1) AND (([r.Employer].[DefendantCode] LIKE (N'%' + 'cert') + N'%' OR [r.Contractor].[DefendantCode] LIKE (N'%' + 'cert') + N'%') OR EXISTS (
SELECT 1
FROM [Records_Products] AS [rp]
INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%' AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID])))
) AS [t]
ORDER BY [t].[SourceCode]
OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY
Here is the new sql that works better, just not sure how to convert it back to a lambda expression
SELECT [t].[Id]
,[t].[StartDate]
,[t].[EndDate]
,[t].[WitnessName]
,[t].[SourceCode]
,[t].[JobsiteName]
,[t].[ShipName]
,[t].[EmployerCode]
FROM (
SELECT DISTINCT [r].[RecordID] AS [Id]
,[r].[StartDate]
,[r].[EndDate]
,[r.Witness].[FullName] AS [WitnessName]
,CASE
WHEN [r].[SourceID] IS NOT NULL
THEN [r.Source].[SourceCode]
ELSE N'zzzzz'
END AS [SourceCode]
,CASE
WHEN [r].[JobsiteID] IS NOT NULL
THEN [r.Jobsite].[JobsiteName]
ELSE N'zzzzz'
END AS [JobsiteName]
,CASE
WHEN [r].[ShipID] IS NOT NULL
THEN [r.Ship].[ShipName]
ELSE N'zzzzz'
END AS [ShipName]
,CASE
WHEN [r].[EmployerID] IS NOT NULL
THEN [r.Employer].[DefendantCode]
ELSE N'zzzzz'
END AS [EmployerCode]
FROM [Records] AS [r]
LEFT JOIN [Ships] AS [r.Ship] ON [r].[ShipID] = [r.Ship].[ShipID]
LEFT JOIN [Jobsites] AS [r.Jobsite] ON [r].[JobsiteID] = [r.Jobsite].[JobsiteID]
LEFT JOIN [Sources] AS [r.Source] ON [r].[SourceID] = [r.Source].[SourceID]
LEFT JOIN [Witnesses] AS [r.Witness] ON [r].[WitnessID] = [r.Witness].[WitnessID]
LEFT JOIN [Defendants] AS [r.Contractor] ON [r].[ContractorID] = [r.Contractor].[DefendantID]
LEFT JOIN [Defendants] AS [r.Employer] ON [r].[EmployerID] = [r.Employer].[DefendantID]
LEFT JOIN (
SELECT [rp].[RecordID]
FROM [Records_Products] AS [rp]
INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID]
INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID]
WHERE (
[rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
AND ([rp].[IsActive] = 1)
)
) AS RecordProduct ON [r].[RecordID] = RecordProduct.[RecordID]
WHERE ([r].[IsActive] = 1)
AND (
(
[r.Employer].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
OR [r.Contractor].[DefendantCode] LIKE (N'%' + 'cert') + N'%'
)
OR RecordProduct.RecordID IS NOT NULL --OR EXISTS ( -- SELECT 1 -- FROM [Records_Products] AS [rp] -- INNER JOIN [Product_Defendant] AS [rp.ProductDefendant] ON [rp].[DefendantProductID] = [rp.ProductDefendant].[DefendantProductID] -- INNER JOIN [Defendants] AS [rp.ProductDefendant.Defendant] ON [rp.ProductDefendant].[DefendantID] = [rp.ProductDefendant.Defendant].[DefendantID] -- WHERE ([rp.ProductDefendant.Defendant].[DefendantCode] LIKE (N'%' + 'cert') + N'%' -- AND ([rp].[IsActive] = 1)) AND ([r].[RecordID] = [rp].[RecordID]) -- ) )) AS [t]ORDER BY [t].[SourceCode]OFFSET 0 ROWS FETCH NEXT 500 ROWS ONLY
)
)
The linq expression you supplied and the SQL generated do not match. For one, the linq expression is performing an Include on the various related tables which would have included all of those entity columns in the top-level SELECT which are not present in your example SQL. I also don't see conditions in the Linq expression for the Take 500 & OrderBy, or IsActive assertion on Record.
To be able to help determine the source of any performance concern we need to see the complete Linq expression and the resulting SQL.
Looking at the basis of the Linq expression you provided:
records = records
.Include(r => r.Employer)
.Include(r => r.Contractor)
.Include(r => r.RecordProducts)
.ThenInclude(rp => rp.ProductDefendant.Defendant)
.Where(r => EF.Functions.Like(r.Employer.DefendantCode, "%" + input.DefendantCode + "%")
|| EF.Functions.Like(r.Contractor.DefendantCode, "%" + input.DefendantCode + "%")
|| r.RecordProducts.Any(rp => EF.Functions.Like(rp.ProductDefendant.Defendant.DefendantCode, "%" + input.DefendantCode + "%") && rp.IsActive == true));
There are a few suggestions I can make:
There is no need for the Functions.Like. You should be able to achieve the same with Contains.
Avoid using Include and instead utilize Select to retrieve the columns from the resulting structure that you actually need. Populate these into ViewModels or consume them in the code. The less data you pull back, the better optimized the SQL can be for indexing, and the less data pulled across the wire. Consuming entities also leads to unexpected lazy-load scenarios as systems mature and someone forgets to Include a new relation.
.
records = records
.Where(r => r.IsActive
&& (r.Employer.DefendantCode.Contains(input.DefendantCode)
|| r.Contractor.DefendantCode.Contains(input.DefendantCode)
|| r.RecordProducts.Any(rp => rp.IsActive
&& rp.ProductDefendant.Defendant.DefendantCode.Contains(input.DefendantCode))
.OrderBy(r => r.SourceCode)
.Select(r => new RecordViewModel
{
// Populate the data you want here.
}).Take(500).ToList();
This also adds the IsActive check, OrderBy, and Take(500) based on your sample SQL.

How to execute union query in zf2 where i m using tablegateway?

How to execute the following query
select * from table1 union select * from table2
in zend-framework2 where i am using tablegateway? In the documentation of zf2,they didn't give any details about union query.
Try -
$select1 = new Select('table1');
[.... rest of the code ....]
$select2 = new Select('table2');
[.... rest of the code ....]
$select1->combine($select2); //This will create the required SQL union statement.
To get count of the two tables you have to use a bit of SQL rather then tableGateway -
$sql = new Sql($this->tableGateway->adapter);
$select_string = $sql->getSqlStringForSqlObject($select1);
$sql_string = 'SELECT * FROM (' . $select_string . ') AS select_union';
$statement = $this->tableGateway->adapter->createStatement($sql_string);
$resultSet = $statement->execute();
$total_records = count($resultSet);
$resultSet gives data.
$total_records gives total no. of records.

Parametrized query inside mysqli class

I am using Mertol Kasanan's class for running parametrized queries -
http://liveplanet.googlecode.com/svn-history/r132/trunk/db/DB.php
I am very satisfied with the script except for some issues that I don't seem to put my finger on.
As it states in the brief tutorial in the class's description the method for running the query is:
$result = $db->query('SELECT * FROM `users` WHERE id = ? AND user_type = ? LIMIT ?',$id,$user_type,$limit);
Can anybody figure out how to run a query without defining any parameter as it seems that
$result = $db->query('SELECT * FROM `users` WHERE id = 'y' ");
neither
$result = $db->query('SELECT * FROM `users` WHERE id = 'y' ", '');
do not do the trick as it returns a binding error;
A workaround would be
$result = $db->query('SELECT * FROM `users` WHERE 1 = ? AND id = 'y' ", 1);
Is there a neater way to run my query?
I don't need parameters as the query gets it's values from a safe source inside a class.
Edit:
Let's say I have this:
if($HC == 'C'){
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat != 'D' GROUP BY pic LIMIT ?";
$query = $this->dbs->query($sql,$this->user,$this->user_head,4);
$results = $this->dbs->numRows($query);
if($results < 3){
$sql = "SELECT * FROM `photo` WHERE `user` = ?i AND `pic` != ?s ORDER BY id ASC LIMIT ?";
$query = $this->dbs->query($sql, $this->user,$this->user_head,4);
}
}else{
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat = ?s ORDER BY RAND() LIMIT ?";
$query = $this->dbs->query($sql,$this->user,$this->user_head,$HC,4);
$results = $this->dbs->numRows($query);
}
Now, in order to get the data from the right query I can either define $data->getAll under each query - but that would mean repeating my code or I could try extracting the data from the last defined $query result - which I do not know how to do.
I know that there may be a better way of doing this but I am trying to improve my coding style as I think the safemysql class would need some improvements even if that would mean a bit more documentation.
I could try using $db->getAll instead of $db->query but, as far as I know, I cannot use numRows on GetAll.
As a matter of fact, this class is totally unusable. And the problem you mentioned is a least one.
It seems that someone who wrote it, never used this class in a real life project.
So, if you want a class which works and works way better, go for SafeMysql, as it will do exactly what you want:
$data = $db->getAll("SELECT * FROM `users` WHERE status = 'y'");
(note that you've got your data already, without any further code)
Nevertheless, you have to understand that the following statement of yours
I don't need parameters as the query gets it's values from a safe source inside a class.
is wrong.
It's OK to use a hard-coded value as you wrote it, but if you were intended to use a "safe" variable - it ought to be added via placeholder. Otherwise your query remains error-prone and unsafe.
So, it have to be
$id = 1; // "safe" variable
$data = $db->getRow("SELECT * FROM `users` WHERE id = ?i", $id);
To answer edited question. Not sure if it's what you need, but here is the code. It wu
if($HC == 'C')
{
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat != 'D' GROUP BY pic LIMIT ?";
$data = $this->dbs->getAll($sql,$this->user,$this->user_head,4);
if (count($data) < 3) {
$sql = "SELECT * FROM `photo` WHERE `user` = ?i AND `pic` != ?s ORDER BY id ASC LIMIT ?";
$data = $this->dbs->query($sql, $this->user,$this->user_head,4);
}
} else {
$sql = "SELECT * FROM `photo_c` WHERE `user` = ?i AND `pic` != ?s AND cat = ?s ORDER BY RAND() LIMIT ?";
$data = $this->dbs->query($sql,$this->user,$this->user_head,$HC,4);
}

Is it possible to refactor this statement to remove the subquery?

I'm trying to take data from one column, MyTable.SSN, and copy it to another in the same table, MyTable.SSNWithDashes, just formatted differently. If MyTable.SSN doesn't have exactly 9 digits, I don't care to process it at all.
I've tried this:
IF( SELECT LEN( [SSN] ) FROM [MyTable] ) = 9
UPDATE [MyTable] SET [SSNWithDashes] = LEFT( [SSN], 3 ) + '-' + SUBSTRING( [SSN], 4, 2 ) + '-' + RIGHT( [SSN], 4 )
ELSE
UPDATE [MyTable] SET [SSNWithDashes] = NULL
While this works, it throws an error:
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
While I do understand what the warning is saying, I'm not really sure how to go about this differently.
How can I refactor this to remove that warning (and perhaps read a little cleaner)?
UPDATE dbo.[MyTable]
SET [SSNWithDashes] = CASE
WHEN LEN(SSN) = 9 THEN
LEFT([SSN],3) + '-' + SUBSTRING([SSN],4,2) + '-' + RIGHT([SSN],4)
ELSE NULL
END;
Assuming your SSNWithDashes is already NULL then
UPDATE dbo.[MyTable]
SET [SSNWithDashes] = LEFT([SSN],3) + '-' + SUBSTRING([SSN],4,2) + '-' + RIGHT([SSN],4)
WHERE LEN(SSN) = 9
Every other rows remain NULL

How do you concatenate strings inside of a CONTAINS in SQL Server 2008?

SQL Server 2008 is telling me that it doesn't like the "+" in the CONTAINS.
Not sure what I'm doing wrong here.
INSERT INTO dbo.tblImportTitles
(
ImportTitleGUID,
UserGUID,
TitleName,
TitleGUID
)
SELECT
ImportTitleGUID = T.Item.value('#ImportTitleGUID', 'uniqueidentifier'),
UserGUID = T.Item.value('#UserGUID', 'uniqueidentifier'),
TitleName = T.Item.value('#TitleName', 'varchar(255)'),
TitleGUID =
CASE
WHEN (SELECT TOP(2) COUNT(TitleGUID) FROM dbo.tblTitles WHERE CONTAINS(Title, '''' + T.Item.value('#TitleName', 'varchar(255)') + '''')) = 1
THEN (SELECT TitleGUID FROM dbo.tblTitles WHERE CONTAINS(Title,'''' + T.Item.value('#TitleName', 'varchar(255)') + ''''))
ELSE NULL
END
FROM #ImportTitlesInsertXml.nodes('BatchTitles/BTitle') AS T(Item)
Update
I'v decided to move this to a scalar function.
It was a lot easier to handle the code that way.
use QUOTENAME(#VARIABLE,'''') the + is not your problem.