একটি ডাটাবেসে "কমপক্ষে একটি" বা "ঠিক এক" প্রয়োগ করার প্রতিবন্ধক


24

বলুন আমাদের ব্যবহারকারী রয়েছে এবং প্রতিটি ব্যবহারকারীর একাধিক ইমেল ঠিকানা থাকতে পারে

CREATE TABLE emails (
    user_id integer,
    email_address text,
    is_active boolean
)

কিছু নমুনা সারি

user_id | email_address | is_active
1       | foo@bar.com   | t
1       | baz@bar.com   | f
1       | bar@foo.com   | f
2       | ccc@ddd.com   | t

আমি প্রতিবন্ধকতা প্রয়োগ করতে চাই যে প্রতিটি ব্যবহারকারীর ঠিক একটি সক্রিয় ঠিকানা রয়েছে। পোস্টগ্রিসে আমি কীভাবে এটি করতে পারি? আমি এটি করতে পারি:

CREATE UNIQUE INDEX "user_email" ON emails(user_id) WHERE is_active=true;

যা ব্যবহারকারীর একাধিক সক্রিয় ঠিকানা থাকা থেকে রক্ষা করবে, তবে আমি বিশ্বাস করি, তাদের ঠিকানাগুলি সমস্ত মিথ্যা হিসাবে সেট করা থেকে রক্ষা করবে না।

সম্ভব হলে আমি ট্রিগার বা একটি pl / pgsql স্ক্রিপ্ট এড়াতে পছন্দ করতাম, কারণ বর্তমানে আমাদের কাছে এর কোনও নেই এবং এটি সেট আপ করা কঠিন would তবে আমি যদি এটির বিষয়টি জানি তবে "এটি করার একমাত্র উপায় একটি ট্রিগার বা pl / pgsql দ্বারা" জানার প্রশংসা করব।

উত্তর:


17

আপনার মোটেই ট্রিগার বা পিএল / পিজিএসকিউএল দরকার নেই।
এমনকি আপনার বাধাও দরকার DEFERRABLE নেই।
এবং আপনার কোনও তথ্য অপ্রয়োজনীয়ভাবে সংরক্ষণ করার দরকার নেই।

usersসারণিতে সক্রিয় ইমেলের আইডি অন্তর্ভুক্ত করুন , যার ফলে পারস্পরিক রেফারেন্স আসে। কেউ ভাবতে পারেন যে DEFERRABLEএকজন ব্যবহারকারী এবং তার সক্রিয় ইমেল ofোকানোর মুরগি এবং ডিমের সমস্যা সমাধানের জন্য আমাদের বাধা প্রয়োজন , তবে ডেটা-মডিফাইং সিটিই ব্যবহার করে আমাদের এটির প্রয়োজনও নেই।

এটি সর্বদা ব্যবহারকারী প্রতি ঠিক একটি সক্রিয় ইমেল প্রয়োগ করে :

CREATE TABLE users (
  user_id  serial PRIMARY KEY
, username text NOT NULL
, email_id int NOT NULL  -- FK to active email, constraint added below
);

CREATE TABLE email (
  email_id serial PRIMARY KEY
, user_id  int NOT NULL REFERENCES users ON DELETE CASCADE ON UPDATE CASCADE 
, email    text NOT NULL
, CONSTRAINT email_fk_uni UNIQUE(user_id, email_id)  -- for FK constraint below
);

ALTER TABLE users ADD CONSTRAINT active_email_fkey
FOREIGN KEY (user_id, email_id) REFERENCES email(user_id, email_id);

"সর্বাধিক একটি সক্রিয় ইমেল" তৈরি করতে এই NOT NULLসীমাবদ্ধতাটি সরিয়ে দিন users.email_id। (আপনি এখনও প্রতি ব্যবহারকারী একাধিক ইমেল সঞ্চয় করতে পারেন, তবে এর মধ্যে কোনওটিই "সক্রিয়" নয়))

আপনি করতে পারেন করতে active_email_fkey DEFERRABLEআরো অতি সামান্য বিচ্যুতি (পৃথক কমান্ড ব্যবহারকারী এবং ইমেল সন্নিবেশ করার অনুমতি একই লেনদেনের), কিন্তু যে প্রয়োজন হয় না

আমি সূচক কভারেজটি অনুকূল user_idকরতে UNIQUEসীমাবদ্ধতায় প্রথমে রেখেছি email_fk_uni। বিবরণ:

Viewচ্ছিক দর্শন:

CREATE VIEW user_with_active_email AS
SELECT * FROM users JOIN email USING (user_id, email_id);

আপনি কীভাবে সক্রিয় ইমেল দিয়ে নতুন ব্যবহারকারীদের প্রবেশ করান তা এখানে (প্রয়োজনীয় হিসাবে):

