سؤال

لدي (مبسط على سبيل المثال) جدول يحتوي على البيانات التالية

Row Start       Finish       ID  Amount
--- ---------   ----------   --  ------
  1 2008-10-01  2008-10-02   01      10
  2 2008-10-02  2008-10-03   02      20
  3 2008-10-03  2008-10-04   01      38
  4 2008-10-04  2008-10-05   01      23
  5 2008-10-05  2008-10-06   03      14
  6 2008-10-06  2008-10-07   02       3
  7 2008-10-07  2008-10-08   02       8
  8 2008-10-08  2008-11-08   03      19

تمثل التواريخ فترة زمنية، والمعرف هو الحالة التي كان عليها النظام خلال تلك الفترة، والمبلغ عبارة عن قيمة مرتبطة بتلك الحالة.

ما أريد القيام به هو تجميع المبالغ مجاور صفوف مع نفس رقم المعرف، ولكن احتفظ بنفس التسلسل العام بحيث يمكن دمج عمليات التشغيل المتجاورة.وبالتالي أريد أن ينتهي بي الأمر ببيانات مثل:

Row Start       Finish       ID  Amount
--- ---------   ----------   --  ------
  1 2008-10-01  2008-10-02   01      10
  2 2008-10-02  2008-10-03   02      20
  3 2008-10-03  2008-10-05   01      61
  4 2008-10-05  2008-10-06   03      14
  5 2008-10-06  2008-10-08   02      11
  6 2008-10-08  2008-11-08   03      19

أنا أبحث عن حل T-SQL يمكن وضعه في SP، ولكن لا يمكنني رؤية كيفية القيام بذلك من خلال الاستعلامات البسيطة.أظن أن الأمر قد يتطلب تكرارًا من نوع ما ولكني لا أريد السير في هذا المسار.

السبب وراء رغبتي في القيام بهذا التجميع هو أن الخطوة التالية في العملية هي إجراء SUM() و Count() مجمعة بواسطة المعرفات الفريدة التي تحدث ضمن التسلسل، بحيث تبدو بياناتي النهائية كما يلي:

ID  Counts Total
--  ------ -----
01       2    71
02       2    31
03       2    33

ولكن إذا قمت بعمل بسيط

SELECT COUNT(ID), SUM(Amount) FROM data GROUP BY ID

على الطاولة الأصلية أحصل على شيء من هذا القبيل

ID  Counts Total
--  ------ -----
01       3    71
02       3    31
03       2    33

وهذا ليس ما أريد.

لا يوجد حل صحيح

نصائح أخرى

إذا قرأت كتاب "تطوير تطبيقات قواعد البيانات الموجهة نحو الوقت في SQL" من تأليف آر تي سنودجراس (يتوفر ملف pdf الخاص به على موقع الويب الخاص به ضمن المنشورات)، وتصل إلى الشكل 6.25 في الصفحات 165-166، وسوف تجد SQL غير التافه الذي يمكن استخدامه في المثال الحالي لتجميع الصفوف المختلفة مع نفس قيمة المعرف والفواصل الزمنية المستمرة.

تطوير الاستعلام أدناه قريب من التصحيح، ولكن هناك مشكلة تم رصدها في النهاية، مصدرها في عبارة SELECT الأولى.لم أتتبع بعد سبب إعطاء الإجابة غير الصحيحة. [إذا كان بإمكان شخص ما اختبار SQL على نظام إدارة قواعد البيانات (DBMS) الخاص به وإخباري ما إذا كان الاستعلام الأول يعمل بشكل صحيح هناك، فسيكون ذلك مفيدًا جدًا!]

يبدو شيء مثل:

-- Derived from Figure 6.25 from Snodgrass "Developing Time-Oriented
-- Database Applications in SQL"
CREATE TABLE Data
(
    Start   DATE,
    Finish  DATE,
    ID      CHAR(2),
    Amount  INT
);

INSERT INTO Data VALUES('2008-10-01', '2008-10-02', '01', 10);
INSERT INTO Data VALUES('2008-10-02', '2008-10-03', '02', 20);
INSERT INTO Data VALUES('2008-10-03', '2008-10-04', '01', 38);
INSERT INTO Data VALUES('2008-10-04', '2008-10-05', '01', 23);
INSERT INTO Data VALUES('2008-10-05', '2008-10-06', '03', 14);
INSERT INTO Data VALUES('2008-10-06', '2008-10-07', '02',  3);
INSERT INTO Data VALUES('2008-10-07', '2008-10-08', '02',  8);
INSERT INTO Data VALUES('2008-10-08', '2008-11-08', '03', 19);

