基于 C# 列表而不是过滤器 table 的过滤器 sql

Filter sql based on C# List instead of a filter table

假设我有一个包含以下数据的 table:

现在我想按主键部门和编号进行过滤。我有一个必须在代码中过滤的部门和数字组合列表。 在我看来,我会创建一个导致以下结果的连接:

select * from employee e
inner join dynamicTable dyn on e.Department = dyn.Department 
                           and e.Number = dyn.Number;

dynamicTable 是我在 C# 代码中的 List,它具有要过​​滤的主键,但我不知道如何将此列表传递到数据库级别。

我不想从我的员工那里加载所有内容 table 并且不想通过 linq 或其他方式在代码中进行过滤,因为我的数据库中有数百万名员工。

我已经考虑过合并 primary_keys 并创建一个 where in (...),但是 firebird 限制在 where in 中最多 1500 条记录。

使用的数据库是 Firebird 2.1 版

就我个人而言,我认为您可以采用两种技巧。还有一个 "blast from the past"。

路由 #1。 使用 GTT:全局临时 TABLE

GTT 是在 FB 2.1 中引入的(您正在使用它)并且可以针对每个连接或每个事务。你会想要每笔交易的。这种差异与数据(行)有关,模式(结构和索引,元数据)是持久的。请参阅 GTT 文档中的 ON COMMIT DELETE ROWS 选项。

等等。

这样,您打开事务,用列表中的数据填充 GTT(将这 1500 个值对数据从您的工作站复制到服务器),您 运行 您的查询 JOINing通过该 GTT,然后您 COMMIT 您的交易和 table 内容被自动删除。

如果您可以 运行 在会话中进行许多几乎相似的查询,那么为每个连接创建 GTT 并根据需要修改数据而不是重新填充数据可能是有意义的对于每个下一个事务中的每个下一个查询,但这是一种更复杂的方法。 Cleanse-early on every COMMIT 是我更喜欢的默认方法,直到争论为什么在这种特定情况下每个连接会更好。只是不要在查询之间将垃圾保留在服务器上。

路线 #2。 使用字符串搜索 - 反向 LIKE 匹配。

在其基本形式中,此方法适用于搜索一些庞大且任意的整数列表。你的情况有点复杂,你匹配一对数字,而不是单个数字。

简单的想法就是这样,假设我们要获取 ID 列可以是 1、4、12、24 的行。 直接的方法是对每个值进行 4 次查询,或者进行 WHERE ID = 1 or ID = 4 or ... 或使用 WHERE id IN (1,4,12,24)。在内部,IN 将展开到那个非常 = or = or = 中,然后很可能作为四个查询执行。对于长列表不是很有效。

因此 - 对于要匹配的非常长的列表 - 我们可能会形成一个特殊的字符串。并将其作为文本进行匹配。这使得匹配本身的效率大大降低,并禁止使用任何索引,服务器 运行 对整个 table 进行自然扫描 - 但它进行一次扫描。当匹配列表非常大时,一次通过所有 table 扫描比数千次按索引获取更有效。 但是 - 仅当列表与 table 的比率非常大时,取决于您的具体数据。

我们让文本包含我们所有的目标值,穿插在一个分隔符中:“~1~4~12~24~”。现在我们把我们的ID列做成同样的定界符-数字-定界符字符串,看看能不能找到这样的子串。

LIKE/CONTAINING 的通常用法是将列与如下数据匹配:SELECT * from the_table WHERE column_name CONTAINING value_param
我们反过来,SELECT * from the_table WHERE value_param CONTAINING column_name-based-expression

  SELECT * from the_table WHERE '~1~4~12~24~' CONTAINING '~' || ID || '~' 

这假设 ID 会自动从整数转换为字符串。如果不是,你将不得不手动完成:.... CONTAINING '~' || CAST( ID as VARCHAR(100) ) || '~'

你的情况有点复杂,你需要匹配两个数字,Department 和 Number,所以如果你按照这种方式,你将不得不使用两个不同的分隔符。像

SELECT * FROM employee e WHERE
  '~1@10~1@11~2@20~3@7~3@66~' CONTAINING
  '~' || e.Department || '@' || e.Number || '~'

明白了:你说你的目标列表是 1500 个元素。目标线会……很长。 具体多长时间???

Firebird 中的 VARCHAR 限制为 32KB AFAIR,较长的文本应作为文本 BLOB,功能会有所降低。 LIKE 是否适用于 FB2.1 中的 BLOB?我不记得了,检查发行说明。还要检查您的库是否甚至允许您将参数类型指定为 BLOB 而不是字符串。 现在,您的连接字符集是什么?如果它类似于 Windows-1250 或 Windows-1251 - 那么一个字符就是一个字节,您可以将 32K 个字符放入 32KBytes。但是如果您的应用程序设置的 CONNECTION CHARSET 是 UTF-8 - 那么每个字母占用 4 个字节并且您的最大 VARCHARable 字符串将减少到 8K 个字母。

您可以尝试避免为这个长字符串使用参数,并将目标字符串常量内联到 SQL 语句中。但是你可能会达到最大 SQL 语句长度的限制。

另请参阅:c:\Program Files\Firebird\Firebird_2_1\doc\README.monitoring_tables.txt 中的 MON$CHARACTER_SET_ID,然后是 SYSTEM TABLES 部分FB 文档如何将 ID 映射到字符集文本名称。

路线 #3 穷人的 GTT。输入伪tables.

这个技巧有时可以在引入 GTT 之前的旧 IB/FB 版本中使用。

亲:您不需要更改您的持久性 SCHEMA。
缺点:不更改 SCHEME - 您不能创建索引,也不能使用索引连接。再一次,您可以达到单个 SQL 语句的长度限制。

真的,不要认为这适用于你的情况,只是为了使答案完整我认为这个技巧也应该提到。

select * from employee e, (
  SELECT 1 as Department, 10 as Number FROM RDB$DATABASE
  UNION ALL SELECT 1, 11 FROM RDB$DATABASE
  UNION ALL SELECT 2, 20 FROM RDB$DATABASE
  UNION ALL SELECT 3, 7 FROM RDB$DATABASE
  UNION ALL SELECT 3, 66 FROM RDB$DATABASE
) t, 
where e.Department = t.Department 
  and e.Number = t.Number

粗鲁丑陋,但有时这种伪table可能会有所帮助。什么时候?大多数情况下,它有助于从 SELECT 批量插入,其中不需要索引:-D 它很少适用于 SELECTs - 但只要知道诀窍即可。