0

我有一个表格,显示位置,每个位置使用的每个工具都有一个 BIT 列:

CREATE TABLE dbo.[ToolsSelected] (
    [LocationID] NVARCHAR(40) NOT NULL,
    [Tool1] INTEGER DEFAULT 0 NOT NULL,
    [Tool2] INTEGER DEFAULT 0 NOT NULL,
    [Tool3] INTEGER DEFAULT 0 NOT NULL,
    [Tool4] INTEGER DEFAULT 0 NOT NULL,
    PRIMARY KEY ([LocationID])
);

LocID  Tool1  Tool2  Tool3  Tool4
-----  -----  -----  -----  -----
AZ     0      1      1      0
NY     1      0      1      1

我需要通过 LocationID 将其转换为表格,指示哪些工具在哪些位置:

CREATE TABLE dbo.[ByLocation] (
    [LocationID] NVARCHAR(40) NOT NULL,
    [Tool] NVARCHAR(40) NOT NULL,  -- Column title of ToolsSelected table
    PRIMARY KEY ([LocationID],  [Tool])
);

LocID  Tool
-----  -----
AZ     Tool2
AZ     Tool3
NY     Tool1
NY     Tool3
NY     Tool4

这个想法是每个位置都可以选择他们需要的工具,然后我需要查询工具表以获取每个所选工具的详细信息(版本等)。每个位置都是独一无二的;每个工具都是独一无二的。有没有办法做到这一点或更好的实施?

4

1 回答 1

4

这是直接问题的答案,仅给出 4 个工具列:

SELECT LocID = LocationID, Tool
FROM
(
    SELECT LocationID, Tool = 'Tool1' FROM dbo.ToolsSelected WHERE Tool1 = 1
    UNION ALL
    SELECT LocationID, Tool = 'Tool2' FROM dbo.ToolsSelected WHERE Tool2 = 1
    UNION ALL
    SELECT LocationID, Tool = 'Tool3' FROM dbo.ToolsSelected WHERE Tool3 = 1
    UNION ALL
    SELECT LocationID, Tool = 'Tool4' FROM dbo.ToolsSelected WHERE Tool4 = 1
) AS x
ORDER BY LocID, Tool;

使用 40 列,您可以做同样的事情,但需要动态生成:

DECLARE @sql NVARCHAR(MAX);

SET @sql = N'';

SELECT @sql += ' 
    UNION ALL 
    SELECT LocationID, Tool = ''' + name + '''
    FROM dbo.ToolsSelected WHERE ' + name + ' = 1'
  FROM sys.columns WHERE [object_id] = OBJECT_ID('dbo.ToolsSelected')
  AND name LIKE 'Tool[0-9]%';

SELECT @sql = N'SELECT LocID = LocationID, Tool
    FROM
    (' + STUFF(@sql, 1, 17, '') + '
    ) AS x ORDER BY LocID, Tool;';

PRINT @sql;
-- EXEC sp_executesql @sql;

*但是*

将这些存储为单独的列是灾难的根源。因此,当您添加 Tool41、Tool42 等时,您必须更改架构,然后更改通过参数等传递列名和 1/0 的所有代码。为什么不将这些表示为简单的数字,例如

CREATE TABLE dbo.LocationTools
(
  LocID  NVARCHAR(40),
  ToolID INT
);

因此,在上述情况下,您将存储:

LocID  Tool
-----  ----
AZ     2
AZ     3
NY     1
NY     3
NY     4

现在,当您传入他们选择的复选框时,大概从前端您会收到两个值,例如:

LocID: "NY"
Tools: "Tool1, Tool5, Tool26"

如果这是正确的,那么您可以在用户创建或更改他们的选择时填充表格,首先使用拆分函数来分解由复选框指定的逗号分隔列表:

CREATE FUNCTION dbo.SplitTools
(
  @ToolList NVARCHAR(MAX)
)
RETURNS TABLE
WITH SCHEMABINDING
AS
   RETURN 
   (  
      SELECT ToolID = y.i.value('(./text())[1]', 'int')
      FROM 
      ( 
        SELECT x = CONVERT(XML, 
          '<i>' + REPLACE(REPLACE(@List, ',', '</i><i>'), 'Tool', '') 
          + '</i>').query('.')
      ) AS a CROSS APPLY x.nodes('i') AS y(i)
   );
GO

(您忘记告诉我们您使用的是哪个版本的 SQL Server - 如果 2008 或更高版本,您可以使用表值参数作为拆分函数的替代方法。)

然后是处理它的过程:

CREATE PROCEDURE dbo.UpdateLocationTools
  @LocID NVARCHAR(40),
  @Tools NVARCHAR(MAX)
AS
BEGIN
  SET NOCOUNT ON;

  -- in case they had previously selected tools
  -- that are no longer selected, clear first:

  DELETE dbo.LocationTools WHERE LocID = @LocID;

  INSERT dbo.LocationTools(LocID, ToolID)
    SELECT @LocID, ToolID
    FROM dbo.SplitTools(@Tools);
END
GO

现在您可以在不更改架构或代码的情况下添加新工具#s,因为您的复选框列表也可以从您的数据中生成 - 假设您有一个dbo.Tools表或想要添加一个表。该表还可用于数据完整性目的(您可以在 上放置外键dbo.LocationTools.ToolID)。

您可以非常简单地生成所需的查询:

SELECT LocID, Tool = 'Tool' + CONVERT(VARCHAR(12), ToolID)
  FROM dbo.LocationTools
  ORDER BY LocID, ToolID;

没有冗余数据,没有带有难以管理的列的宽表,并且适当的索引甚至可以帮助您有效地使用 Tool3 搜索所有位置......

于 2012-05-29T17:50:08.563 回答