【问题标题】:search refinement in WebMatrixWebMatrix 中的搜索细化
【发布时间】:2013-07-23 12:53:35
【问题描述】:

我开始构建一个网页来优化一些搜索结果。下面的代码运行良好,如果我添加一个查询字符串(即?beds=4,它会返回正确的结果。但是,如果我指定两个查询字符串(即?beds=4&sleeps=8,它返回匹配任何一个(所有具有 4 张床(不考虑睡眠)的属性和具有 8 个睡眠(不考虑床)的所有属性)的结果,而不是两者。我需要某种 AND 语句,以便结果匹配床和睡眠?

@{
Layout = "~/_SiteLayout.cshtml";
Page.Title = "Search";

string searchText = Request.Unvalidated["searchText"];

var searchTerms = searchText.Split('"').Select((element, index) => index % 2 == 0 ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) : new string[] { element }).SelectMany(element => element).ToList();

for (int i=0; i<searchTerms.Count; i++)
{
    if (searchTerms[i].ToUpper() == "THE" || searchTerms[i].ToUpper() == "AND" || searchTerms[i].ToUpper() == "AS" || searchTerms[i].ToUpper() == "AN" || searchTerms[i].ToUpper() == "BUT" || searchTerms[i].ToUpper() == "OR" || searchTerms[i].ToUpper() == "OF" || searchTerms[i].ToUpper() == "IF" || searchTerms[i].ToUpper() == "IS" || searchTerms[i].ToUpper() == "IN" || searchTerms[i].ToUpper() == "IT" || searchTerms[i].ToUpper() == "BY" || searchTerms[i].ToUpper() == "TO" || searchTerms[i].ToUpper() == "FOR" || searchTerms[i].Length <= 1 || String.IsNullOrWhiteSpace(searchTerms[i]))
        {
        searchTerms.RemoveAt(i);
        i--;  //decrements 'i' if an element is removed because all indexes after this one will drop by one. This ensures that no indexes get skipped.
        }
}



var db = Database.Open("StayInFlorida");
string searchQueryString = "";
int termCount = searchTerms.Count;
string[] searchTermsArray = searchTerms.ToArray();

searchQueryString = "SELECT * FROM PropertyInfo WHERE numBedrooms = ";

for (int i=0; i<termCount; i++)
{
    if (i != 0)
        {
        searchQueryString += "OR numBedrooms = "; //Ensures that this is not appended for the first term. Alternatively, of course, you can use "AND", depending on how you want the results returned, but you probably want "OR".
        }

searchQueryString += "@" + i + " ";
}

searchQueryString += "UNION ";
searchQueryString += "SELECT * FROM PropertyInfo WHERE numSleeps = ";

for (int i=0; i<termCount; i++)
{
    if (i != 0)
        {
        searchQueryString += "OR numSleeps = ";
        }

searchQueryString += "@" + i + " ";
}

searchQueryString += "UNION ";
searchQueryString += "SELECT * FROM PropertyInfo WHERE numBathrooms = ";

for (int i=0; i<termCount; i++)
{
    if (i != 0)
        {
        searchQueryString += "OR numBathrooms = ";
        }

searchQueryString += "@" + i + " ";
}

searchQueryString += "ORDER BY anyTermYouWishToOrderBy DESC";

if (searchTermsArray.Length > 0) //Prevents a server-side error if the searchTerm list was empty when converted to the searchTermsArray
    {
    var queryResults = db.Query(searchQueryString, searchTermsArray);
    }
}

【问题讨论】:

  • 我不确定我是否完全理解您的要求,但我知道没有任何方法可以在查询字符串中包含逻辑 AND。我认为您应该采用的方法是收集查询字符串值并在服务器端处理您需要的物流(从代码的外观来看,使用 C#)。
  • 当/如果我觉得我完全理解您需要的结果时,我很乐意给出更具体的答案。
  • 嗨,感谢 VoidKing 回复我。所以我基本上需要从查询字符串中提取 2 个参数,并让它们在我的 SQL 选择语句中返回与它们匹配的结果。听起来是不是更清楚一点?
  • 我猜上面的 2 个 IF 语句需要以某种方式合并在一起,但我想不出正确的语法
  • 好吧,我刚刚在 cmets 中看到了您对 Mike 回答的附加规定。我必须说,对您所需解决方案的任何要求都应在您的问题中得到解决,以便第一次可以更合适地编写答案。话虽如此,我意识到您当时可能不知道您需要这个额外的功能。我知道这一直发生在我身上。我正在考虑如何最好地从逻辑上处理您的问题。

标签: sql razor webmatrix webmatrix-2


【解决方案1】:

警告:

我希望这个答案可以帮助您在逻辑上找到一种解决方案,以扩展功能以包含许多您直到运行时才知道但必须用于查询数据库的变量。我知道您不是在搜索字符串,而是在搜索数字,或者可能是其他类型的数据,这些数据可能是从前一页的表单中收集的,但是一旦您获得了目标页面所需的所有数据(使用查询字符串可能不会合适),物流应该是一样的,所以我们从那里开始。最后,如果您发现无法从表单获取未知数量的变量到目标页面,我们也可以解决这个问题,但现在我假设您已经在页面上收集了必要的数据查询数据库。

对于这个例子,我将假设用户输入一个文本字符串,我必须用空格分割,从中提取单词,并将每个单词存储在一个列表中。

首先,(对于此示例)使用前一页上的GET&lt;form&gt; 检索单个查询字符串中的值。这个,你已经知道怎么做了。

string searchText = Request.Unvalidated["searchText"];

请注意,我必须在未验证的情况下检索此值,因为如果用户在搜索词中键入尖括号,则会引发服务器端错误。不过这没关系,因为我们会小心处理不受信任的数据。

接下来,您可以简单地检查他们的整个搜索文本,以确保它不是空的或像这样的空格:

if (!String.IsNullOrWhiteSpace(searchText))

在该分支中,您可以开始执行完成这项工作所需的所有功能。

您可以替换任何您想忽略的简单字符(我们将使用撇号和逗号)。

searchText = searchText.Replace("'", "").Replace(",", "");

现在,下一行有点复杂,我什至不确定我是否会正确解释它(我对 LINQ 还是很陌生),所以,只要说它有效地将字符串拆分为空格就足够了(处理双空格等问题并将引号中的文本视为单个项目)并将它们存储在列表中(这部分可能超出您的问题范围,如果是,我很抱歉,但以防万一您需要使用类似的东西...)。

var searchTerms = searchText.Split('"').Select((element, index) => index % 2 == 0 ? element.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) : new string[] { element }).SelectMany(element => element).ToList();

现在,如果你愿意,你可以从这个列表中省略你想忽略的词(我将在这个例子中使用一些简单的常用词,以及任何单个字符的项目,当然还有空的、空的,或空白项目):

for (int i=0; i<searchTerms.Count; i++)
{
    if (searchTerms[i].ToUpper() == "THE" || searchTerms[i].ToUpper() == "AND" || searchTerms[i].ToUpper() == "AS" || searchTerms[i].ToUpper() == "AN" || searchTerms[i].ToUpper() == "BUT" || searchTerms[i].ToUpper() == "OR" || searchTerms[i].ToUpper() == "OF" || searchTerms[i].ToUpper() == "IF" || searchTerms[i].ToUpper() == "IS" || searchTerms[i].ToUpper() == "IN" || searchTerms[i].ToUpper() == "IT" || searchTerms[i].ToUpper() == "BY" || searchTerms[i].ToUpper() == "TO" || searchTerms[i].ToUpper() == "FOR" || searchTerms[i].Length <= 1 || String.IsNullOrWhiteSpace(searchTerms[i]))
    {
        searchTerms.RemoveAt(i);
        i--;  //decrements 'i' if an element is removed because all indexes after this one will drop by one. This ensures that no indexes get skipped.
    }
}

接下来,声明一些您需要的基本变量:

var db = Database.Open("StayInFlorida");
string searchQueryString = "";
int termCount = searchTerms.Count;
string[] searchTermsArray = searchTerms.ToArray();

现在,当您不知道要针对多少个变量测试列时,一些逻辑将帮助您处理编译 sql 查询字符串(对于此示例,我将假设存在三个数据库列: numBedrooms、numSleeps 和 numBathrooms)。

searchQueryString = "SELECT * FROM PropertyInfo WHERE numBedrooms = ";

for (int i=0; i<termCount; i++)
{
    if (i != 0)
    {
        searchQueryString += "OR numBedrooms = "; //Ensures that this is not appended for the first term. Alternatively, of course, you can use "AND", depending on how you want the results returned, but you probably want "OR".
    }

    searchQueryString += "@" + i + " ";
}

searchQueryString += "UNION ";
searchQueryString += "SELECT * FROM PropertyInfo WHERE numSleeps = ";

for (int i=0; i<termCount; i++)
{
    if (i != 0)
    {
        searchQueryString += "OR numSleeps = ";
    }

    searchQueryString += "@" + i + " ";
}

searchQueryString += "UNION ";
searchQueryString += "SELECT * FROM PropertyInfo WHERE numBathrooms = ";

for (int i=0; i<termCount; i++)
{
    if (i != 0)
    {
        searchQueryString += "OR numBathrooms = ";
    }

    searchQueryString += "@" + i + " ";
}

searchQueryString += "ORDER BY anyTermYouWishToOrderBy DESC"; //You can order by whatever term you need, and, as always, can use "ASC" instead of "DESC"

现在该部分已经结束,剩下的就是确保将适当数量的参数传递给 db.Query() 方法,并且由于我们已经有了列表的数组副本,我们可以使用它.

if (searchTermsArray.Length > 0) //Prevents a server-side error if the searchTerm list was empty when converted to the searchTermsArray
{
    var queryResults = db.Query(searchQueryString, searchTermsArray);

谢天谢地,db.Query() 方法通过接受一个值数组作为第二个参数使这变得简单,它只是填充查询中的参数,就像您在第一个参数之后添加多个参数一样。

最后,简单的展示一下结果(这部分你可能已经知道了,但为了完整起见,我还是举个例子吧)

foreach (var row in queryResults)
{
    <div>Number of Bedrooms: @row.numBedrooms</div><br/>
    <div>Number of Sleeps: @row.numSleeps</div><br/>
    <div>Number of Bathrooms: @row.numBathrooms</div><br/><hr/><br/>
}
} // <-- Don't forget to close the `if (searchTermsArray.Length > 0)` branch.
} // <-- Don't forget to close the `if (!String.IsNullOrWhiteSpace(searchText))` branch.

