2013-02-12 3 views
3

Я пытаюсь вычислить возвращаемое максимальное значение в таблице вместе с другими значениями в этой таблице. Однако таблица, в которой я делаю это, не является «реальной» таблицей, она генерируется подзапросом. Это дает мне проблемы, так как я не думаю, что могу присоединиться к нему дважды, не отменяя весь подзапрос.Groupwise MAX() в подзапросе

В настоящее время у меня есть решение SQL Server для этого, используя ROW_NUMBER() OVER (PARTITION BY providerId ORDER BY partnershipSetScore DESC) rnk, но я ищу агномическую версию СУБД, если это возможно, поскольку модульные тесты для проекта выполняются в Sqlite DB, который не имеет этой функции.

Вот схема и мой SQL Server конкретный запрос, в случае, если они полезны:

курс:

  • INT Идентификатор
  • имя VARCHAR
  • INT schoolId

Школа:

  • INT Идентификатор
  • имя VARCHAR

Partnership:

  • INT Идентификатор
  • VARCHAR partnershipName

SchoolPartnership:

  • INT Идентификатор
  • INT schoolId
  • INT partnershipId

Вот запрос:

SELECT 
    schoolId, 
    partnershipId AS bestPartnershipSetId, 
    partnershipScore AS bestPartnershipScore 