WITH new_data(username, email) AS (
   VALUES
      ('usr1', 'abc@d.com')   -- new users with *1* active email
    , ('usr2', 'def3@d.com')
    , ('usr3', 'ghi1@d.com')
   )
, u AS (
   INSERT INTO users(username, email_id)
   SELECT n.username, nextval('email_email_id_seq'::regclass)
   FROM   new_data n
   RETURNING *
   )
INSERT INTO email(email_id, user_id, email)
SELECT u.email_id, u.user_id, n.email
FROM   u
JOIN   new_data n USING (username);

সুনির্দিষ্ট অসুবিধাটি হ'ল আমাদের আর user_idনা email_idশুরু করার দরকার to উভয়ই স্ব স্ব সম্পর্কিত ক্রমিক সংখ্যা SEQUENCE। এটি একটি একক RETURNINGধারা (অন্য মুরগী ​​এবং ডিমের সমস্যা) দিয়ে সমাধান করা যায় না। সমাধান nextval()হিসাবে নীচে লিঙ্ক উত্তরে বিস্তারিতভাবে ব্যাখ্যা

আপনি যদি কলামটির সাথে সংযুক্ত ক্রমের নামটি না জানেন তবে আপনি প্রতিস্থাপন করতে পারেন:serialemail.email_id

nextval('email_email_id_seq'::regclass)

সঙ্গে

nextval(pg_get_serial_sequence('email', 'email_id'))

আপনি কীভাবে একটি নতুন "সক্রিয়" ইমেল যুক্ত করবেন তা এখানে:

WITH e AS (
   INSERT INTO email (user_id, email)
   VALUES  (3, 'new_active@d.com')
   RETURNING *
   )
UPDATE users u
SET    email_id = e.email_id
FROM   e
WHERE  u.user_id = e.user_id;

এসকিউএল ফিডল।

কিছু সাধারণ-মনের ওআরএম যদি এটিকে মোকাবেলার জন্য যথেষ্ট স্মার্ট না হয় তবে আপনি সার্ভার-সাইড ফাংশনে এসকিউএল কমান্ডগুলি সজ্জিত করতে পারেন।

নিবিড়ভাবে সম্পর্কিত, যথেষ্ট ব্যাখ্যা সহ:

সম্পর্কিত:

DEFERRABLEবাধা সম্পর্কে :

সম্পর্কে nextval()এবং pg_get_serial_sequence():


এটি কি কমপক্ষে এক সম্পর্কে 1 টি প্রয়োগ করা যেতে পারে? এই উত্তরে প্রদর্শিত হিসাবে 1 -1 নয়।
সিএমসিডিগ্র্যাগনকাই

@ সিএমসিডিগ্রাগনকাই: হ্যাঁ ব্যবহারকারী হিসাবে ঠিক এক সক্রিয় ইমেল প্রয়োগ করা হয়। কিছুই আপনাকে একই ব্যবহারকারীর জন্য আরও (অ-সক্রিয়) ইমেল যোগ করা থেকে বিরত রাখে না। আপনি যদি সক্রিয় ইমেলের জন্য বিশেষ ভূমিকা না চান তবে ট্রিগারগুলি একটি (কম কঠোর) বিকল্প হতে পারে। তবে আপনাকে সমস্ত আপডেট এবং ডিলিটগুলি কভার করতে সাবধান থাকতে হবে। আপনার যদি প্রয়োজন হয় তবে একটি প্রশ্ন জিজ্ঞাসা করুন ।
এরউইন ব্র্যান্ডসটেটার

ব্যবহারকারীরা ব্যবহার না করে মুছে ফেলার কোনও উপায় আছে কি ON DELETE CASCADE? শুধু কৌতূহলী (ক্যাসকেডিং আপাতত ভাল কাজ করছে)।
amoe

@ এমো: বিভিন্ন উপায় আছে। ডেটা-সংশোধনকারী সিটিই, ট্রিগার, নিয়ম, একই লেনদেনে একাধিক বিবৃতি, ... এটি সমস্ত সঠিক প্রয়োজনের উপর নির্ভর করে। আপনার উত্তর প্রয়োজন হলে আপনার নির্দিষ্টকরণের সাথে একটি নতুন প্রশ্ন জিজ্ঞাসা করুন। আপনি প্রসঙ্গের জন্য সর্বদা এটির সাথে লিঙ্ক করতে পারেন।
এরউইন ব্র্যান্ডসটেটার

5

আপনি যদি টেবিলটিতে একটি কলাম যুক্ত করতে পারেন তবে নিম্নলিখিত স্কিমটি প্রায় 1 টি কাজ করবে:

