Как я могу объединить два запроса на выборку с количеством в основном идентичных столбцов, но один набор возвращает несколько строк

У меня есть два запроса, которые отлично работают:

DECLARE @StartDate DATETIME = '2021-11-01 00:00:00';
DECLARE @EndDate DATETIME = '2022-03-16 23:59:59';
DECLARE @SalesEstimateTransactionTypeId INT = 16;
DECLARE @SalesOrderTransactionTypeId INT = 15;

SELECT 
    DATENAME(mm, GeneralJournal.[TransactionDate]) AS ReportingMonth, 
    DATEPART(mm, GeneralJournal.[TransactionDate]) AS MonthNumber, 
    DATEPART(yyyy, GeneralJournal.[TransactionDate]) AS ReportingYear,
    COUNT(TransactionId) AS TransactionCount
FROM 
    GeneralJournal 
WHERE 
    GeneralJournal.[TransactionDate] >= @StartDate 
    AND GeneralJournal.[TransactionDate] <= @EndDate 
    AND MasterRecord = 1 
    AND TransactionTypeId = @SalesEstimateTransactionTypeId
GROUP BY 
    DATEPART(yyyy, GeneralJournal.[TransactionDate]), 
    DATEPART(mm, GeneralJournal.[TransactionDate]),  
    DATENAME(mm,GeneralJournal.[TransactionDate]);

SELECT 
    DATENAME(mm, GeneralJournal.[TransactionDate]) AS ReportingMonth, 
    DATEPART(mm, GeneralJournal.[TransactionDate]) AS MonthNumber, 
    DATEPART(yyyy, GeneralJournal.[TransactionDate]) AS ReportingYear, 
    COUNT(DISTINCT TransactionId) AS ConversionCount
FROM 
    GeneralJournal 
WHERE 
    GeneralJournal.[TransactionDate] >= @StartDate 
    AND GeneralJournal.[TransactionDate] <= @EndDate 
    AND MasterRecord = 0 
    AND TransactionTypeId = @SalesOrderTransactionTypeId 
    AND SEReferenceId > 0
GROUP BY 
    DATEPART(yyyy, GeneralJournal.[TransactionDate]), 
    DATEPART(mm, GeneralJournal.[TransactionDate]),  
    DATENAME(mm,GeneralJournal.[TransactionDate]);

Обратите внимание, что второй запрос возвращает отдельные, потому что он может возвращать несколько значений, и мы хотим подсчитать каждое TransactionId только один раз в этом сценарии. Они возвращают следующие результаты:

Месяц ОтчетностиМесяцНомерОтчетный годTransactionCount
ноябрь11202182
Декабрь12202149
январь1202264
февраль2202267
Маршировать3202249
Месяц ОтчетностиМесяцНомерОтчетный годConversionCount
ноябрь11202142
Декабрь12202127
январь1202231
февраль2202250
Маршировать3202224

Мне на самом деле нужно объединить их следующим образом:

Месяц ОтчетностиМесяцНомерОтчетный годTransactionCountConversionCount
ноябрь1120218242
Декабрь1220214927
январь120226431
февраль220226750
Маршировать320224924

Я перепробовал практически все, что только мог придумать - союзы, объединения, подзапросы - но пока ничего не получается. Это самое близкое, что я могу получить:

SELECT 
    DATENAME(mm, GeneralJournal.[TransactionDate]) AS ReportingMonth, 
    DATEPART(mm, GeneralJournal.[TransactionDate]) AS MonthNumber, 
    DATEPART(yyyy, GeneralJournal.[TransactionDate]) AS ReportingYear, 
    SUM(CASE 
            WHEN TransactionTypeId = @SalesEstimateTransactionTypeId 
                 AND MasterRecord = 1 
               THEN 1 ELSE 0 
        END) AS TransactionCount, 
    COUNT(CASE 
              WHEN TransactionTypeId = @SalesOrderTransactionTypeId  
                   AND SEReferenceId > 0 THEN 1 
          END) AS ConversionCount