FROM 
(
    SELECT 
     pp.schoolId, 
     partnershipScores.partnershipId, 
     partnershipScores.partnershipScore, 
     ROW_NUMBER() OVER (PARTITION BY schoolId ORDER BY partnershipScore DESC) rnk 
    FROM schoolPartnership pp 
    INNER JOIN (
     SELECT 
      pp.partnershipId, 
      (
       (CASE WHEN SUM(CASE WHEN c.name LIKE '%French%' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END) 
       + (CASE WHEN SUM(CASE WHEN c.name LIKE '%History%' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END) 
      ) AS partnershipScore 
     FROM schoolPartnership pp 
     INNER JOIN course c ON c.schoolId = pp.schoolId 
     GROUP BY partnershipId 
    ) AS partnershipScores ON partnershipScores.partnershipId = pp.partnershipId 
) AS schoolPartnershipScores 
WHERE rnk = 1 

Если вам нужна дополнительная информация о том, что я m, котор нужно достигнуть, см. Custom sorting algorithm for a large amount of data: Этот запрос будет подзапросом большего запроса, который выполняет сортировку школ по наиболее подходящему партнерству.

ответ

0

Мне не удалось найти решение (кроме дублируя подзапрос, чего я пытался избежать), поэтому я только что определил строки MAX для каждого партнерства в PHP и выбросил любые другие строки. Не идеальное решение, но поскольку мне нужен кросс-платформенный подход, не было слишком много других вариантов, доступных мне.

0

Это структура вы хотите:

with t as (<subquery goes here>) 
select t.*, 
     max(col) over() as MaxVal 
from t 

Это немного трудно понять, как она вписывается в ваш запрос, потому что я не могу сказать, что база подзапрос.

Что касается присоединения к подзапросу более одного раза, вы можете сделать это, используя то, что SQL Server вызывает «общие табличные выражения» - вышеописанное предложение with. Большинство других разумных баз данных поддерживают это (MySQL и MS Access начинают два заметных исключения).

+0

Спасибо, это было бы идеально, но, к сожалению, Sqlite не поддерживает CTE. – ChrisC

+0

@ChrisC. , , Когда я ответил на этот вопрос, он не был помечен SQLite. Почему он помечен двумя базами данных? –

+0

Он был отредактирован кем-то. Редактирование в порядке, исходный вопрос объясняет, почему: У меня в настоящее время есть специальный запрос SQL Server, который мне нужно скрывать, поэтому он работает как в SQL Server, так и в SQLite. – ChrisC

0

Наиболее SQL агностик способом будет использовать 'NON EXISTS':

SELECT * FROM schoolPartnership t1 
WHERE NOT EXISTS 
     (SELECT * FROM schoolPartnership t2 
     WHERE t1.schoolId = t2.schoolId 
       AND t1.partnershipScore < t2.partnershipScore) 

Это даст вам строки из schoolPartnership с максимальной partnershipScore на каждый schoolId.

+0

Я не уверен, как это помогает, я боюсь: поле партнерства партнерства не существует на t1 (schoolPartnership), оно находится в подзапросе, поэтому я столкнулся с этой проблемой. – ChrisC

+0

@ChrisC это просто пример, показывающий, как вы можете это сделать. Я рекомендую создать sqlfiddle для вопросов с большим SQL. – Bulat

1

Может быть, когда речь идет о присоединении к подзапрос дважды, вы имели эту технику в своем уме:

SELECT a.* 
FROM atable a 
INNER JOIN (
    SELECT 
    col1, 
    MAX(col2) AS max_col2 
    FROM atable 
    GROUP BY col1 
) m 
ON a.col1 = m.col1 AND a.col2 = m.max_col2 
; 

И это было бы прекрасно, чтобы использовать в качестве СУБД агностик способом (по крайней мере, один рабочий как в SQL Server, так и в SQLite), чтобы выполнить задание , если это была одна таблица.

Вместо этого у вас есть подзапрос. Однако я не вижу другого способа выполнить то, о чем вы просите. В этой ситуации, поэтому я вижу два варианта для вас (один, возможно, неприменимы в вашем конкретном случае, но все же вариант в целом):

  1. Делайте то, что вы пытаетесь избежать, то есть дублировать подзапрос специально для нахождение агрегированных значений для каждой группы, а затем присоединить их к тому же подзапросу, как описано выше.

  2. Временно сохраняйте результаты подзапроса, затем применяйте описанную выше технику к временному набору результатов.

Первый вариант не очень привлекателен, тем более, что есть надежда, что второй может работать.

Одной из проблем со вторым вариантом является то, что временные наборы данных реализованы по-разному в SQL Server и SQLite. В SQLite вы используете для этого инструкцию CREATE TEMPORARY TABLE. SQL Server не поддерживает ключевое слово TEMPORARY в контексте оператора CREATE TABLE и вместо этого использует специальный символ (#) в начале имени таблицы, чтобы обозначить, что таблица на самом деле является временной.

Таким образом, единственным обходным решением, которое я могу видеть, является использование таблицы normal в качестве временного хранения. Вы можете создать его один раз, а затем удалить его содержимое каждый раз при запуске вашего запроса, непосредственно перед установкой временного набора результатов:

DELETE FROM TempTable; 
INSERT INTO TempTable (
    schoolId, 
    bestPartnershipSetId, 
    bestPartnershipScore 
) 
SELECT 
    pp.schoolId, 
    partnershipScores.partnershipId, 
    partnershipScores.partnershipScore, 
FROM 
    ... 
; 
SELECT ... 
FROM TempTable 
... 
; 

Или вы можете создать & падение его каждый раз при выполнении запроса:

CREATE TABLE TempTable (
    ... 
); 
INSERT INTO TempTable (...) 
SELECT ... 
FROM ... 
; 
SELECT ... 
FROM TempTable 
... 
; 
DROP TABLE TempTable; 

Обратите внимание, что использование обычной таблицы в качестве временного хранилища, как это, не является совместимым с параллелизмом в SQL Server. Если это может создать проблему, вам, вероятно, придется отказаться от этой опции и в итоге получить первую. (Но это, вероятно, затраты, которые вы должны заплатить, если хотите независимое от платформы решение, особенно когда платформы отличаются от SQL Server и SQLite.)

+0

Большое спасибо за подробный ответ. Я не думаю, что смогу спуститься по нормальному/временному маршруту, из-за проблем с параллелизмом, поэтому я думаю, что мне (к сожалению) придется дублировать подзапрос. Я обновлю этот вопрос, когда закончим. – ChrisC

Смежные вопросы