Учитывая ваше ожидание около 20
размеров, пример ограничен 60
. Требуется контролируемый процесс для определения каждого набора измерений (серии).
Обоснование
-- DIM is a valid numeric identifier for a dimension.
--
valid_dim {DIM}
PK {DIM}
CHECK ((DIM = 1) OR ((DIM > 1) AND (mod(DIM,2) = 0)))
-- data sample
(DIM)
---------
(2^0)
, (2^1)
, (2^2)
, ...
, (2^58)
, (2^59)
-- Dimension DIM, named DIM_NAME exists.
--
dimension {DIM, DIM_NAME}
PK {DIM}
AK {DIM_NAME}
FK {DIM} REFERENCES valid_dim {DIM}
-- data sample
(DIM, DIM_NAME)
---------------
(2^0, 'FREQ')
, (2^1, 'GEOG_AREA')
, (2^2, 'UNIT')
, ...
, (2^58, 'AGE_GROUP')
, (2^59, 'HAIR_COLOR')
Загрузка series
и ser_dim
может быть сделано из функции, приложения или чего-либо еще. Однако это должен быть контролируемый процесс.
SER
уникален для данного набора измерений. Обратите внимание, что |
является побитовым OR
оператором.
-- Series SER, named SER_NAME exists.
--
series {SER, SER_NAME}
PK {SER}
AK {SER_NAME}
-- data sample
(SER, SER_NAME)
--------------------------------
((2^0 | 2^1 | 2^2) , 'F-G-U')
, ((2^1 | 2^58) , 'G-A' )
, ((2^0 | 2^58 | 2^59), 'F-A-H')
-- Series SER has dimension DIM.
--
ser_dim {SER, DIM}
PK {SER, DIM}
FK1 {SER} REFERENCES series {SER}
FK2 {DIM} REFERENCES dimension {DIM}
CHECK ((DIM & SER) = DIM)
-- data sample
(SER, DIM)
--------------------------------
((2^0 | 2^1 | 2^2) , 2^0)
, ((2^0 | 2^1 | 2^2) , 2^1)
, ((2^0 | 2^1 | 2^2) , 2^2)
, ((2^1 | 2^58) , 2^1 )
, ((2^1 | 2^58) , 2^58)
, ((2^0 | 2^58 | 2^59), 2^0)
, ((2^0 | 2^58 | 2^59), 2^58)
, ((2^0 | 2^58 | 2^59), 2^59)
Примечание:
All attributes (columns) NOT NULL
PK = Primary Key
AK = Alternate Key (Unique)
FK = Foreign Key
PostgreSQL
-- DIM is a valid numeric identifier
-- for a dimension.
--
CREATE TABLE valid_dim (
DIM bigint NOT NULL
, CONSTRAINT pk_valid_dim PRIMARY KEY (DIM)
, CONSTRAINT chk_valid_dim
CHECK ( (DIM = 1)
OR ( (DIM > 1)
AND (mod(DIM, 2) = 0) )
)
);
-- define some of valid DIMs
INSERT INTO valid_dim (DIM)
VALUES
((2^ 0)::bigint)
, ((2^ 1)::bigint)
, ((2^ 2)::bigint)
-- fill this gap
, ((2^58)::bigint)
, ((2^59)::bigint) ;
-- Dimension DIM, named DIM_NAME exists.
--
CREATE TABLE dimension (
DIM bigint NOT NULL
, DIM_NAME text NOT NULL
, CONSTRAINT pk_dim PRIMARY KEY (DIM)
, CONSTRAINT ak_dim UNIQUE (DIM_NAME)
, CONSTRAINT
fk_dim FOREIGN KEY (DIM)
REFERENCES valid_dim (DIM)
);
-- define few dimensions
INSERT INTO dimension (DIM, DIM_NAME)
VALUES
((2^ 0)::bigint, 'FREQ')
, ((2^ 1)::bigint, 'GEOG_AREA')
, ((2^ 2)::bigint, 'UNIT')
, ((2^58)::bigint, 'AGE_GROUP')
, ((2^59)::bigint, 'HAIR_COLOR') ;
-- Series SER, named SER_NAME exists.
--
CREATE TABLE series (
SER bigint NOT NULL
, SER_NAME text NOT NULL
, CONSTRAINT pk_series PRIMARY KEY (SER)
, CONSTRAINT ak_series UNIQUE (SER_NAME)
);
-- define three series
INSERT INTO series (SER, SER_NAME)
SELECT bit_or(DIM) as SER, 'F-G-U' as SER_NAME
FROM dimension
WHERE DIM_NAME IN ('FREQ', 'GEOG_AREA', 'UNIT')
UNION
SELECT bit_or(DIM) as SER, 'G-A' as SER_NAME
FROM dimension
WHERE DIM_NAME IN ('GEOG_AREA', 'AGE_GROUP')
UNION
SELECT bit_or(DIM) as SER, 'F-A-H' as SER_NAME
FROM dimension
WHERE DIM_NAME IN ('FREQ', 'AGE_GROUP', 'HAIR_COLOR') ;
-- Series SER has dimension DIM.
--
CREATE TABLE ser_dim (
SER bigint NOT NULL
, DIM bigint NOT NULL
, CONSTRAINT pk_ser_dim PRIMARY KEY (SER, DIM)
, CONSTRAINT
fk1_ser_dim FOREIGN KEY (SER)
REFERENCES series (SER)
, CONSTRAINT
fk2_ser_dim FOREIGN KEY (DIM)
REFERENCES dimension (DIM)
, CONSTRAINT
chk_ser_dim CHECK ((DIM & SER) = DIM)
);
-- populate ser_dim
INSERT INTO ser_dim (SER, DIM)
SELECT SER, DIM
FROM series
JOIN dimension ON true
WHERE (DIM & SER) = DIM ;
Другой вариант - использовать (материализованное) представление для ser_dim
. Это зависит от остальной части модели: если FK
необходим для {SER, DIM}
хранения таблицы, в противном случае представление будет лучше.
-- An option, instead of the table.
--
CREATE VIEW ser_dim
AS
SELECT SER, DIM
FROM series
JOIN dimension ON true
WHERE (DIM & SER) = DIM ;
Test
-- Show already defined series
-- and their dimensions.
SELECT SER_NAME, DIM_NAME
FROM ser_dim
JOIN series USING (SER)
JOIN dimension USING (DIM)
ORDER BY SER_NAME, DIM_NAME ;
-- Get SER for a set of dimensions;
-- use this when defining a series.
SELECT bit_or(DIM) AS SER
FROM dimension
WHERE DIM_NAME IN ('FREQ', 'GEOG_AREA', 'UNIT') ;
-- Find already defined series,
-- given a set of dimensions.
SELECT x.SER
FROM (
SELECT bit_or(DIM) AS SER
FROM dimension
WHERE DIM_NAME IN ('FREQ', 'GEOG_AREA', 'UNIT')
) AS x
WHERE EXISTS
(SELECT 1 FROM series AS s WHERE s.SER = x.SER) ;
Резюме
К сожалению, стандартные реализации SQL не поддерживают утверждения, ограничения для всей базы данных. SQL Стандарт фактически определяет их, но пока не повезло. Следовательно, не все бизнес-ограничения могут быть выполнены элегантно в SQL, обычно требуется некоторая креативность и компромисс.