1

I have a table EmployeeMoves:

| EmployeeID         | CityIDs    
+------------------------------
| 24                 | 23,21,22 
| 25                 | 25,12,14 
| 29                 | 1,2,5 
| 31                 | 7 
| 55                 | 11,34 
| 60                 | 7,9,21,23,30 

I'm trying to figure out how to expand the comma-delimited values from the EmployeeMoves.CityIDs column to populate an EmployeeCities table, which should look like this:

| EmployeeID         | CityID    
+------------------------------
| 24                 | 23 
| 24                 | 21 
| 24                 | 22 
| 25                 | 25 
| 25                 | 12 
| 25                 | 14 
| ... and so on

I already have a function called SplitADelimitedList that splits a comma-delimited list of integers into a rowset. It takes the delimited list as a parameter. The SQL below will give me a table with split values under the column Value:

select value from dbo.SplitADelimitedList ('23,21,1,4');

| Value   
+----------- 
| 23          
| 21        
| 1       
| 4  

The question is: How do I populate EmployeeCities from EmployeeMoves with a single (even if complex) SQL statement using the comma-delimited list of CityIDs from each row in the EmployeeMoves table, but without any cursors or looping in T-SQL? I could have 100 records in the EmployeeMoves table for 100 different employees.

4

2 回答 2

3

This is how I tried to solve this problem. It seems to work and is very quick in performance.

INSERT INTO EmployeeCities
SELECT
    em.EmployeeID,
    c.Value
FROM EmployeeMoves em
CROSS APPLY dbo.SplitADelimitedList(em.CityIDs) c;

UPDATE 1:

This update provides the definition of the user-defined function dbo.SplitADelimitedList. This function is used in above query to split a comma-delimited list to table of integer values.

  CREATE FUNCTION dbo.fn_SplitADelimitedList1
   (
      @String NVARCHAR(MAX)
   )
  RETURNS @SplittedValues TABLE(
   Value INT
  )
 AS
 BEGIN
  DECLARE @SplitLength INT
  DECLARE @Delimiter VARCHAR(10) 
  SET @Delimiter = ',' --set this to the delimiter you are using

  WHILE len(@String) > 0
   BEGIN
    SELECT @SplitLength = (CASE charindex(@Delimiter, @String)
         WHEN 0 THEN
           datalength(@String) / 2
         ELSE
           charindex(@Delimiter, @String) - 1
       END)

   INSERT INTO @SplittedValues
   SELECT cast(substring(@String, 1, @SplitLength) AS INTEGER)
   WHERE
   ltrim(rtrim(isnull(substring(@String, 1, @SplitLength), ''))) <> '';

   SELECT @String = (CASE ((datalength(@String) / 2) - @SplitLength)
         WHEN 0 THEN
           ''
         ELSE
           right(@String, (datalength(@String) / 2) - @SplitLength - 1)
       END)

  END

 RETURN

END
于 2013-06-11T23:38:49.897 回答
-3

Preface

This is not the right way to do it. You shouldn't create comma-delimited lists in SQL Server. This violates first normal form, which should sound like an unbelievably vile expletive to you.

It is trivial for a client-side application to select rows of employees and related cities and display this as a comma-separated list. It shouldn't be done in the database. Please do everything you can to avoid this kind of construction in the future. If at all possible, you should refactor your database.

The Right Answer

To get the list of cities, properly expanded, from a table containing lists of cities, you can do this:

INSERT dbo.EmployeeCities
SELECT
    M.EmployeeID,
    C.CityID
FROM
   EmployeeMoves M
   CROSS APPLY dbo.SplitADelimitedList(M.CityIDs) C
;

The Wrong Answer

I wrote this answer due to a misunderstanding of what you wanted: I thought you were trying to query against properly-stored data to produce a list of comma-separated CityIDs. But I realize now you wanted the reverse: to query the list of cities using existing comma-separated values already stored in a column.

WITH EmployeeData AS (
   SELECT
      M.EmployeeID,
      M.CityID
   FROM
      dbo.SplitADelimitedList ('23,21,1,4') C
      INNER JOIN dbo.EmployeeMoves M
         ON Convert(int, C.Value) = M.CityID
)
SELECT
   E.EmployeeID,
   CityIDs = Substring((
      SELECT ',' + Convert(varchar(max), CityID)
      FROM EmployeeData C
      WHERE E.EmployeeID = C.EmployeeID
      FOR XML PATH (''), TYPE
   ).value('.[1]', 'varchar(max)'), 2, 2147483647)
FROM
   (SELECT DISTINCT EmployeeID FROM EmployeeData) E
;

Part of my difficulty in understanding is that your question is a bit disorganized. Next time, please clearly label your example data and show what you have, and what you're trying to work toward. Since you put the data for EmployeeCities last, it looked like it was what you were trying to achieve. It's not a good use of people's time when questions are not laid out well.

于 2013-06-11T21:58:42.293 回答