COLDFUSION: как сгруппировать по первому столбцу и преобразовать второй столбец в три отдельных столбца

Это пример необработанных данных, с которыми я работаю, из таблицы «employeeRatings» до вывода cfquery:

        (showcasing employeeID:1128 for the month of May)

employeeID  |   Possible_Factor  |   Factor   |  ratingDate
=======================================================================
 1128       |        .1          |  .1        | 5/25/2013 2:05:13 PM 
 1128       |        .1          |  .0        | 5/22/2013 9:30:43 AM 
 1128       |        .2          |  .1        | 5/17/2013 9:42:09 AM 
 1128       |        .1          |  .1        | 5/13/2013 8:07:15 AM 
 1128       |        .1          |  .0        | 5/10/2013 7:52:51 AM 
 1128       |        .4          |  .0        | 5/6/2013 12:41:12 PM 

Вот cfquery (оператор SQL):

SELECT ROUND(100 * (SUM(Factor) / SUM(Possible_Factor)), 2) AS employeeRating, CONVERT(CHAR(4), ratingDate, 100) + CONVERT(CHAR(4), ratingDate, 120) AS month, employeeID, DATEADD(MONTH, DATEDIFF(MONTH, 0, ratingDate), 0) AS shortdate 
FROM employeeRatings
GROUP BY CONVERT(CHAR(4), ratingDate, 100) + CONVERT(CHAR(4), ratingDate, 120), DATEADD(MONTH, DATEDIFF(MONTH, 0, ratingDate), 0), employeeID 
ORDER BY employeeID, DATEADD(MONTH, DATEDIFF(MONTH, 0, ratingDate), 0) DESC

После cfquery вывод будет выглядеть так:

employeeID  |   employeeRating   |   month      |  shortdate
=======================================================================
 1128       |        30          |  May 2013    | 5/1/2013 12:00:00 AM 
 1128       |        60          |  April 2013  | 4/1/2013 12:00:00 AM
 1128       |        90          |  Jan 2013    | 1/1/2013 12:00:00 AM
 7310000    |        95          |  April 2013  | 4/1/2013 12:00:00 AM
 7310000    |        85          |  Mar 2013    | 3/1/2013 12:00:00 AM
 7310000    |        75          |  Feb 2013    | 2/1/2013 12:00:00 AM
 7310000    |        55          |  Jan 2013    | 1/1/2013 12:00:00 AM
 444981     |        27          |  Mar 2013    | 3/1/2013 12:00:00 AM
 444981     |        77          |  Jan 2013    | 1/1/2013 12:00:00 AM
 444981     |        97          |  Nov 2012    | 11/1/2012 12:00:00 AM
 444981     |        37          |  Sept 2012   | 9/1/2012 12:00:00 AM
 444981     |        47          |  Aug 2012    | 8/1/2012 12:00:00 AM

Мне нужно взять сотрудника и перечислить его ПОСЛЕДНИЕ ТРИ рейтинга (если месяц нулевой, пропустить нулевой месяц и получить следующий месяц с рейтингом, чтобы продемонстрировать последние три задокументированных рейтинга). Это динамический запрос, в котором перечислены более 200 сотрудников. Ниже приведен желаемый результат:

supplierID  |   LastRating   |   SecondLastRating  |   ThirdLastRating
======================================================================
 1128       |        30      |       60            |         90
 7310000    |        95      |       85            |         75
 444981     |        27      |       77            |         97

Я использую ColdFusion на SQL Server 2000 (совместимость 80), однако версия ColdFusion, которую я использую, не поддерживает атрибут группы cfloop. Я хотел бы взять новый вывод и поместить его в новый запрос, чтобы его можно было СОЕДИНИТЬ с другим запросом. Решение = подарок Starbucks от FB ;) Спасибо всем за ваше время и внимание!!!!


