#sql #sql-server #window-functions
Вопрос:
Во-первых, у меня есть эта информация:
- Вес А
- Вес B
- Отношение B к A: 1 ко многим
Таким образом, ниже может быть получен результат:
A_Id | Вес А | Вес B | B_Id |
---|---|---|---|
1 | 3 | 16 | 1 |
2 | 5 | 16 | 1 |
3 | 6 | 16 | 1 |
4 | 7 | 16 | 1 |
5 | 2 | 12 | 2 |
6 | 6 | 12 | 2 |
Теперь добавим еще два столбца: Sum Weight A By B_Id
, Accumulative Difference
(рассмотрим ниже в виде таблицы t2
)
A_Id | Вес А | Вес Суммы A По B_Id | Вес B | B_Id | Накопительная Разница |
---|---|---|---|---|---|
1 | 3 | 21 | 16 | 1 | 5 |
2 | 5 | 21 | 16 | 1 | 5 |
3 | 6 | 21 | 16 | 1 | 5 |
4 | 7 | 21 | 16 | 1 | 5 |
5 | 2 | 8 | 12 | 2 | 1 |
6 | 6 | 8 | 12 | 2 | 1 |
Например, выше,
- накопительная разница в первом ряду => 21 — 16 = 5
- накопительная разница в пятом ряду => (21 8) — (16 12) = 1
Итак, моя цель состоит в том, чтобы вычислить такое « Accumulative Difference
Весь результат должен быть отображен в отчете.
Технически, используя «Оконные функции», этого можно достичь без проблем. Во-первых, я должен создать еще 2 столбца: Accumulate Weight A By B_Id
, Accumulate Weight B
. Затем просто найдите разницу между ними.
На самом деле мне нужно еще 3 колонки:
- [Строка По B_Id]
- [Суммарный Вес A По B_Id]
- [Накопить Вес B]
A_Id | Вес А | Вес Суммы A По B_Id | Вес B | B_Id | Строка По B_Id | Накопите Вес A На B_Id | Накопить Вес В | Накопительная Разница |
---|---|---|---|---|---|---|---|---|
1 | 3 | 21 | 16 | 1 | 1 | 21 | 16 | 5 |
2 | 5 | 21 | 16 | 1 | 2 | 21 | 16 | 5 |
3 | 6 | 21 | 16 | 1 | 3 | 21 | 16 | 5 |
4 | 7 | 21 | 16 | 1 | 4 | 21 | 16 | 5 |
5 | 2 | 8 | 12 | 2 | 1 | 29 | 28 | 1 |
6 | 6 | 8 | 12 | 2 | 2 | 29 | 28 | 1 |
sample SQL (to generate t2
):
SELECT
*,
[Accumulate Weight A By B_Id] = SUM(WeightA) OVER (PARTITION BY … ORDER BY B_Id),
[Accumulate Weight B] = SUM(WeightB) OVER (PARTITION BY … ORDER BY B_Id)
FROM t2
— (…) could be by date year month
— Accumulate Weight B can set to only 1st row, etc
;WITH tableA AS (
SELECT [A_Id] = 1, [Weight] = 3, [B_Id] = 1, [date] = '2021-10-01'
UNION
SELECT [A_Id] = 2, [Weight] = 5, [B_Id] = 1, [date] = '2021-10-02'
UNION
SELECT [A_Id] = 3, [Weight] = 6, [B_Id] = 1, [date] = '2021-10-03'
UNION
SELECT [A_Id] = 4, [Weight] = 7, [B_Id] = 1, [date] = '2021-10-04'
UNION
SELECT [A_Id] = 5, [Weight] = 2, [B_Id] = 2, [date] = '2021-10-05'
UNION
SELECT [A_Id] = 6, [Weight] = 6, [B_Id] = 2, [date] = '2021-10-06'
--Uncomment for testing NULL value
--UNION
--SELECT [A_Id] = 7, [Weight] = 9, [B_Id] = NULL, [date] = '2021-10-07'
--UNION
--SELECT [A_Id] = 8, [Weight] = 10, [B_Id] = 3, [date] = '2021-10-08'
),
tableB AS (
SELECT [B_Id] = 1, [Weight] = 16, [date] = '2021-10-03'
UNION
SELECT [B_Id] = 2, [Weight] = 12, [date] = '2021-10-06'
--Uncomment for testing NULL value
--UNION
--SELECT [B_Id] = 3, [Weight] = 8, [date] = '2021-10-08'
),
t1a AS (
SELECT
[A_Id] = tableA.A_Id,
[WeightA] = tableA.Weight,
[WeightB] = tableB.Weight,
[B_Id] = tableB.B_Id,
[Row By B_Id] = ROW_NUMBER() OVER(PARTITION BY tableB.B_Id ORDER BY A_Id)
FROM
tableA
FULL JOIN tableB ON tableA.B_Id = tableB.B_Id
),
t1b AS (
SELECT
*,
[Sum Weight A By B_Id] = SUM(WeightA) OVER (ORDER BY B_Id),
[Accumulate Weight B] = SUM(CASE WHEN [Row By B_Id] = 1 THEN WeightB ELSE 0 END) OVER (ORDER BY B_Id)
FROM t1a
),
t2 AS (
SELECT
*,
[Accumulate Difference] = [Sum Weight A By B_Id] - [Accumulate Weight B]
FROM t1b
)
SELECT
*
FROM t2
Теперь возникает проблема, ЕСЛИ один из B_Id
них есть NULL
. (Раскомментируйте детали, чтобы сгенерировать нулевой B_Id)
Ниже приведен мой ожидаемый результат, особенно в выделенной строке:
A_Id | Вес А | Вес Суммы A По B_Id | Вес B | B_Id | Накопите Вес A На B_Id | Накопить Вес В | Накопительная Разница |
---|---|---|---|---|---|---|---|
1 | 3 | 21 | 16 | 1 | 21 | 16 | 5 |
2 | 5 | 21 | 16 | 1 | 21 | 16 | 5 |
3 | 6 | 21 | 16 | 1 | 21 | 16 | 5 |
4 | 7 | 21 | 16 | 1 | 21 | 16 | 5 |
5 | 2 | 8 | 12 | 2 | 29 | 28 | 1 |
6 | 6 | 8 | 12 | 2 | 29 | 28 | 1 |
7 | 9 | 9 | 0 | нулевой | 38 | 28 | 10 |
8 | 7 | 10 | 8 | 3 | 48 | 36 | 12 |
9 | 3 | 10 | 8 | 3 | 48 | 36 | 12 |
Однако с моим примерным запросом это не работает. Вместо этого ниже появится:
Вместо этого в первой строке отображается пустой B_Id. (Заказ испорчен)
Итак, мой вопрос, как справиться с такой ситуацией? (Сохраните исходную строку, как в ожидаемом результате)
Как, например, почему порядок таков? (поднято @ThorstenKettner)
Порядок по умолчанию, основан B_TransactionDatetime
. Если B_Id
равно NULL, то он будет основан на A_TransactionDatetime
. Итак, я вычисляю другой столбец RefDateTime = COALESCE(B_TransactionDatetime, A_TransactionDatetime)
и упорядочиваю его на основе этого.
пс:
Вдохновленный @ThorstenKettner, я должен использовать функцию RefDateTime
в окне, т. е.:
[Sum Weight A By B_Id] = SUM(WeightA) OVER (ORDER BY RefDateTime),
[Accumulate Weight B] = SUM(CASE WHEN [Row By B_Id] = 1 THEN WeightB ELSE 0 END) OVER (ORDER BY RefDateTime)
Дело закрыто.
Комментарии:
1. Вы используете
-
, чтобы представлятьNULL
s здесь? Это было бы менее запутанно, если бы вы использовалиNULL
его .'-'
это строка, которая подразумевает, что вы храните числовые данные вvarchar
; основной недостаток дизайна.2. Вы также говорите о наборе данных с a
B_Id
NULL
, но ни один из ваших примеров данных не демонстрирует эту проблему.3. @Larnu спасибо за отзыв. я только что добавил колонку
B_Id
. Между тем, » — » не равно НУЛЮ, вы можете считать, что это то же значение, что и в предыдущей строке. (я поставил дефис, чтобы сфокусировать другие значения)4. Тогда вам нужно исправить свои данные, zeroflaw.
-
<>The value of the previous row
. Если все несколько строк имеют одинаковое значение, они также должны быть указаны в ваших образцах данных.5. Таким образом, есть A без B. Однако я не понимаю, по каким критериям вы упорядочиваете свои строки. Сначала я, хотя по b.id, а.вес, но положение b.id Значение NULL противоречит этому, а также порядку строк для b.id 3.
Ответ №1:
Вы хотите внешнее соединение B с A, потому что не у каждого A есть связанный B.
Затем вы смотрите на строки по блокам. Один блок-это либо все строки.принадлежащие одному B, либо одна строка A, в которой нет B. b_id будет хорошим ключом группы для первого, в то время как a_id подойдет для второго. Для комбинированного ключа существуют различные варианты. COALESCE(b_id, a_id)
не является одним из них, потому что мы могли бы иметь a_id 1 и b_id 1 в результирующем наборе, но не хотим, чтобы они находились в одной группе. Одно из решений простое COALESCE(b_id, -a_id)
, при условии, что ваши идентификаторы, конечно, не могут быть отрицательными.
Теперь все ваши вычисления основаны на агрегированных группах, т. Е. вас не интересуют отдельные значения A, когда они принадлежат группе B. По этой причине я бы сразу же объединил и снова присоединился только к одной строке » А » в самом конце.
Порядок строк таков COALESCE(b_date, a_date)
.
with grouped as
(
select
coalesce(b.b_id, -a.a_id) as grp_id,
max(coalesce(b.date, a.date)) as grp_date,
coalesce(max(b.weight), 0) as b_weight,
sum(a.weight) as a_weight
from a
left join b on b.b_id =a.b_id
group by coalesce(b.b_id, -a.a_id)
)
, calculated as
(
select
grp_id,
grp_date,
b_weight,
a_weight,
sum(a_weight - b_weight) over (order by grp_date) as running_diff
from grouped
)
select *
from calculated c
join a on a.b_id = c.grp_id or a.a_id = -c.grp_id
order by c.grp_date, a.date;
Надеюсь, я все понял правильно. У меня нет компьютера под рукой, и я печатаю это на своем мобильном телефоне, что оказывается сложнее, чем я думал 🙂
Комментарии:
1. Пожалуйста, кто-нибудь отредактируйте этот код для меня. Он не работает с моего мобильного телефона.
{}
Кнопки нет, и отступ из четырех пробелов, похоже, тоже не работает должным образом для меня.2.Я отредактировал для вас 🙂 Ваш ответ вдохновил меня на использование
RefDateTime
COALESCE(b_date, a_date)
функции «БытьORDERed
в окне». (это сделало бы работу). Тем временем я узнал, чтоCOALESCE(b_id, -a_id)
могу служить группой. Потрясающе.
Ответ №2:
Вы можете использовать coalesce().
SELECT
*,
[Accumulate Weight A By B_Id] = SUM(WeightA) OVER (PARTITION BY B_id ORDER BY B_Id),
[Accumulate Weight B] = SUM(WeightB) OVER (PARTITION BY B_id ORDER BY B_Id),
SUM(coalesce(WeightA,0)-coalesce(WeightB,0)) OVER (PARTITION BY B_id ORDER BY B_Id) difference
FROM t2
PS: На самом деле ваш первоначальный запрос кажется мне неправильным, если бы это было правильно, то это подошло бы.
Вероятно, вам следует привести примеры данных A и B. Для меня имеет больше смысла суммировать (), прежде чем присоединяться к ним вообще.
Комментарии:
1. Привет, приношу свои извинения. Я добавил больше информации в вопрос.
Ответ №3:
Вам придется внести изменения, но это должно помочь.`
SELECT [Accumulate Weight A By B_Id] = SUM(WeightA) OVER (
PARTITION BY...ORDER BY B_Id
)
,[Accumulate Weight B] = SUM(WeightB) OVER (
PARTITION BY...ORDER BY B_Id
)
FROM t2
WHERE B_Id IS NOT NULL
UNION
SELECT [Accumulate Weight A By B_Id] = SUM(TAB.WeightA) OVER (
PARTITION BY TAB.ROW_NUM ORDER BY B_Id
)
,[Accumulate Weight B] = SUM(TAB.WeightB) OVER (
PARTITION BY TAB.ROW_NUM ORDER BY B_Id
)
FROM (
SELECT WeightA
,WeightB
,B_Id
,ROW_NUMBER() OVER (
ORDER BY B_ID
) AS ROW_NUM
FROM T2
WHERE B_ID IS NULL
) TAB
`
Комментарии:
1. Привет, приношу свои извинения. я добавил больше информации в вопрос. Как видно из вашего запроса, означает ли это, что, наконец, порядок изменится?