FROM 
    GeneralJournal 
WHERE 
    GeneralJournal.[TransactionDate] >= @StartDate 
    AND GeneralJournal.[TransactionDate] <= @EndDate 
    AND TransactionTypeId IN (@SalesOrderTransactionTypeId, @SalesEstimateTransactionTypeId)
GROUP BY 
    DATEPART(yyyy, GeneralJournal.[TransactionDate]), 
    DATEPART(mm, GeneralJournal.[TransactionDate]),    
    DATENAME(mm,GeneralJournal.[TransactionDate]);

Однако я не могу найти способ получить значение Distinct для ConversionCount. В результате он возвращает полный счет:

Месяц ОтчетностиМесяцНомерОтчетный годTransactionCountConversionCount
ноябрь11202182152
Декабрь1220214967
январь1202264101
февраль2202267136
Маршировать320224964

Может ли кто-нибудь указать мне, как объединить два результата запроса, сохраняя при этом Distinct в подсчете конверсий? Я должен добавить, что для его работы ответ должен быть совместим как с SQL Server, так и с VistaDB, синтаксис которого является подмножеством T-SQL, потому что я обязан поддерживать оба механизма базы данных с одним и тем же запросом.

РЕДАКТИРОВАТЬ - Окончательное решение

Следуя отличному ответу Ника, я смог встроить решение в свой существующий код запроса, чтобы гарантировать наличие результатов даже в течение нескольких месяцев без записей, показанных здесь на случай, если это поможет кому-то еще:

DECLARE @StartDate DATETIME = '2021-11-01T00:00:00';
DECLARE @EndDate DATETIME = '2022-10-31T23:59:59';
DECLARE @SalesEstimateTransactionTypeId INT = 16;
DECLARE @SalesOrderTransactionTypeId INT = 15;

DECLARE @CurrentDate DATETIME;
DECLARE @Months TABLE(ReportingYear INT, MonthNumber INT, ReportingMonth VARCHAR (40));

-- Set the initial date
SET @CurrentDate = @StartDate
-- insert all dates into temp table
WHILE @CurrentDate <=  @EndDate
BEGIN
    INSERT INTO @Months VALUES(DATEPART(year, @CurrentDate), DATEPART(month, @CurrentDate), DATENAME(mm, @CurrentDate))
    SET @CurrentDate = dateadd(mm, 1, @CurrentDate)
END;

SELECT ReportingMonth, ReportingYear, Coalesce(TransactionCount, 0) AS TransactionCount, Coalesce(ConversionCount,0) AS ConversionCount
FROM
(
    SELECT months.[ReportingMonth], months.[ReportingYear], conversionData.[TransactionCount], conversionData.[ConversionCount]
    FROM @Months months
    LEFT JOIN
    (
        SELECT
        ReportingMonth      = DATENAME(mm, GeneralJournal.[TransactionDate]),
        MonthNumber         = DATEPART(mm, GeneralJournal.[TransactionDate]),
        ReportingYear       = DATEPART(yyyy, GeneralJournal.[TransactionDate]),
        TransactionCount    = SUM(CASE WHEN TransactionTypeId = @SalesEstimateTransactionTypeId AND GeneralJournal.[MasterRecord] = 1 THEN
                                        1
                                    ELSE
                                        0
                                END
                            ),
        ConversionCount     = COUNT(DISTINCT CASE WHEN GeneralJournal.[TransactionTypeId] = @SalesOrderTransactionTypeId
                                        AND GeneralJournal.[SEReferenceId] > 0
                                        AND GeneralJournal.[MasterRecord] = 0 THEN
                                        GeneralJournal.[TransactionID]
                                END
                            )
        FROM GeneralJournal
        WHERE GeneralJournal.[TransactionDate] >= @StartDate
            AND GeneralJournal.[TransactionDate] <= @EndDate
            AND GeneralJournal.[TransactionTypeId] IN ( @SalesOrderTransactionTypeId, @SalesEstimateTransactionTypeId)
        GROUP BY
            DATEPART(yyyy, GeneralJournal.[TransactionDate]),
            DATEPART(mm, GeneralJournal.[TransactionDate]),
            DATENAME(mm, GeneralJournal.[TransactionDate])
    ) as conversionData
    ON months.[ReportingYear] = conversionData.[ReportingYear] AND months.[MonthNumber] = conversionData.[MonthNumber]
) AS data;

