0

假设我有一张包含以下数据的表格:

在此处输入图像描述

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

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

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

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

我已经考虑过组合 primary_keys 并创建一个where in (...),但 firebird 限制在一个where in.

使用的数据库是 Firebird 2.1 版

4

1 回答 1

4

就个人而言,我可以看到您可以追求的两个技巧。还有一个“过去的爆炸”。

路线#1。 使用 GTT:全局临时表

GTTs 是在 FB 2.1 中引入的(你使用它),可以是每个连接或每个事务。你会想要每笔交易一个。这种差异在于数据(行)、模式(结构和索引、元数据)是持久的。请参阅ON COMMIT DELETE ROWSGTT 文档中的选项。

等等。

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

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

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

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

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

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

我们让文本列出了我们所有的目标值,穿插着 AND 包装成一个分隔符:“~1~4~12~24~”。现在我们为我们的 ID 列制作相同的 delimiter-number-delimiter 字符串,看看是否可以找到这样的子字符串。

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) ) || '~'

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

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

Gotcha:你说你的目标列表是 1500 个元素。目标线将是……长。具体多久???

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

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

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

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

这个技巧有时可以在引入 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

粗糙而丑陋,但有时这个伪表可能会有所帮助。什么时候?大多数情况下,它有助于批量 INSERT-from-SELECT,其中不需要索引 :-D 它很少适用于 SELECT - 但只知道诀窍。

于 2017-05-16T09:40:39.023 回答