我知道这里 100% 的逻辑可能并不完全适合您的场景,但希望能够为您提供足够的内容,以便您可以根据需要对其进行修改。另外,如果您有任何问题,我很乐意为您提供进一步的帮助。

【讨论】:

  • 嗨 voidking。感谢您提供如此详细的解释。我试图理解这一切,并有点困惑。所有这些代码都打算放在同一页面上吗?它是否打算读取我发送到该页面的查询字符串,例如 ?beds=1&sleeps=2@bathrooms=3 ?
  • @Gavin5511 是的,这一切都是为了在同一个页面上进行,是的,在我的示例中,它旨在读取我发送到该页面的查询字符串,然而 i>,这适用于一个长查询字符串,它被分成几部分。这只是我使用的示例,我完全希望您必须在您的情况下做一些事情才能将所有变量都放到这个页面上。但是,如果您能够将所有变量放入此页面的列表中,那么其中大部分都是相同的。我有一个问题:您知道需要从用户输入发送到此页面的最大变量数吗?
  • @Gavin5511 很抱歉,我的示例使用了一个被分成多个变量的查询字符串,因为我很确定这不是您的方案所需要的,但我不太确定如果您的表单只是从用户输入表单中发布(或“获取”)一些变量,或者如果您真的根本不知道您将在目标页面上收到多少变量。我的例子只是展示了一个真正可能的场景是如何发生的,涉及真正不确定的变量数量。
  • @Gavin5511 但是,如果您在页面上有一个指向该表单的表单,例如,用户可能(或可能不会)过滤的 30 个不同的“可能”变量,那么,也很容易实现。在此代码将继续执行的同一页面上,您不必将查询字符串的必要部分转换为部分,撕掉逗号和不必要的单词,而是只需读取所有 possible 变量(测试它们的空值或空值)并将非空值添加到列表中。如果您使用两种数据类型(例如 int 和 string),只需转换...
  • @Gavin5511 ...ints 到字符串并存储在同一个字符串列表中(稍后可以使用.AsInt.ToInt32 重新转换,甚至使用IsInt(stringVariable) 进行检查如果您需要确定一个值是否应该只是一个数字)。获得列表后,您几乎可以使用我在var db = Database.Open("StayInFlorida"); 下的示例。
