Вставка в несколько таблиц
Краткое описание соответствующей части домена:
A Категория состоит из четырех данных:
- Пол (Мужской/Женский)
- Возрастная дивизия (Могущественный клещ для мастера)
- Цвет пояса (от белого до черного)
- Подразделение по весу (от петуха до тяжелого)
Итак, Male Adult Black Rooster
образует одну категорию. Некоторые комбинации могут не существовать, например, могучий клещный пояс.
Спортсмен сражается с спортсменами той же категории, и если он классифицирует, он борется с спортсменами разных весовых дивизий (но одного пола, возраста и пояса).
К моделированию. У меня есть таблица Category
, уже заполненная всеми комбинациями, которые существуют в домене.
CREATE TABLE Category (
[Id] [int] IDENTITY(1,1) NOT NULL,
[AgeDivision_Id] [int] NULL,
[Gender] [int] NULL,
[BeltColor] [int] NULL,
[WeightDivision] [int] NULL
)
A CategorySet
и a CategorySet_Category
, который формирует отношение многих ко многим с Category
.
CREATE TABLE CategorySet (
[Id] [int] IDENTITY(1,1) NOT NULL,
[Championship_Id] [int] NOT NULL,
)
CREATE TABLE CategorySet_Category (
[CategorySet_Id] [int] NOT NULL,
[Category_Id] [int] NOT NULL
)
Учитывая следующий набор результатов:
| Options_Id | Championship_Id | AgeDivision_Id | BeltColor | Gender | WeightDivision |
|------------|-----------------|----------------|-----------|--------|----------------|
1. | 2963 | 422 | 15 | 7 | 0 | 0 |
2. | 2963 | 422 | 15 | 7 | 0 | 1 |
3. | 2963 | 422 | 15 | 7 | 0 | 2 |
4. | 2963 | 422 | 15 | 7 | 0 | 3 |
5. | 2964 | 422 | 15 | 8 | 0 | 0 |
6. | 2964 | 422 | 15 | 8 | 0 | 1 |
7. | 2964 | 422 | 15 | 8 | 0 | 2 |
8. | 2964 | 422 | 15 | 8 | 0 | 3 |
Поскольку спортсмены могут сражаться с двумя категориями, мне нужно CategorySet
и CategorySet_Category
записаться двумя разными способами (это могут быть два запроса):
Один Category_Set
для каждой строки, при этом один CategorySet_Category
указывает на соответствующий Category
.
Один Category_Set
, который группирует все WeightDivisions в одном CategorySet
в том же AgeDivision_Id, BeltColor, Gender. В этом примере изменяется только BeltColor
.
Таким образом, итоговый результат будет состоять из 10 строк CategorySet
:
| Id | Championship_Id |
|----|-----------------|
| 1 | 422 |
| 2 | 422 |
| 3 | 422 |
| 4 | 422 |
| 5 | 422 |
| 6 | 422 |
| 7 | 422 |
| 8 | 422 |
| 9 | 422 | /* groups different Weight Division for BeltColor 7 */
| 10 | 422 | /* groups different Weight Division for BeltColor 8 */
И CategorySet_Category
будет иметь 16 строк:
| CategorySet_Id | Category_Id |
|----------------|-------------|
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | 4 |
| 5 | 5 |
| 6 | 6 |
| 7 | 7 |
| 8 | 8 |
| 9 | 1 | /* groups different Weight Division for BeltColor 7 */
| 9 | 2 | /* groups different Weight Division for BeltColor 7 */
| 9 | 3 | /* groups different Weight Division for BeltColor 7 */
| 9 | 4 | /* groups different Weight Division for BeltColor 7 */
| 10 | 5 | /* groups different Weight Division for BeltColor 8 */
| 10 | 6 | /* groups different Weight Division for BeltColor 8 */
| 10 | 7 | /* groups different Weight Division for BeltColor 8 */
| 10 | 8 | /* groups different Weight Division for BeltColor 8 */
Я понятия не имею, как вставить в CategorySet
, захватить его сгенерированный идентификатор, а затем использовать его для вставки в CategorySet_Category
Надеюсь, я сделал свои намерения ясными.
Я также создал SQLFiddle.
Изменить 1: Я прокомментировал в ответе Яцека, что это будет работать только один раз, но это ложь. Он будет работать пару раз в неделю. У меня есть возможность запускаться как команда SQL из С# или хранимой процедуры. Производительность не имеет решающего значения.
Изменить 2: Яцек предложил использовать SCOPE_IDENTITY
для возврата идентификатора. Проблема в том, что SCOPE_IDENTITY
возвращает только последний вставленный идентификатор, и я вставляю несколько строк в CategorySet
.
Изменить 3: Отвечать на @FutbolFan, который спросил, как извлекается FakeResultSet.
Это таблица CategoriesOption
(Id, Price_Id, MaxAthletesByTeam)
И таблицы CategoriesOptionBeltColor
, CategoriesOptionAgeDivision
, CategoriesOptionWeightDivison
, CategoriesOptionGender
. Эти четыре таблицы в основном одинаковы (Id, CategoriesOption_Id, Value).
Запрос выглядит следующим образом:
SELECT * FROM CategoriesOption co
LEFT JOIN CategoriesOptionAgeDivision ON
CategoriesOptionAgeDivision.CategoriesOption_Id = co.Id
LEFT JOIN CategoriesOptionBeltColor ON
CategoriesOptionBeltColor.CategoriesOption_Id = co.Id
LEFT JOIN CategoriesOptionGender ON
CategoriesOptionGender.CategoriesOption_Id = co.Id
LEFT JOIN CategoriesOptionWeightDivision ON
CategoriesOptionWeightDivision.CategoriesOption_Id = co.Id
Ответы
Ответ 1
Решение, описанное здесь, будет корректно работать в многопользовательской среде, а таблицы назначения CategorySet
и CategorySet_Category
не будут пустыми.
Я использовал схему и образцы данных из SQL Fiddle.
Первая часть прямолинейная
(ab) используйте MERGE
с предложением OUTPUT
.
MERGE
могут INSERT
, UPDATE
и DELETE
строки. В нашем случае нам нужно только INSERT
. 1=0
всегда false, поэтому часть NOT MATCHED BY TARGET
всегда выполняется. В общем, могут быть другие отрасли, см. Документы. WHEN MATCHED
обычно используется для UPDATE
; WHEN NOT MATCHED BY SOURCE
обычно используется для DELETE
, но мы не нуждаемся в них здесь.
Эта запутанная форма MERGE
эквивалентна простому INSERT
, но в отличие от простого INSERT
предложение OUTPUT
позволяет ссылаться на нужные нам столбцы.
MERGE INTO CategorySet
USING
(
SELECT
FakeResultSet.Championship_Id
,FakeResultSet.Price_Id
,FakeResultSet.MaxAthletesByTeam
,Category.Id AS Category_Id
FROM
FakeResultSet
INNER JOIN Category ON
Category.AgeDivision_Id = FakeResultSet.AgeDivision_Id AND
Category.Gender = FakeResultSet.Gender AND
Category.BeltColor = FakeResultSet.BeltColor AND
Category.WeightDivision = FakeResultSet.WeightDivision
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT
(Championship_Id
,Price_Id
,MaxAthletesByTeam)
VALUES
(Src.Championship_Id
,Src.Price_Id
,Src.MaxAthletesByTeam)
OUTPUT inserted.id AS CategorySet_Id, Src.Category_Id
INTO CategorySet_Category (CategorySet_Id, Category_Id)
;
FakeResultSet
соединяется с Category
, чтобы получить Category.id
для каждой строки FakeResultSet
. Предполагается, что Category
имеет уникальные комбинации AgeDivision_Id, Gender, BeltColor, WeightDivision
.
В разделе OUTPUT
нам нужны столбцы из исходных и целевых таблиц. Предложение OUTPUT
в простой инструкции INSERT
не предоставляет их, поэтому мы используем MERGE
здесь.
В приведенном выше запросе MERGE
вставка 8 строк в CategorySet
и вставка 8 строк в CategorySet_Category
с использованием сгенерированных идентификаторов.
Вторая часть
требуется временная таблица. Я буду использовать переменную таблицы для хранения сгенерированных идентификаторов.
DECLARE @T TABLE (
CategorySet_Id int
,AgeDivision_Id int
,Gender int
,BeltColor int);
Нам нужно запомнить сгенерированный CategorySet_Id
вместе с комбинацией AgeDivision_Id, Gender, BeltColor
, которая вызвала его.
MERGE INTO CategorySet
USING
(
SELECT
FakeResultSet.Championship_Id
,FakeResultSet.Price_Id
,FakeResultSet.MaxAthletesByTeam
,FakeResultSet.AgeDivision_Id
,FakeResultSet.Gender
,FakeResultSet.BeltColor
FROM
FakeResultSet
GROUP BY
FakeResultSet.Championship_Id
,FakeResultSet.Price_Id
,FakeResultSet.MaxAthletesByTeam
,FakeResultSet.AgeDivision_Id
,FakeResultSet.Gender
,FakeResultSet.BeltColor
) AS Src
ON 1 = 0
WHEN NOT MATCHED BY TARGET THEN
INSERT
(Championship_Id
,Price_Id
,MaxAthletesByTeam)
VALUES
(Src.Championship_Id
,Src.Price_Id
,Src.MaxAthletesByTeam)
OUTPUT
inserted.id AS CategorySet_Id
,Src.AgeDivision_Id
,Src.Gender
,Src.BeltColor
INTO @T(CategorySet_Id, AgeDivision_Id, Gender, BeltColor)
;
MERGE
выше будет группировать FakeResultSet
по мере необходимости и вставлять 2 строки в CategorySet
и 2 строки в @T
.
Затем присоедините @T
к Category
, чтобы получить Category.IDs
:
INSERT INTO CategorySet_Category (CategorySet_Id, Category_Id)
SELECT
TT.CategorySet_Id
,Category.Id AS Category_Id
FROM
@T AS TT
INNER JOIN Category ON
Category.AgeDivision_Id = TT.AgeDivision_Id AND
Category.Gender = TT.Gender AND
Category.BeltColor = TT.BeltColor
;
Это вставляет 8 строк в CategorySet_Category
.
Ответ 2
Вот не полный ответ, но направление, которое вы можете использовать для решения этой проблемы:
1-й запрос:
select row_number() over(order by t, Id) as n, Championship_Id
from (
select distinct 0 as t, b.Id, a.Championship_Id
from FakeResultSet as a
inner join
Category as b
on
a.AgeDivision_Id=b.AgeDivision_Id and
a.Gender=b.Gender and
a.BeltColor=b.BeltColor and
a.WeightDivision=b.WeightDivision
union all
select distinct 1, BeltColor, Championship_Id
from FakeResultSet
) as q
Второй запрос:
select q2.CategorySet_Id, c.Id as Category_Id from (
select row_number() over(order by t, Id) as CategorySet_Id, Id, BeltColor
from (
select distinct 0 as t, b.Id, null as BeltColor
from FakeResultSet as a
inner join
Category as b
on
a.AgeDivision_Id=b.AgeDivision_Id and
a.Gender=b.Gender and
a.BeltColor=b.BeltColor and
a.WeightDivision=b.WeightDivision
union all
select distinct 1, BeltColor, BeltColor
from FakeResultSet
) as q
) as q2
inner join
Category as c
on
(q2.BeltColor is null and q2.Id=c.Id)
OR
(q2.BeltColor = c.BeltColor)
конечно, это будет работать только для пустых таблиц CategorySet
и CategorySet_Category
, но вы можете использовать select coalese(max(Id), 0) from CategorySet
для получения текущего номера и добавления его в row_number
, таким образом, вы получите реальный идентификатор, который будет вставлен в Строка CategorySet для второго запроса
Ответ 3
То, что я делаю, когда сталкиваюсь с этими ситуациями, заключается в создании одной или нескольких временных таблиц с помощью row_number() над предложениями, дающими мне идентификаторы во временных таблицах. Затем я проверяю наличие каждой записи в фактических таблицах, и если они существуют, обновите временную таблицу с фактическими идентификаторами записей. Наконец, я запустил цикл while во временных таблицах, в которых отсутствует фактический идентификатор и вставляем их по одному, после вставки я обновляю временную запись таблицы с фактическими идентификаторами. Это позволяет вам управлять всеми данными контролируемым образом.
Ответ 4
@@IDENTITY - ваш друг во второй части вопроса
https://msdn.microsoft.com/en-us/library/ms187342.aspx
а также
Лучший способ получить идентификатор вставленной строки?
Некоторые API (драйверы) возвращают функцию int из функции update(), то есть идентификатор, если он "вставляет". Какой API/среда вы используете?
Я не понимаю 1-ю проблему. Вы не должны вставлять столбец идентификаторов.
Ответ 5
Ниже запрос даст конечный результат. Для строк CategorySet:
SELECT
ROW_NUMBER () OVER (PARTITION BY Championship_Id ORDER BY Championship_Id) RNK,
Championship_Id
FROM
(
SELECT
Championship_Id
,BeltColor
FROM #FakeResultSet
UNION ALL
SELECT
Championship_Id,BeltColor
FROM #FakeResultSet
GROUP BY Championship_Id,BeltColor
)BASE