您好,我需要通过 dapper 在类似于下面的伪代码的结构上实现一些查询。
// Tags
[{id: 1, name: "Tech"}, {id: 2, name: "SQL"}, {id: 3, name: "C#"}]
// BlogPost [
{
Id: 1
Tags: [1, 2] // Tech, Sql
},
{
Id: 2,
Tags: [1,3] // Tech, C#
},
{
Id: 3,
Tags: [1,2,3] // Text, Sql, C#
}]
鉴于此查询
SELECT
[Blogpost].*
From BlogPost blogPost
LEFT JOIN BlogPostTags tags ON tags.blogId = blogPost.Id
WHERE blogpost.Tags IN (1,2)
运行上面的查询我希望得到这个结果。 [{博客ID:1},{博客ID:2},{博客ID:3}]
在上面的查询中给定相同的参数(1,2),我需要获得类似于 [{blogId: 1}] 的结果。
我还需要根据上面查询中的参数 (1,2) 获得像 post [{blogId: 1}, {BlogId: 3}] 这样的结果。
sql(MSSQL)有什么好方法来获得这些结果吗?
或者我怎样才能以高性能的方式获得这些结果,因为我在实际查询中会有更多的连接?
谢谢!
这些类型的查询在 SQL 中表达起来非常尴尬(注意:我会避免在这里使用
LEFT OUTER JOIN
,因为这可能会导致行重复)
“包含任何”是可以的(通常通过
EXISTS (SELECT 1 FROM BlogPostTags tags WHERE tags.blogId = blogPost.Id AND tags.Tags IN @tags)
,使用 Dapper 的自动扩展 @tags
) - 但是,您要求的是“包含所有”和“仅包含所有”;这些要复杂得多; “包含全部”通常是多个 EXISTS
操作,而“仅包含全部”可以是相同的“包含全部”,并附加一个 NOT EXISTS (SELECT 1 FROM BlogPostTags tags WHERE tags.blogId = blogPost.Id AND tags.Tags NOT IN @tags)
但是!我再次强调:这个操作在 SQL 中并不理想。对于 Stack Overflow 帖子标记(看起来与此基本相同),我们通过添加一个单独的索引/服务器(纯粹为了执行标记操作)而存在,包括将所有帖子/标记数据预加载到 RAM 中,从而彻底改变了这一点以优化的方式,并按标签建立索引(处理增量更新);这允许使用各种技巧在内存中执行大量操作。比简单的 SQL 查询要付出更多的努力和工作,而且效率也更高。 “包含任何”只是一个简单的
EXISTS
var tagIds = new[]{ 1, 2, };
using connection = GetConnection();
const string query = @"
SELECT
bp.*
FROM BlogPost bp
WHERE EXISTS (SELECT 1
FROM BlogPostTags tags
WHERE tags.blogId = bp.Id
AND tags.Id IN @tags
);
var results = await connection.QueryAsync<BlogPost>(query, new { tags, });
关系除法
,一个是With Remainder,另一个是Without Remainder。
对于 With Remainder,只需执行 EXISTS
连接,并进行分组
HAVING
检查它们是否全部存在。
var tagIds = new[]{ 1, 2, };
using connection = GetConnection();
const string query = @"
SELECT
bp.*
FROM BlogPost bp
WHERE EXISTS (SELECT 1
FROM BlogPostTags tags
WHERE tags.blogId = bp.Id
AND tags.Id IN @tags
HAVING COUNT(*) = @count
);
var results = await connection.QueryAsync<BlogPost>(query, new { tags, count = tags.Length });
对于 Without Remainder,它是类似的,但是您需要查找 all标签,然后检查
HAVING
是否匹配的 only 就是您要查找的标签。
var tagIds = new[]{ 1, 2, };
using connection = GetConnection();
const string query = @"
SELECT
bp.*
FROM BlogPost bp
WHERE EXISTS (SELECT 1
FROM BlogPostTags tags
WHERE tags.blogId = bp.Id
HAVING COUNT(*) = @count
AND COUNT(*) = COUNT(CASE WHEN tags.Id IN @tags THEN 1 END)
);
var results = await connection.QueryAsync<BlogPost>(query, new { tags, count = tags.Length });
请注意,Dapper 自动将列表参数化为 (@p1, @p2)
等。
您还可以使用表值参数,这对于大型列表更有效。定义一个表类型,最好保留一些有用的标准类型。
CREATE TYPE dbo.IntList AS TABLE (value int PRIMARY KEY);
然后将值放入
DataTable
并使用
.AsTableValuedParameter
。
查询也略有不同,因为您将在此处使用联接。var tagIds = new[]{ 1, 2, };
var table = new DataTable { Columns = { { "value", typeof(int) } } };
foreach (var tag in tagIds)
table.Add(tag);
using connection = GetConnection();
const string query = @"
SELECT
bp.*
FROM BlogPost bp
WHERE EXISTS (SELECT 1
FROM BlogPostTags tags
LEFT JOIN @tags input ON input.value = tags.Id
WHERE tags.blogId = bp.Id
HAVING COUNT(*) = @count
AND COUNT(*) = COUNT(input.Tag)
);
var results = await connection.QueryAsync<BlogPost>(query, new { tags = tags.AsTableValuedParameter("dbo.IntList"), count = tags.Length });