CREATE TABLE emails 
(
    UserID integer NOT NULL,
    EmailAddress varchar(254) NOT NULL,
    IsActive boolean NOT NULL,

    -- New column
    ActiveAddress varchar(254) NOT NULL,

    -- Obvious PK
    CONSTRAINT PK_emails_UserID_EmailAddress
        PRIMARY KEY (UserID, EmailAddress),

    -- Validate that the active address row exists
    CONSTRAINT FK_emails_ActiveAddressExists
        FOREIGN KEY (UserID, ActiveAddress)
        REFERENCES emails (UserID, EmailAddress),

    -- Validate the IsActive value makes sense    
    CONSTRAINT CK_emails_Validate_IsActive
    CHECK 
    (
        (IsActive = true AND EmailAddress = ActiveAddress)
        OR
        (IsActive = false AND EmailAddress <> ActiveAddress)
    )
);

-- Enforce maximum of one active address per user
CREATE UNIQUE INDEX UQ_emails_One_IsActive_True_PerUser
ON emails (UserID, IsActive)
WHERE IsActive = true;

Test SQLFiddle

আমার_দেশীয় এসকিউএল সার্ভার থেকে a_horse_with_no_name সাহায্যে অনুবাদ করেছেন

ইয়পারকিউব যেমন একটি মন্তব্যে উল্লিখিত হয়েছে, আপনি আরও যেতে পারেন:

  • বুলিয়ান কলামটি ফেলে দিন; এবং
  • তৈরি করুন UNIQUE INDEX ON emails (UserID) WHERE (EmailAddress = ActiveAddress)

প্রভাব একই, তবে এটি তর্কযোগ্যভাবে সহজ এবং আরও সুন্দর।


1 সমস্যাটি হ'ল বিদ্যমান সীমাবদ্ধতাগুলি কেবল এটি নিশ্চিত করে যে অন্য সারি দ্বারা 'সক্রিয়' হিসাবে উল্লেখ করা একটি সারি উপস্থিত রয়েছে , এটি আসলে সক্রিয়ও নয়। আমি নিজেকে অতিরিক্ত বাধা প্রয়োগ করার জন্য পোস্টগ্র্রেসকে যথেষ্ট ভালভাবে জানি না (কমপক্ষে এখনই ঠিক নয়), তবে এসকিউএল সার্ভারে এটি এভাবে করা যেতে পারে:

CREATE TABLE Emails 
(
    EmailID integer NOT NULL UNIQUE,
    UserID integer NOT NULL,
    EmailAddress varchar(254) NOT NULL,
    IsActive bit NOT NULL,

    -- New columns
    ActiveEmailID integer NOT NULL,
    ActiveIsActive AS CONVERT(bit, 'true') PERSISTED,

    -- Obvious PK
    CONSTRAINT PK_emails_UserID_EmailAddress
        PRIMARY KEY (UserID, EmailID),

    CONSTRAINT UQ_emails_UserID_EmailAddress_IsActive
        UNIQUE (UserID, EmailID, IsActive),

    -- Validate that the active address exists and is active
    CONSTRAINT FK_emails_ActiveAddressExists_And_IsActive
        FOREIGN KEY (UserID, ActiveEmailID, ActiveIsActive)
        REFERENCES emails (UserID, EmailID, IsActive),

    -- Validate the IsActive value makes sense    
    CONSTRAINT CK_emails_Validate_IsActive
    CHECK 
    (
        (IsActive = 'true' AND EmailID = ActiveEmailID)
        OR
        (IsActive = 'false' AND EmailID <> ActiveEmailID)
    )
);

-- Enforce maximum of one active address per user
CREATE UNIQUE INDEX UQ_emails_One_IsActive_PerUser
ON emails (UserID, IsActive)
WHERE IsActive = 'true';

এই প্রচেষ্টাটি সম্পূর্ণ ইমেল ঠিকানাটির সদৃশ করার পরিবর্তে একটি সারোগেট ব্যবহার করে কিছুটা উন্নত করে।


4

স্কিমা পরিবর্তন ছাড়াই এগুলির দুটিরই একমাত্র উপায় হ'ল পিএল / পিজিএসকিউএল ট্রিগার।

"হুবুহু এক" কেসটির জন্য, আপনি উল্লেখগুলি একটির সাথে পারস্পরিক করতে পারেন DEFERRABLE INITIALLY DEFERRED। সুতরাং A.b_id(এফকে) রেফারেন্স B.b_id(পিকে) এবং B.a_id(এফকে) রেফারেন্স A.a_id(পিকে)। অনেকগুলি ওআরএম ইত্যাদি যদিও পিছিয়ে থাকা বাধাগুলির সাথে লড়াই করতে পারে না। তাই এই ক্ষেত্রে আপনি একটি deferrable এফ কে ব্যবহারকারীর কাছ থেকে ঠিকানায় একটি কলাম যোগ চাই active_address_id, পরিবর্তে একটি ব্যবহারের activeথাকা ফ্ল্যাগ address


এফকেও হতে হবে না DEFERRABLE
এরউইন ব্র্যান্ডস্টেটার
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.