it-swarm.asia

كيف أقوم بإجبار سجل واحد على الحصول على قيمة حقيقية لعمود منطقي ، وجميع القيم الأخرى قيمة خاطئة؟

أريد فرض أن سجل واحد فقط في الجدول يعتبر القيمة "الافتراضية" للاستعلامات أو طرق العرض الأخرى التي قد تصل إلى هذا الجدول.

في الأساس ، أريد أن أضمن أن هذا الاستعلام سيعرض دائمًا صفًا واحدًا بالضبط:

SELECT ID, Zip 
FROM PostalCodes 
WHERE isDefault=True

كيف أفعل ذلك في SQL؟

20
emaynard

تحرير: هذا موجه إلى SQL Server قبل أن نعرف MySQL

يوجد 4 5 طرق للقيام بذلك: بالترتيب من الأكثر رغبة إلى الأقل رغبة

لا نعلم ما هو نظام إدارة قواعد البيانات الموزعة (RDBMS) إذا كان الأمر كذلك ، ولكن قد لا ينطبق ذلك على الجميع

  1. أفضل طريقة هي فهرس تمت تصفيته. يستخدم هذا DRI للحفاظ على التفرد.

  2. عمود محسوب بتفرد (انظر إجابة جاك دوجلاس) (أضيف بواسطة التعديل 2)

  3. عرض مفهرس/مادي يشبه الفهرس الذي تمت تصفيته باستخدام DRI

  4. الزناد (حسب الإجابات الأخرى)

  5. تحقق من القيد باستخدام UDF. هذا ليس آمنًا لعزل التزامن ولقطة. انظر واحداثنانثلاثةأربعة

لاحظ أن شرط "افتراضي واحد" موجود على الطاولة وليس الإجراء المخزن. سيكون للإجراء المخزن نفس مشاكل التزامن مثل قيد الشيك مع UUD

ملاحظة: سُئل في SO عدة مرات:

8
gbn

ملاحظة: تم تقديم هذه الإجابة قبل أن يتضح أن السائل يريد شيئًا محددًا لـ MySQL. هذه الإجابة منحازة إلى SQL Server.

تطبيق الاختيار القيد على الجدول الذي يستدعي UDF والتأكد من إرجاعه القيمة هي <= 1. يمكن لـ UDF فقط حساب الصفوف في الجدول حيث isDefault = TRUE. سيضمن ذلك عدم وجود أكثر من صف افتراضي واحد في الجدول.

يجب عليك إضافة صورة نقطية أو مصفاة فهرس على العمود isDefault (اعتمادًا على المنصة الخاصة بك) للتأكد من تشغيل UDF بسرعة كبيرة.

تحذيرات

  • تحقق القيود لها معينةقيود . على وجه التحديد ، يتم استدعاؤها لكل صف يتم تعديله ، حتى لو لم يتم الالتزام بهذه الصفوف حتى الآن في المعاملة. لذا ، إذا بدأت معاملة ، قم بتعيين صف جديد ليكون الافتراضي ثم قمت بإلغاء تعيين الصف القديم ، فسوف يشكو قيد الشيك. وذلك لأنه سيتم تنفيذه بعد أن تجعل الصف الجديد هو الافتراضي ، وتجد أن هناك الآن افتراضيان ، ويفشلان. الحل إذن هو التأكد من إلغاء تعيين الصف الافتراضي أولاً قبل تعيين افتراضي جديد ، حتى إذا كنت تقوم بهذا العمل في معاملة.
  • كما gbnالمشار إليه ، قد ترجع UDFs نتائج غير متناسقة إذا كنت تستخدم عزل اللقطة في SQL Server. ومع ذلك ، فإن UDF المضمّن لا يواجه هذه المشكلة ، وبما أن UDF الذي يقوم فقط بالعد قادر على التضمين ، فهذه ليست مشكلة هنا.
  • تتمثل قوة قوة معالجة محرك قاعدة البيانات في قدرته على العمل على مجموعات من البيانات في وقت واحد. مع قيد الاختيار المدعوم من UDF ، سيقتصر المحرك على تطبيق UDF هذا بشكل تسلسلي على كل صف يتم تعديله. في حالة استخدامك ، من غير المرجح أن تقوم بإجراء تحديثات جماعية على جدول PostalCodes ، ومع ذلك يظل هذا مصدر قلق للأداء في المواقف التي يُرجح فيها نشاط قائم على مجموعة.

