使用 SQL 到 select 记录,来自多个位字段的单个 "true" 位字段

Using SQL to select records with a single "true" bit field from several bit fields

如果我有这样的table:

CREATE TABLE `Suppression` (
  `SuppressionId` int(11) NOT NULL AUTO_INCREMENT,
  `Address` varchar(255) DEFAULT NULL,
  `BooleanOne` bit(1) NOT NULL DEFAULT '0',
  `BooleanTwo` bit(1) NOT NULL DEFAULT '0',
  `BooleanThree` bit(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`SuppressionId`),
)

有没有一种基于集合的方式,我可以 select 所有恰好具有三个位字段之一的记录 = 1 而无需写出字段名称

例如给定:

1 10 Pretend Street 1 1 1 
2 11 Pretend Street 0 0 0 
3 12 Pretend Street 1 1 0 
4 13 Pretend Street 0 1 0 
5 14 Pretend Street 1 0 1 
6 14 Pretend Street 1 0 0

我想return记录4和6。

你可以"add them up":

where cast(booleanone as unsigned) + cast(booleantwo as unsigned) + cast(booleanthree as unsigned) = 1

或者,使用元组:

where ( (booleanone, booleantwo, booleanthree) ) in ( (0b1, 0b0, 0b0), (0b0, 0b1, 0b0), (0b0, 0b0, 0b1) )

我不确定你说的 "set-based" 是什么意思。

如果您的布尔值数量会随时间变化并且您不想更新代码,我建议您将它们设为行而不是列。 例如:

CREATE TABLE `Suppression` (
  `SuppressionId` int(11) NOT NULL AUTO_INCREMENT,
  `Address` varchar(255) DEFAULT NULL,
  `BooleanId` int(11) NOT NULL,
  `BooleanValue` bit(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`SuppressionId`,`BooleanId`),
)

因此,通过 1 个查询和 'group by',您可以检查布尔值的所有值,无论它们有多少。当然,这会使您的 table 变大。

编辑:刚想出另一个主意:为什么不添加一个 checksum 列,其值将是所有位的总和?所以你会在每次写入你的 table 时更新它,并在你的 select

中检查这个

如果你

  1. 必须使用这种非规范化的方式来表示这些标志,而您
  2. 必须 能够在生产环境中向您的 table 添加新的标志列,并且您
  3. 无法 在添加列时手动重写查询,

然后你必须弄清楚如何编写程序来编写你的查询。

您可以使用此查询检索布尔值列的结果集,然后您可以在程序中使用该结果集来编写涉及所有这些列的查询。

SELECT COLUMN_NAME
  FROM INFORMATION_SCHEMA.COLUMNS
 WHERE TABLE_SCHEMA = DATABASE()
   AND TABLE_NAME = 'Suppression'
   AND COLUMN_NAME LIKE 'Boolean%'
   AND DATA_TYPE = 'bit'
   AND NUMERIC_PRECISION=1

不幸的是,当您添加列时,您在此处提出的方法的效果会呈指数级下降。每当软件工程师说 "exponential" 时,就该 运行 离开尖叫了。说真的。

一种 更具可扩展性的方法是在您的Suppression 行和标志之间建立一对多关系。添加此 table.

CREATE TABLE SuppressionFlags (
   SuppressionId int(11) NOT NULL,
   FlagName varchar(31) NOT NULL,
   Value bit(1) NOT NULL DEFAULT '0',
   PRIMARY KEY (SuppressionID, FlagName)
)

然后,当您想要插入带有一些标志变量的行时,执行以下查询序列。

 INSERT INTO Suppression (Address) VALUES ('some address');
 SET @SuppressionId := LAST_INSERT_ID();
 INSERT INTO SuppressionFlags (SuppressionId,   FlagName,    Value)
                       VALUES (@SuppressionId, 'BooleanOne',   1);
 INSERT INTO SuppressionFlags (SuppressionId,   FlagName,    Value)
                       VALUES (@SuppressionId, 'BooleanTwo',   0);
 INSERT INTO SuppressionFlags (SuppressionId,   FlagName,    Value)
                       VALUES (@SuppressionId, 'BooleanThree', 0);

这为您提供了一个 Suppression 行,其中在 SuppressionFlags table 中设置了三个标志。注意使用 @SuppressionId 在第二个 table.

中设置 Id

然后要查找仅设置了一个标志的所有行,请执行此操作。

 SELECT Suppression.SuppressionId, Suppression.Address
   FROM Suppression
   JOIN SuppressionFlags ON Suppression.SuppressionId = SuppressionFlags.SuppressionId
  GROUP BY Suppression.SuppressionId, Suppression.Address
 HAVING SUM(SuppressionFlags.Value) = 1 

如果你想要更精细的组合,它会变得有点棘手。例如,如果您希望所有行都设置了 BooleanOneBooleanTwoBooleanThree,则需要执行类似这样的操作。

SELECT S.SuppressionId, S.Address
  FROM Suppression S
  JOIN SuppressionFlags A ON S.SuppressionId=A.SuppressionId AND A.FlagName='BooleanOne'
  JOIN SuppressionFlags B ON S.SuppressionId=B.SuppressionId AND B.FlagName='BooleanTwo'
  JOIN SuppressionFlags C ON S.SuppressionId=C.SuppressionId AND C.FlagName='BooleanThree'
 WHERE A.Value = 1 AND (B.Value = 1 OR C.Value = 1)

这种通用的数据库模式称为属性/值模式。因为 SQL 不容易让您使用变量作为列名(它实际上没有反射),这种命名属性的方式是实现可扩展性的最佳途径。

多了一点SQL。但是您可以在生产中根据需要添加任意数量的新标志,而无需重写查询或获得标志匹配的组合爆炸。 SQL 就是为处理这种查询而构建的。