В сторону... У меня есть два запроса, которые отлично работают... в очень специфических обстоятельствах. Попытка использовать '2021-11-01 00:00:00' для даты и времени даст 01 ноября 2021 года, когда действует set dateformat mdy, но 11 января 2021 года, когда действует set dateformat dmy. Если вы хотите быть независимым от формата даты, вам нужно использовать разделитель T, например: '2021-11-01T00:00:00'.

AlwaysLearning 18.03.2022 14:47

Не используйте включающую верхнюю границу для диапазонов даты и времени — см. знакомства ответственно

SMor 18.03.2022 14:58

Можете ли вы предоставить нам некоторые примеры подробных данных для работы?

Nick Fotopoulos 18.03.2022 17:15

Привет @AlwaysLearning, спасибо за советы. Обстоятельства немного сложны, так как даты передаются из приложения в форматах, перечисленных на основе данных диапазона учетных дат и параметризованных для реального запроса. Он передает ToStartOfDay и ToEndOfDay в начальной и конечной дате, и эта степень детализации слишком сложна для обработки SQL Server, поэтому ее необходимо настроить. Вы правы, хотя во всем, что вы говорите, и я очень ценю указатели.

oldcoder 19.03.2022 15:46

Привет @SMor, спасибо за совет. Я работаю с предоставленными значениями, которые имеют включающую границу для просмотра человеком и передаются в качестве параметров значения. Я еще раз посмотрю, можно ли это изменить, но это нужно сделать в вызывающем коде, например. добавьте одну миллисекунду к EndOfDay, чтобы перейти к StartOfDay на следующий день, как вы рекомендуете.

oldcoder 19.03.2022 15:52
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
5
41
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете просто поместить два столбца в один и тот же запрос. Это усложняется тем, что предложения WHERE немного отличаются. Итак, вам нужно сгруппировать, затем снова сгруппировать и использовать условную агрегацию для подсчета правильных строк для каждого столбца.