SELECT DISTINCT F.ID, F.Start, L.Finish
    FROM Data AS F, Data AS L
    WHERE F.Start < L.Finish
      AND F.ID = L.ID
      -- There are no gaps between F.Finish and L.Start
      AND NOT EXISTS (SELECT *
                        FROM Data AS M
                        WHERE M.ID = F.ID
                        AND F.Finish < M.Start
                        AND M.Start < L.Start
                        AND NOT EXISTS (SELECT *
                                            FROM Data AS T1
                                            WHERE T1.ID = F.ID
                                              AND T1.Start <  M.Start
                                              AND M.Start  <= T1.Finish))
      -- Cannot be extended further
      AND NOT EXISTS (SELECT *
                          FROM Data AS T2
                          WHERE T2.ID = F.ID
                            AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish)
                              OR (T2.Start <= L.Finish AND L.Finish <  T2.Finish)));

الإخراج من هذا الاستعلام هو:

01  2008-10-01      2008-10-02
01  2008-10-03      2008-10-05
02  2008-10-02      2008-10-03
02  2008-10-06      2008-10-08
03  2008-10-05      2008-10-06
03  2008-10-05      2008-11-08
03  2008-10-08      2008-11-08

تم تحريره:هناك مشكلة في الصف قبل الأخير - لا ينبغي أن يكون موجودًا.وليس واضحًا (حتى الآن) من أين يأتي.

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

SELECT M.ID, M.Start, M.Finish, SUM(D.Amount)
    FROM Data AS D,
         (SELECT DISTINCT F.ID, F.Start, L.Finish
              FROM Data AS F, Data AS L
              WHERE F.Start < L.Finish
                AND F.ID = L.ID
                -- There are no gaps between F.Finish and L.Start
                AND NOT EXISTS (SELECT *
                                    FROM Data AS M
                                    WHERE M.ID = F.ID
                                    AND F.Finish < M.Start
                                    AND M.Start < L.Start
                                    AND NOT EXISTS (SELECT *
                                                        FROM Data AS T1
                                                        WHERE T1.ID = F.ID
                                                          AND T1.Start <  M.Start
                                                          AND M.Start  <= T1.Finish))
                  -- Cannot be extended further
                AND NOT EXISTS (SELECT *
                                    FROM Data AS T2
                                    WHERE T2.ID = F.ID
                                      AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish)
                                        OR (T2.Start <= L.Finish AND L.Finish <  T2.Finish)))) AS M
    WHERE D.ID = M.ID
      AND M.Start  <= D.Start
      AND M.Finish >= D.Finish
    GROUP BY M.ID, M.Start, M.Finish
    ORDER BY M.ID, M.Start;

هذا يعطي:

ID  Start        Finish       Amount
01  2008-10-01   2008-10-02   10
01  2008-10-03   2008-10-05   61
02  2008-10-02   2008-10-03   20
02  2008-10-06   2008-10-08   11
03  2008-10-05   2008-10-06   14
03  2008-10-05   2008-11-08   33              -- Here be trouble!
03  2008-10-08   2008-11-08   19

تم تحريره:هذا هو بالكاد مجموعة البيانات الصحيحة التي سيتم من خلالها إجراء تجميع COUNT وSUM المطلوب بواسطة السؤال الأصلي، وبالتالي فإن الإجابة النهائية هي:

SELECT I.ID, COUNT(*) AS Number, SUM(I.Amount) AS Amount
    FROM (SELECT M.ID, M.Start, M.Finish, SUM(D.Amount) AS Amount
            FROM Data AS D,
                 (SELECT DISTINCT F.ID, F.Start, L.Finish
                      FROM  Data AS F, Data AS L
                      WHERE F.Start < L.Finish
                        AND F.ID = L.ID
                        -- There are no gaps between F.Finish and L.Start
                        AND NOT EXISTS
                            (SELECT *
                                FROM  Data AS M
                                WHERE M.ID = F.ID
                                  AND F.Finish < M.Start
                                  AND M.Start < L.Start
                                  AND NOT EXISTS
                                      (SELECT *
                                          FROM Data AS T1
                                          WHERE T1.ID = F.ID
                                            AND T1.Start <  M.Start
                                            AND M.Start  <= T1.Finish))
                          -- Cannot be extended further
                        AND NOT EXISTS
                            (SELECT *
                                FROM  Data AS T2
                                WHERE T2.ID = F.ID
                                  AND ((T2.Start <  F.Start  AND F.Start  <= T2.Finish) OR
                                       (T2.Start <= L.Finish AND L.Finish <  T2.Finish)))
                 ) AS M
            WHERE D.ID = M.ID
              AND M.Start  <= D.Start
              AND M.Finish >= D.Finish
            GROUP BY M.ID, M.Start, M.Finish
          ) AS I
        GROUP BY I.ID
        ORDER BY I.ID;

