it-swarm.com.ru

автоинкремент на составном первичном ключе

У меня есть таблица под названием "Рабочие пространства", где столбцы "AreaID" и "SurfaceID" работают как составной первичный ключ. AreaID ссылается на другую таблицу под названием "Areas", в которой в качестве первичного ключа используется только AreaID. То, что я хочу сделать сейчас, это сделать, чтобы SurfaceID восстанавливался от 1 на каждом новом AreaID. Прямо сейчас я использую следующий код для таблиц "Области" и "Рабочие пространства":

--Table 'Areas'
CREATE TABLE Areas (
AreaID INT IDENTITY(1,1) PRIMARY KEY,
Areaname VARCHAR(60) UNIQUE NOT NULL
)

--Table 'Workspaces'
CREATE TABLE Workspaces (
AreaID INT
CONSTRAINT ck_a_areaid REFERENCES Areas(AreaID)
ON DELETE CASCADE
ON UPDATE NO ACTION,
SurfaceID INT IDENTITY(1,1)
CONSTRAINT ck_surfaceid CHECK (surfaceid > 0 AND surfaceid < 1001),
Description VARCHAR(300) NOT NULL,
CONSTRAINT ck_workspaces PRIMARY KEY (AreaID, SurfaceID)
)

Когда я использую приведенный выше код, я получаю такой результат при создании новых рабочих областей в разных областях:

AreaID    SurfaceID
1         1
1         2
1         3
2         4
2         5
3         6
Etc...

Но я хочу, чтобы SurfaceID пересчитывался с 1 на каждый новый areaID, поэтому мой желаемый результат будет таким:

AreaID    SurfaceID
1         1
1         2
1         3
2         1
2         2
3         1
Etc...

Кто-нибудь знает, как это можно исправить?

11
xerzina

Я согласен с ответ г-на Линоффа но если вы хотите сохранить его физически, вы можете сделать это с помощью insert trigger:

Update Your_Table
set SurfaceID =  ( select max(isnull(SurfaceID,0))+1 as max 
                  from Workspaces t
                  where t.AreaID = INSERTED.AreaID )

РЕДАКТИРОВАТЬ: * (в качестве примера хотел узнать, как его реализовать)

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

Пример таблицы:

CREATE TABLE testTbl 
(
    AreaID INT,
    SurfaceID INT, --we want this to be auto increment per specific AreaID 
    Dsc VARCHAR(60)NOT NULL
)

Триггер:

CREATE TRIGGER TRG
ON testTbl
INSTEAD OF INSERT

AS

DECLARE @sid INT
DECLARE @iid INT
DECLARE @dsc VARCHAR(60)

SELECT @iid=AreaID FROM INSERTED
SELECT @dsc=DSC FROM INSERTED

--check if inserted AreaID exists in table -for setting SurfaceID
IF NOT EXISTS (SELECT * FROM testTbl WHERE [email protected])
SET @sid=1
ELSE
SET @sid=(  SELECT MAX(T.SurfaceID)+1 
            FROM testTbl T
            WHERE [email protected]
          )

INSERT INTO testTbl (AreaID,SurfaceID,Dsc)
            VALUES  (@iid,@sid,@dsc)

Вставка:

INSERT INTO testTbl(AreaID,Dsc) VALUES (1,'V1');
INSERT INTO testTbl(AreaID,Dsc) VALUES (1,'V2');
INSERT INTO testTbl(AreaID,Dsc) VALUES (1,'V3');
INSERT INTO testTbl(AreaID,Dsc) VALUES (2,'V4');
INSERT INTO testTbl(AreaID,Dsc) VALUES (2,'V5');
INSERT INTO testTbl(AreaID,Dsc) VALUES (2,'V6');
INSERT INTO testTbl(AreaID,Dsc) VALUES (2,'V7');
INSERT INTO testTbl(AreaID,Dsc) VALUES (3,'V8');
INSERT INTO testTbl(AreaID,Dsc) VALUES (4,'V9');
INSERT INTO testTbl(AreaID,Dsc) VALUES (4,'V10');
INSERT INTO testTbl(AreaID,Dsc) VALUES (4,'V11');
INSERT INTO testTbl(AreaID,Dsc) VALUES (4,'V12');

Проверьте значения:

SELECT * FROM testTbl

Выход:

AreaID  SurfaceID   Dsc
   1       1        V1
   1       2        V2
   1       3        V3
   2       1        V4
   2       2        V5
   2       3        V6
   2       4        V7
   3       1        V8
   4       1        V9
   4       2        V10
   4       3        V11
   4       4        V12

ВАЖНОЕ ЗАМЕЧАНИЕ: этот триггер не обрабатывает многострочную вставку один раз , и это необходимо вставить одну запись один раз, как в примере. для обработки вставки нескольких записей необходимо изменить тело и использовать row_number