نظرًا لكل هذه التحذيرات ، أوصي باستخدام اقتراح gbn من فهرس فريد تمت تصفيته بدلاً من قيد التحقق.

10
Nick Chammas

فيما يلي إجراء مخزَّن (لهجة MySQL):

DELIMITER $$
DROP PROCEDURE IF EXISTS SetDefaultForZip;
CREATE PROCEDURE SetDefaultForZip (NEWID INT)
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;

    SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
    IF FOUND_TRUE = 1 THEN
        SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
        IF NEWID <> OLDID THEN
            UPDATE PostalCode SET isDefault = FALSE WHERE ID = OLDID;
            UPDATE PostalCode SET isDefault = TRUE  WHERE ID = NEWID;
        END IF;
    ELSE
        UPDATE PostalCode SET isDefault = TRUE WHERE ID = NEWID;
    END IF;
END;
$$
DELIMITER ;

للتأكد من نظافة جدولك وعمل الإجراء المخزن ، بافتراض أن المعرف 200 هو الافتراضي ، قم بتنفيذ الخطوات التالية:

ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
CALL SetDefaultForZip(200);
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;

بدلاً من إجراء مخزّن ، ماذا عن الزناد؟

DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;

للتأكد من أن الجدول نظيف وأن المشغل يعمل ، بافتراض أن المعرف 200 هو الافتراضي ، قم بتنفيذ الخطوات التالية:

DROP TRIGGER postalcodes_bu;
ALTER TABLE PostalCode DROP INDEX isDefault_ndx;
UPDATE PostalCodes SET isDefault = FALSE;
ALTER TABLE PostalCode ADD INDEX isDefault_ndx (isDefault);
DELIMITER $$
CREATE TRIGGER postalcodes_bu BEFORE UPDATE ON PostalCodes FOR EACH ROW
BEGIN
    DECLARE FOUND_TRUE,OLDID INT;
    IF NEW.isDefault = TRUE THEN
        SELECT COUNT(1) INTO FOUND_TRUE FROM PostalCode WHERE isDefault = TRUE;
        IF FOUND_TRUE = 1 THEN
            SELECT ID INTO OLDID FROM PostalCode WHERE isDefault = TRUE;
            UPDATE PostalCodes SET isDefault = FALSE WHERE ID = OLDID;
        END IF;
    END IF;
END;
$$
DELIMITER ;
UPDATE PostalCodes SET isDefault = TRUE WHERE ID = 200;
SELECT ID FROM PostalCodes WHERE isDefault = TRUE;
4
RolandoMySQLDBA

يمكنك استخدام مشغل لتطبيق القواعد. عند تعيين عبارة UPDATE أو INSERT إلى الافتراضي ، يمكن لـ SQL في المشغل تعيين كافة الصفوف الأخرى إلى False.

سيتعين عليك مراعاة المواقف الأخرى عند تطبيق ذلك. على سبيل المثال ، ما الذي يجب أن يحدث إذا كان أكثر من صف واحد في مجموعات UPDATE أو INSERT هو الافتراضي إلى True؟ ما القواعد التي ستطبق لتجنب كسر القاعدة؟

أيضا ، هل من الممكن الشرط حيث لا يوجد تقصير؟ ماذا سيحدث عندما يقوم التحديث بتعيين isDefault من True إلى False.

بمجرد تحديد القواعد ، يمكنك إنشاؤها في الزناد.

بعد ذلك ، كما هو موضح في إجابة أخرى ، تريد تطبيق قيد التحقق للمساعدة في تطبيق القواعد.

3
bobs

فيما يلي إعداد مثال جدول وبيانات:

IF  EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[MyTable]') AND type in (N'U'))
DROP TABLE [dbo].[MyTable]
GO

CREATE TABLE dbo.MyTable
(
    [id] INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , [IsDefault] BIT DEFAULT 0
)
GO

INSERT dbo.MyTable DEFAULT VALUES
GO 100