id     number  amount
01      2      71
02      2      31
03      3      66

مراجعة:أوه!Drat... الإدخال لـ 3 يحتوي على ضعف "المبلغ" الذي يجب أن يحتوي عليه.تشير الأجزاء "المعدلة" السابقة إلى أين بدأت الأمور تسوء.يبدو كما لو أن الاستعلام الأول خاطئ تمامًا (ربما كان مخصصًا لسؤال مختلف)، أو أن المُحسِّن الذي أعمل معه يسيء التصرف.ومع ذلك، ينبغي أن تكون هناك إجابة وثيقة الصلة بهذا الأمر تعطي القيم الصحيحة.

للسجل:تم اختباره على IBM Informix Dynamic Server 11.50 على Solaris 10.ومع ذلك، يجب أن تعمل بشكل جيد على أي نظام إدارة قواعد بيانات SQL آخر متوافق مع المعايير القياسية.

وربما تحتاج إلى إنشاء مؤشر وحلقة من خلال النتائج، التي تتبع معرف كنت تعمل مع وتراكم البيانات على طول الطريق. عندما يتغير معرف يمكنك إدراج البيانات المتراكمة في جدول مؤقت والعودة الجدول في نهاية الإجراء (تحديد كافة من ذلك). وظيفة على أساس الجدول قد يكون أفضل ما يمكنك ثم إدراج فقط في الجدول المقابل كما تذهب على طول.

<اقتباس فقرة>   

وأظن أنه قد تتطلب التكرار من نوع ما لكنني لا أريد أن تسلك هذا المسار.

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

وأود أن أضيف حقل قليلا مع الافتراضية 0 إلى الجدول المصدر لتتبع والتي تم تجهيزها السجلات. افتراض عدم وجود واحد هو استخدام حدد * على الطاولة، إضافة عمود مع قيمة افتراضية لن يؤثر على بقية طلبك.

إضافة تعليق على هذا المنصب اذا كنت تريد مساعدة الترميز الحل.

وحسنا قررت السير في طريق التكرار باستخدام مزيج من الصلات والمؤشرات. من خلال الانضمام إلى جدول البيانات على نفسه يمكنني إنشاء قائمة الارتباط من تلك السجلات فقط التي هي على التوالي.

INSERT INTO #CONSEC
  SELECT a.ID, a.Start, b.Finish, b.Amount 
  FROM Data a JOIN Data b 
  ON (a.Finish = b.Start) AND (a.ID = b.ID)

وبعد ذلك يمكنني استرخاء القائمة عن طريق بالتكرار أكثر من ذلك مع المؤشر، والقيام التحديثات إلى جدول البيانات إلى ضبط (وحذف السجلات الآن غريبة من جدول البيانات)

DECLARE CCursor  CURSOR FOR
  SELECT ID, Start, Finish, Amount FROM #CONSEC ORDER BY Start DESC

@Total = 0
OPEN CCursor
FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
WHILE @FETCH_STATUS = 0
BEGIN
  @Total = @Total + @Amount
  @Start_Last = @Start
  @Finish_Last = @Finish
  @ID_Last = @ID

  DELETE FROM Data WHERE Start = @Finish
  FETCH NEXT FROM CCursor INTO @ID, @START, @FINISH, @AMOUNT
  IF (@ID_Last<> @ID) OR (@Finish<>@Start_Last)
    BEGIN
      UPDATE Data
        SET Amount = Amount + @Total
        WHERE Start = @Start_Last
      @Total = 0
    END  
END

CLOSE CCursor
DEALLOCATE CCursor

وهذا كله يعمل ولديه أداء مقبول للبيانات النموذجية التي أستخدمه.

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top