person Enchauva    schedule 05.06.2013    source источник
comment
Какую СУБД вы используете?   -  person Miguel-F    schedule 05.06.2013
comment
Вместо сгруппированного cfloop используйте сгруппированный cfoutput, действительный во всех версиях CF.   -  person duncan    schedule 05.06.2013
comment
Можно ли изменить исходный запрос к базе данных? Если это так, обновите теги вопроса, указав тип/версию вашей базы данных.   -  person Leigh    schedule 05.06.2013
comment
Всем привет! Во-первых, спасибо всем, что нашли время, чтобы рассмотреть мой вопрос.   -  person Enchauva    schedule 05.06.2013
comment
@ Мигель-Ф, я использую MS SQL Server.   -  person Enchauva    schedule 05.06.2013
comment
@duncan, это работает! В настоящее время я использую решение Джеймса Молера, в котором используется этот атрибут. :) Спасибо!   -  person Enchauva    schedule 05.06.2013
comment
@Leigh, изменение исходного запроса к БД не вариант. В настоящее время я получаю эти данные из базы данных, над которыми я не контролирую. :(   -  person Enchauva    schedule 05.06.2013
comment
@Enchauva - Нет, я имел в виду, есть ли у вас контроль над кодом cfquery (а не над таблицей базы данных). Если вы можете изменить cfquery, это можно сделать и в SQL. Если нет, вам придется использовать код CF, как предлагали другие.   -  person Leigh    schedule 05.06.2013
comment
@Leigh - О, моя ошибка. Да, у меня есть контроль над cfquery. Я хотел бы видеть, какой дорогой вы ведете с этим :)   -  person Enchauva    schedule 05.06.2013
comment
@Enchauva - Хорошо. Я не могу сделать это позже, но я посмотрю, смогу ли я смоделировать пример.   -  person Leigh    schedule 05.06.2013
comment
@Ли - Круто!!! Нет проблем... Я очень признателен за различные методы и подходы. Жду вашего примера!!!! :D   -  person Enchauva    schedule 05.06.2013
comment
Что такое исходный cfquery, который подтягивает этот список? И как выглядит та таблица, из которой вы берете? Я согласен с Ли. Это должно быть правильно отсортировано в запросе, а не в коде. В зависимости от того, как структурирован исходный запрос, вы можете получить неожиданные возвращаемые данные. Если вы можете сделать PIVOT в своей базе данных, это будет правильный путь. Если нет, вам придется поворачивать вручную. Какую версию SQL Server вы используете?   -  person Shawn    schedule 06.06.2013
comment
Что касается версии SQL Server: я полагаю, что это SQL Server 2000, поскольку уровень совместимости равен 80.   -  person Enchauva    schedule 06.06.2013
comment
@Leigh - Полностью согласен! Спасибо за предложение... готово :)   -  person Enchauva    schedule 06.06.2013


Ответы (3)


SQL Server 2005+

Другим вариантом является использование PIVOT SQL Server оператор

Сначала используйте ROW_NUMBER(), чтобы ранжировать записи по сотруднику и дате. (Примечание. Если ваша таблица не содержит фактического столбца даты и времени, вы можете заменить столбец идентификаторов или преобразовать «месяц» в datetime, используя convert()).

    SELECT  employeeID
            , employeeRating
            , ROW_NUMBER() OVER ( 
                    PARTITION BY employeeID 
                    ORDER BY employeeID, theRatingDateCol DESC
            ) AS Row
    FROM   yourTable
    ...

Результаты:

employeeID  employeeRating Row
----------- -------------- --------------------
1128        30             1
1128        60             2
1128        90             3
444981      27             1
444981      77             2
444981      97             3
444981      37             4
7310000     95             1
7310000     85             2
7310000     75             3
7310000     55             4

Затем PIVOT результаты первых трех (3) строк:

    ... 
    PIVOT
    (       
            MIN(employeeRating)
            FOR Row IN ( [1],[2],[3]) 
    )

Полный запрос:

SELECT pvt.employeeID
        , pvt.[1] AS LastRating  
        , pvt.[2] AS SecondLastRating  
        , pvt.[3] AS ThirdLastRating  
FROM (
        --- order by employee and rating date (descending)
        SELECT  employeeID
                , employeeRating
                , ROW_NUMBER() OVER ( 
                    PARTITION BY employeeID 
                    ORDER BY employeeID, theRatingDateCol DESC
                ) AS Row
        FROM   yourTable
    ) data
    PIVOT
    (   -- take top 3 values
        MIN(employeeRating)
        FOR Row IN ( [1],[2],[3]) 
    ) pvt

Результаты:

