将位字段表转换为表

时间:2012-05-29 17:26:06

标签: sql-server sql-server-2005

我有一个表格,显示每个位置使用的每个工具的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

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

1 个答案:

答案 0 :(得分: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;

*的 BUT *

将这些存储为单独的列是灾难的一个方法。因此,当您添加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

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

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

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

没有冗余数据,没有无法管理列的宽表以及正确的索引甚至可以帮助您有效地使用Tool3搜索所有位置......