【解决方案2】:

您有许多替代方案需要处理:

  1. 如果指定床位数但未指定睡眠次数

  2. 如果指定了睡眠次数但未指定床位数

  3. 如果两者都指定了

  4. 如果两者都没有指定

(这里有一行文字来解决此编辑器中不允许将代码直接发布在有序列表下的错误。)

var selectCommand = "SELECT * FROM PropertyInfo ";
var beds = Request["beds"].AsInt();
var sleeps = Request["sleeps"].AsInt();
IEnumerable<dynamic> selectedData = null;

if(beds > 0 && sleeps == 0) { 
    selectCommand = "SELECT * FROM PropertyInfo WHERE NumBedrooms = @0";
    selectedData = db.Query(selectCommand, beds);
}

if(sleeps > 0 && beds = 0) { 
    selectCommand = "SELECT * FROM PropertyInfo WHERE NumSleeps = @0";
    selectedData = db.Query(selectCommand, sleeps)
}

if(beds > 0 && sleeps > 0){
    selectCommand = "SELECT * FROM PropertyInfo WHERE NumBedrooms = @0 AND NumSleeps = @1";
    selectedData = db.Query(selectCommand, beds, sleeps)
}

if(beds == 0 && sleeps == 0){
    //no meaningful numbers where specified
}