إذا قمت بإنشاء إجراء مخزن لتعيين علامة IsDefault وإزالة الأذونات لتغيير الجدول الأساسي ، فيمكنك فرض ذلك باستخدام الاستعلام أدناه. قد يتطلب مسح جدول في كل مرة.

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END 
GO

بناءً على حجم الجدول ، قد يؤدي الفهرس الموجود في IsDefault (DESC) إلى استعلام واحد أو كليهما من الاستعلامات أدناه لتجنب المسح الكامل.

DECLARE @Id INT
SET @Id = 10

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  IsDefault = 1

UPDATE
    dbo.MyTable
SET
    IsDefault = CASE WHEN [id] = @Id THEN 1 ELSE 0 END
FROM
    dbo.MyTable
WHERE
    [id] = @Id
OR  ([id] != @Id AND IsDefault = 1)

إذا لم تتمكن من إزالة الأذونات من الجدول الأساسي ، فستحتاج إلى فرض التكامل بوسائل أخرى وتستخدم SQL2008 ، يمكنك استخدام فهرس فريد تمت تصفيته:

CREATE UNIQUE INDEX IX_MyTable_IsDefault  ON dbo.MyTable (IsDefault) WHERE IsDefault = 1
3
Mark Storey-Smith

بالنسبة إلى MySQL التي لا تحتوي على فهارس مُصفاة ، يمكنك فعل شيء مثل ما يلي. يستغل حقيقة أن MySQL يسمح بالعديد من القيم الفارغة في الفهارس الفريدة ، ويستخدم جدولًا مخصصًا ومفتاحًا خارجيًا لمحاكاة نوع بيانات يمكن أن يحتوي فقط على NULL و 1 كقيم.

باستخدام هذا الحل ، بدلاً من true/false ، ستستخدم 1/NULL فقط.

CREATE TABLE DefaultFlag (id TINYINT UNSIGNED NOT NULL PRIMARY KEY);
INSERT INTO DefaultFlag (id) VALUES (1);

CREATE TABLE PostalCodes (
    ID INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    Zip VARCHAR (32) NOT NULL,
    isDefault TINYINT UNSIGNED NULL DEFAULT NULL,
    CONSTRAINT FOREIGN KEY (isDefault) REFERENCES DefaultFlag (id),
    UNIQUE KEY (isDefault)
);

INSERT INTO PostalCodes (Zip, isDefault) VALUES ('123', 1   );
/* Only one row can be default */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('abc', 1   );
/* ERROR 1062 (23000): Duplicate entry '1' for key 'isDefault' */

/* MySQL allows multiple NULLs in unique indexes */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('456', NULL);
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', NULL);

/* only NULL and 1 are admitted in isDefault thanks to foreign key */
INSERT INTO PostalCodes (Zip, isDefault) VALUES ('789', 2   );
/* ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`PostalCodes`, CONSTRAINT `PostalCodes_ibfk_1` FOREIGN KEY (`isDefault`) REFERENCES `DefaultFlag` (`id`))*/

أعتقد أن الشيء الوحيد الذي يمكن أن يحدث بشكل خاطئ هو إذا قام شخص ما بإضافة صف إلى الجدول DefaultFlag. أعتقد أن هذا ليس مصدر قلق كبير ، ويمكنك دائمًا إصلاحه بالأذونات إذا كنت تريد حقًا أن تكون آمنًا.

1
djfm
SELECT ID, Zip FROM PostalCodes WHERE isDefault=True 

كان هذا في الأساس يسبب لك مشاكل لأنك وضعت الحقل في المكان الخطأ وهذا كل شيء.

ضع جدول "الإعدادات الافتراضية" ، مع الرمز البريدي فيه ، والذي يحمل المعرّف الذي تبحث عنه. بشكل عام ، من الجيد أيضًا تسمية جداولك وفقًا لسجل واحد ، لذلك سيكون اسم الجدول الأولي أفضل حالًا باسم "الرمز البريدي". ستكون الإعدادات الافتراضية جدول صف واحد ، حتى تحتاج إلى تخصيص الإعدادات الافتراضية الخاصة بك ضد بعض التكوينات الأخرى (مثل التاريخ).

الاستعلام الأخير الذي تريده هو كما يلي:

select Zip from PostalCode p, Defaults d where p.ID=d.PostalCode;
0
Konchog