employeeID  LastRating  SecondLastRating ThirdLastRating
----------- ----------- ---------------- ---------------
1128        30          60               90
444981      27          77               97
7310000     95          85               75

SQL Server 2000

К сожалению, SQL Server 2000 и более ранние версии не поддерживают ни одну из этих функций. Хотя это не так гладко, как PIVOT, вы все же можете смоделировать его с помощью подзапроса и CASE.

Во-первых, используйте подзапрос вместо ROW_NUMBER(). По сути, вы count количество записей с более ранней датой рейтинга и используете его вместо номера строки. Примечание. Предполагается, что даты оценки уникальны для каждого сотрудника. Если это не так, вам нужно будет добавить еще один столбец, чтобы разорвать связь.

Затем используйте CASE, чтобы проверить номера строк и создать столбцы для первого три записи:

SELECT  r.employeeID
        , MAX( CASE WHEN r.Row = 0 THEN r.EmployeeRating ELSE 0 END ) AS LastRating  
        , MAX( CASE WHEN r.Row = 1 THEN r.EmployeeRating ELSE 0 END ) AS SecondLastRating  
        , MAX( CASE WHEN r.Row = 2 THEN r.EmployeeRating ELSE 0 END ) AS ThirdLastRating  
FROM  (
        SELECT  m.employeeID
                , m.employeeRating
                , m.theRatingDate
                , (  SELECT COUNT(*)
                     FROM   yourTable cnt
                    WHERE  cnt.employeeID = m.employeeID
                    AND    cnt.theRatingDate > m.theRatingDate
                ) AS Row
        FROM   yourTable m
        GROUP BY m.employeeID
            , m.employeeRating
            , m.theRatingDate
        ) r
WHERE  r.Row <= 2
GROUP BY r.employeeID

Холодный синтез

Последний вариант — использовать ColdFusion. Вы можете адаптировать ответ Джеймса Молера для заполнения отдельного "основного" запроса. Перед циклом запроса создайте новый объект запроса и последовательно назовите столбцы рейтинга, например rating1,rating2,rating3. Внутри внешнего цикла добавьте строку для каждого сотрудника. Наконец, используйте счетчик для заполнения первых трех столбцов внутри внутреннего цикла.

Примечание. Исходный запрос должен быть заказан employeeID, shortDate DESC, иначе код не будет работать правильно.

<cfset newQuery = queryNew("employeeID,rating1,rating2,rating3", "integer,Decimal,Decimal,Decimal")>

<cfoutput query="originalQuery" group="employeeID">
    <!--- add new employee row --->
    <cfset ratingRow = queryAddRow(newQuery, 1)>
    <cfset newQuery["employeeID"][ratingRow] = employeeID>

    <!--- initialize rating counter --->
    <cfset ratingIndex = 0>
    <cfoutput>
        <cfset ratingColumn++>
        <!--- populate top 3 rating columns --->
        <cfif ratingColumn lte 3>
            <cfset newQuery["rating"& ratingColumn][ratingRow] = employeeRating>
        </cfif>
    </cfoutput>