【讨论】:

  • 谢谢迈克,这完全有道理。因此,如果我要将搜索条件扩展到 3 个变量(例如,浴室的数量),我是否必须添加 if 语句来检查所有 3 个变量的所有可能场景?我可能希望用户能够对许多事情进行高级搜索,所以我可以看到它很快变得难以管理?这是所有搜索网站都使用的相同逻辑吗?
  • 嘿,我终于在你这里的回答中随机找到了一个问题的答案,我已经有一段时间了 Mike :) 现在我知道使用 var 关键字创建的列表分配 SQL对变量的查询结果是 IEnumerable&lt;dynamic&gt; 数据类型 :) 谢谢,Mike!
  • @Gavin5511 如果“我可能希望用户能够对许多事情进行高级搜索”,您的意思是在运行时之前您不知道有多少变量,那么您可能会有创建一个存储所有变量的列表,然后你可以用它做各种各样的事情,因为你总是可以用List.Count 来确定你有多少项目。您甚至可以(我自己刚刚完成此操作)在循环中创建 SQL 查询字符串,以生成所需的占位符数量 (i),将列表转换为数组,并使用它来填充参数。
  • 这听起来正是我需要做的。您是否有一些示例代码的链接,以便我可以开始构建自己的列表并创建这些 SQL 循环?这是输入,它非常有帮助
  • @Gavin5511 我现在正在为您提供一个额外的答案,它可能有点臃肿,但您会看到其中的逻辑,并希望能够将其应用于您的情况。
猜你喜欢
  • 2013-08-31
  • 2012-03-24
  • 2013-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多