2
Null

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

CREATE TABLE Workspaces (
    WorkspacesId int not null identity(1, 1) primary key,
    AreaID INT,
    Description VARCHAR(300) NOT NULL,
    CONSTRAINT ck_a_areaid REFERENCES Areas(AreaID) ON DELETE CASCADE ON UPDATE NO ACTION,
);

Затем, когда вы запрашиваете (или в представлении):

select w.*, row_number() over (partition by areaId
                               order by WorkspaceId) as SurfaceId
from Workspaces

Примечание: это не проверяет максимальное значение surfaceId. Если вам действительно нужно реализовать эту логику, вам нужно будет использовать триггеры.

4
Gordon Linoff

Вот решение, которое работает с несколькими строками.

Спасибо jFun за работу, проделанную для вставки в одну строку, но триггер не совсем безопасен в использовании.

ОК, предполагая эту таблицу:

create table TestingTransactions (
   id int identity,
   transactionNo int null,
   contract_id int not null,
   Data1 varchar(10) null, 
   Data2 varchar(10) null
);

В моем случае мне нужно было "транзакция №", чтобы всегда иметь правильное следующее значение для каждого КОНТРАКТА. Для меня в устаревшей финансовой системе важно, чтобы в транзакции не было пробелов.

Итак, нам нужен следующий триггер для обеспечения ссылочной целостности для столбца транзакции.

CREATE TRIGGER dbo.Trigger_TransactionNo_Integrity 
   ON  dbo.TestingTransactions 
   INSTEAD OF INSERT
AS 
BEGIN
    -- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    -- Discard any incoming transactionNo's and ensure the correct one is used.
    WITH trans 
         AS (SELECT F.*, 
                    Row_number() 
                      OVER ( 
                        ORDER BY contract_id) AS RowNum, 
                    A.* 
             FROM   inserted F 
                    CROSS apply (SELECT Isnull(Max(transactionno), 0) AS 
                                        LastTransaction 
                                 FROM   dbo.testingtransactions 
                                 WHERE  contract_id = F.contract_id) A), 
         newtrans 
         AS (SELECT T.*, 
                    NT.minrowforcontract, 
                    ( 1 + lasttransaction + ( rownum - NT.minrowforcontract ) ) AS 
                       NewTransactionNo 
             FROM   trans t 
                    CROSS apply (SELECT Min(rownum) AS MinRowForContract 
                                 FROM   trans 
                                 WHERE  T.contract_id = contract_id) NT) 
    INSERT INTO dbo.testingtransactions 
    SELECT Isnull(newtransactionno, 1) AS TransactionNo, 
           contract_id, 
           data1, 
           data2 
    FROM   newtrans 
END
GO

Хорошо, я признаю, что это довольно сложный триггер, в котором описаны почти все приемы из этой книги, но эта версия должна работать вплоть до SQL 2005. В сценарии используются 2 CTE, 2 перекрестных применения и Row_Num () поверх отработать правильный "следующий" TransactionNo для всех строк в Inserted.

Он работает с использованием триггера instead of insert и отбрасывает все входящие транзакции No и заменяет их на "NEXT" транзакции No.

Итак, теперь мы можем запустить эти обновления:

delete from dbo.TestingTransactions
insert into dbo.TestingTransactions (transactionNo, Contract_id, Data1)
values (7,213123,'Blah')

insert into dbo.TestingTransactions (transactionNo, Contract_id, Data2)
values (7,333333,'Blah Blah')

insert into dbo.TestingTransactions (transactionNo, Contract_id, Data1)
values (333,333333,'Blah Blah')

insert into dbo.TestingTransactions (transactionNo, Contract_id, Data2)
select 333  ,333333,'Blah Blah' UNION All
select 99999,44443,'Blah Blah' UNION All
select 22,   44443 ,'1' UNION All
select 29,   44443 ,'2' UNION All
select 1,    44443 ,'3'

select * from dbo.TestingTransactions
order by Contract_id,TransactionNo

Мы обновляем отдельные строки и несколько строк со смешанными номерами контрактов - но правильный TransactionNo переопределяет переданное значение, и мы получаем ожидаемый результат:

id  transactionNo  contract_id  Data1       Data2
117             1        44443  NULL        Blah Blah
118             2        44443  NULL        1
119             3        44443  NULL        2
120             4        44443  NULL        3
114             1       213123  Blah        NULL
115             1       333333  NULL        Blah Blah
116             2       333333  Blah Blah   NULL
121             3       333333  NULL        Blah Blah

Мне интересны мнения людей относительно параллелизма. Я почти уверен, что два CTE будут рассматриваться как один проход, поэтому я на 99,99% уверен, что ссылочная целостность всегда будет сохраняться.

1
soddoff Baldrick