</cfoutput>
person Leigh    schedule 06.06.2013
comment
Спасибо Ли!!! Я попробовал ваш код, однако мой сервер — SQL Server (80), который не поддерживает функцию ROW_NUMBER(). Его поддерживает только SQL Server 2005 (90), SQL Server 2008 (100) или SQL Server 2012 (110). Печальный день, потому что ваше решение выглядит великолепно и многообещающе!!!! :( - person Enchauva; 06.06.2013
comment
О, тогда PIVOT тоже нет. Оба были введены после 2000 года. Однако вы можете имитировать это с помощью подзапроса и case. Это не так красиво, но я должен сделать это для устаревших систем. Я опубликую обновленный пример для SQL Server 2000. - person Leigh; 06.06.2013
comment
Ли... ты просто эпична!!! Прямо сейчас просматриваю и подключаю ваше решение. Я предоставлю обновление статуса в течение часа! скрестить пальцы - person Enchauva; 06.06.2013
comment
Еще раз спасибо Лей! Все работает, однако оператор CASE WHEN r.Row = 0... не дает правильного рейтинга, и я понимаю, почему. Когда мы просим внутренний-внутренний оператор подсчитать количество строк, он подсчитывает строки перед вычислением сводки рейтинга сотрудников. Исходная таблица распределения работы сотрудников имеет разный объем работы для конкретного сотрудника за каждый месяц, и cfquery сводит эти данные в месячный рейтинг сотрудника за конкретный месяц. Я приведу пример таблицы необработанных данных перед запросом... это ЧРЕЗВЫЧАЙНО близко к решению!!! - person Enchauva; 07.06.2013
comment
(Редактировать) Ну, технически вы могли бы заставить его работать, но, учитывая новые требования и существующие ограничения, требуемый sql станет немного нелепым... Что вам действительно нужно, так это создать view или временную таблицу и используйте ее в место yourTable. Доступно ли это разрешение в вашей среде? Если нет, возможно, вам лучше использовать CF на этом этапе. Не зная, зачем нужна сводная точка для соединения с другим запросом, трудно сказать, является ли это единственным вариантом. - person Leigh; 07.06.2013

Вот решение только для ColdFusion

<table>
<tr>
    <td>SupplierID</td>
    <td>LastRating</td>
    <td>SecondLastRating</td>
    <td>ThirdLastRating</td>
</tr>
<cfoutput name="qrySupplier" group="employeeID">
<cfset Rating  = 0>
<tr>
    <td>#employeeid#</td>
    <cfoutput>
       <cfset Rating++>
       <cfif Rating LTE 3>
           <td>#employeerating#</td>
       </cfif>
     </cfoutput>
</tr>
</cfoutput>
</table>
person James A Mohler    schedule 05.06.2013
comment
Это работает!!!!!! Я работаю с ним!!!! Спасибо огромное!!! Это была трудная часть для меня, но теперь я увижу, что могу создать новый запрос из этого. В любом случае... Я постараюсь найти вас на FB и подарить вам подарок из Starbucks ^_^ Еще раз спасибо! - person Enchauva; 05.06.2013
comment
Теперь трюк, который я пытаюсь понять, заключается в том, как взять это и создать новый запрос. И предложения? - person Enchauva; 06.06.2013
comment
Сводная таблица была бы полезна - person James A Mohler; 06.06.2013
comment
@Enchauva - В этом конкретном случае имитация разворота в 2000 году станет немного уродливой / неуправляемой - если у вас нет возможности создавать представления или временные таблицы. Ты? Хотя это и не идеально, вы все же можете использовать код CF. Смотрите мой обновленный ответ. - person Leigh; 11.06.2013

Вот что вы можете попробовать, чтобы начать. Я оставил комментарии. У меня не было доступа к созданию быстрой таблицы, поэтому она не проверена, но может быть хорошим стартером. Я учел тот факт, что у вашего сотрудника/поставщика может быть больше или меньше 3 оценок.

<!--- Counter to count ratings  --->
<Cfset x=0>
<table width="600" border="0" cellspacing="0" cellpadding="0">
  <tr>
    <td>supplierid</td>
    <td>last rating</td>
    <td>second last rating</td>
    <td>thirdlastrating</td>
  </tr>
<!--- Group by employee --->
<cfoutput query="yourQuery" group="employeeid">
    <!--- if previous employee had less then 3 ratings, close off table --->
    <cfif x gt 0 and x lt 3>
         <cfif x eq 1><td>&nbsp</td><td>&nbsp;</td></tr></cfif>
         <cfif x eq 2><td>&nbsp;</td></tr></cfif>
    </cfif>
    <!--- Loop through employee --->
  <tr>
    <td>#employeeid#</td>
    <!--- Check counter to make sure we are only doing 3 ratings per line --->
    <cfif x lt 3>
        <cfoutput>
        <td>#employeerating#</td>
        <cfset x=x+1>
        </cfoutput>
    </cfif>
    <!--- If at the 3rd rating, close off the row --->
    <cfif x eq 3>
        </tr>
        <cfset x=0>
        <!--- if at 3rd rating, reset counter --->
    </cfif>
</cfoutput>
</table>
person steve    schedule 05.06.2013
comment
это сработало для первого сотрудника, но не заполнило рейтинг остальных сотрудников. Я попробую еще раз, чтобы трижды проверить, что я ничего не просмотрел. В любом случае, я ценю вашу поддержку и ваш подход!!!! :D - person Enchauva; 05.06.2013