Обратите внимание на следующее:

  • Теоретически вы могли бы сделать COUNT(DISTINCT CASE, однако обычно это медленнее, поскольку компилятор не распознает, что делает CASE, и вместо этого выполнит полную сортировку.
  • Группировать по единому EOMONTHрасчету быстрее, чем группировать по целому месяцу. Вы можете вытащить год и месяц в SELECT.
  • COUNT(TransactionId) вернет количество ненулевых TransactionId значений. если TransactionId не может быть нулевым, то COUNT(*) — это то же самое.
  • Если TransactionDate имеет компонент времени, то вам следует использовать полуоткрытый интервал >= AND <
  • Используйте псевдонимы для таблиц, это сделает ваши запросы более читабельными.
  • Используйте whitepsace, это бесплатно.
DECLARE @StartDate DATETIME = '2021-11-01T00:00:00';
DECLARE @EndDate DATETIME = '2022-03-17T00:00:00';
DECLARE @SalesEstimateTransactionTypeId INT = 16;
DECLARE @SalesOrderTransactionTypeId INT = 15;

SELECT
  DATENAME(month, gj.mth) AS ReportingMonth,
  DATEPART(month, gj.mth) AS MonthNumber,
  DATEPART(year , gj.mth) AS ReportingYear,
  SUM(TransactionCount) AS TransactionCount,
  COUNT(CASE WHEN ConversionCount > 0 THEN 1 END) AS ConversionCount
FROM (
    SELECT
      EOMONTH(gj.TransactionDate) AS mth,
      gj.TransactionId,
      COUNT(CASE WHEN gj.MasterRecord = 1 AND gj.TransactionTypeId = @SalesEstimateTransactionTypeId THEN 1 END) AS TransactionCount,
      COUNT(CASE WHEN gj.MasterRecord = 0 AND gj.TransactionTypeId = @SalesOrderTransactionTypeId AND gj.SEReferenceId > 0 THEN 1 END) AS ConversionCount
    FROM GeneralJournal gj
    WHERE gj.TransactionDate >= @StartDate
      AND gj.TransactionDate <  @EndDate
      AND gj.TransactionTypeId IN (@SalesOrderTransactionTypeId, @SalesEstimateTransactionTypeId)
    GROUP BY
      EOMONTH(gj.TransactionDate),
      TransactionId
) g
GROUP BY
  mth;

Критерии разные для каждого COUNT. Посмотрите на пункты WHERE.

Nick Fotopoulos 18.03.2022 17:19

Правда, пропустил. Изменили

Charlieface 18.03.2022 18:51
Ответ принят как подходящий

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

  1. Вы забыли MasterRecord = 0 в выписке ConversionCount CASE.
  2. Вместо того, чтобы возвращать 1 или 0 из ConversionCount CASE, вы должны вернуть TransactionID или NULL, чтобы вы могли по-прежнему подсчитывать различные значения.
  3. Вам не хватает DISTINCT в вашем счетчике конверсий COUNT.
  4. Вам нужно будет обрабатывать значения NULL в ConversionCount COUNT. Я предполагал, что у вас всегда будет одна или несколько NULL, поэтому я просто вычитаю 1 из COUNT(DISTINCT ...), чтобы компенсировать это.

(Я не могу быть на 100% в синтаксисе здесь без некоторых примеров подробных данных для работы.)

Код

SELECT
    ReportingMonth      = DATENAME(mm, GeneralJournal.TransactionDate),
    MonthNumber         = DATEPART(mm, GeneralJournal.TransactionDate),
    ReportingYear       = DATEPART(yyyy, GeneralJournal.TransactionDate),
    TransactionCount    = SUM(CASE
                                WHEN TransactionTypeId = @SalesEstimateTransactionTypeId
                                    AND MasterRecord = 1 THEN
                                    1
                                ELSE
                                    0
                            END
                        ),
    ConversionCount     = COUNT(DISTINCT CASE
                                WHEN TransactionTypeId = @SalesOrderTransactionTypeId
                                    AND SEReferenceId > 0
                                    AND MasterRecord = 0 THEN
                                    TransactionID
                                ELSE
                                    NULL
                            END
                        ) - 1 /* Subtract 1 for the NULL */
FROM    GeneralJournal
WHERE
    GeneralJournal.TransactionDate >= @StartDate
    AND GeneralJournal.TransactionDate <= @EndDate
    AND TransactionTypeId IN (
            @SalesOrderTransactionTypeId,
            @SalesEstimateTransactionTypeId
        )
GROUP BY
    DATEPART(yyyy, GeneralJournal.TransactionDate),
    DATEPART(mm, GeneralJournal.TransactionDate),
    DATENAME(mm, GeneralJournal.TransactionDate);

Привет, Ник, спасибо за этот ответ. Вы заметили мои ошибки, и я узнал кое-что новое, поскольку я никогда раньше не видел синтаксиса псевдоним = выражение и всегда использовал выражение как псевдоним. Единственное изменение, которое мне нужно было сделать, это удалить ноль и соответствующий вывод. Поскольку мы подсчитываем только те, которые совпадают, в каждом диапазоне дат возвращались значения на единицу меньше, чем должны были быть. Я вернул его обратно в код, который у меня уже есть, который позволяет месяцами не проводить транзакции и по-прежнему возвращает ноль, и все выглядит идеально. Большое спасибо за отличный ответ.

oldcoder 19.03.2022 16:02

Я предпочитаю формат ColumnName =, потому что он делает ваши псевдонимы столбцов легко читаемыми и упорядоченными, когда у вас сложный вычисляемый столбец.

Nick Fotopoulos 21.03.2022 13:49

Другие вопросы по теме