سؤال

هذا استعلام أساسي للغاية لا يمكنني اكتشافه ....

لنفترض أن لدي جدول عمود مثل هذا:

userid  |  roleid
--------|--------
   1    |    1
   1    |    2
   1    |    3
   2    |    1

أريد الحصول على جميع المستخدمين المتميزين لديهم roleids 1 و 2 و 3. باستخدام المثال أعلاه ، فإن النتيجة الوحيدة التي أريد إرجاعها هي userid 1. كيف أفعل هذا؟

هل كانت مفيدة؟

المحلول

SELECT userid
FROM UserRole
WHERE roleid IN (1, 2, 3)
GROUP BY userid
HAVING COUNT(DISTINCT roleid) = 3;

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


تقضيات التفكير بصوت عالٍ ، هناك طريقة أخرى لكتابة التواصل الذاتي الذي وصفه @celetus هي:

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid
JOIN userrole t3 ON t2.userid = t3.userid
WHERE (t1.roleid, t2.roleid, t3.roleid) = (1, 2, 3);

قد يكون هذا أسهل في قراءتك ، ويدعم MySQL مقارنات من Tuples من هذا القبيل. يعرف MySQL أيضًا كيفية استخدام تغطية الفهارس بذكاء لهذا الاستعلام. فقط قم بتشغيله من خلال EXPLAIN وشاهد "استخدام الفهرس" في الملاحظات لجميع الجداول الثلاثة ، مما يعني أنه يقرأ الفهرس ولا يضطر حتى إلى لمس صفوف البيانات.

قمت بتشغيل هذا الاستعلام أكثر من 2.1 مليون صف (تفريغ بيانات المكدس Overflow July لـ PostTags) باستخدام MySQL 5.1.48 على جهاز MacBook الخاص بي ، وأعاد النتيجة 1.08 ثانية. على خادم لائق مع ذاكرة كافية مخصصة ل innodb_buffer_pool_size, ، يجب أن يكون أسرع.

نصائح أخرى

حسنًا ، لقد انخفضت على هذا الأمر ، لذلك قررت اختباره:

CREATE TABLE userrole (
  userid INT,
  roleid INT,
  PRIMARY KEY (userid, roleid)
);

CREATE INDEX ON userrole (roleid);

تشغيل هذا:

<?php
ini_set('max_execution_time', 120); // takes over a minute to insert 500k+ records 

$start = microtime(true);

echo "<pre>\n";
mysql_connect('localhost', 'scratch', 'scratch');
if (mysql_error()) {
    echo "Connect error: " . mysql_error() . "\n";
}
mysql_select_db('scratch');
if (mysql_error()) {
    echo "Selct DB error: " . mysql_error() . "\n";
}

$users = 200000;
$count = 0;
for ($i=1; $i<=$users; $i++) {
    $roles = rand(1, 4);
    $available = range(1, 5);
    for ($j=0; $j<$roles; $j++) {
        $extract = array_splice($available, rand(0, sizeof($available)-1), 1);
        $id = $extract[0];
        query("INSERT INTO userrole (userid, roleid) VALUES ($i, $id)");
        $count++;
    }
}

$stop = microtime(true);
$duration = $stop - $start;
$insert = $duration / $count;

echo "$count users added.\n";
echo "Program ran for $duration seconds.\n";
echo "Insert time $insert seconds.\n";
echo "</pre>\n";

function query($str) {
    mysql_query($str);
    if (mysql_error()) {
        echo "$str: " . mysql_error() . "\n";
    }
}
?>

انتاج:

499872 users added.
Program ran for 56.5513510704 seconds.
Insert time 0.000113131663847 seconds.

يضيف ذلك 500000 مجموعة عشوائية لالتحول المستخدم وهناك ما يقرب من 25000 تتطابق مع المعايير المختارة.

الاستعلام الأول:

SELECT userid
FROM userrole
WHERE roleid IN (1, 2, 3)
GROUP by userid
HAVING COUNT(1) = 3

وقت الاستعلام: 0.312s

SELECT t1.userid
FROM userrole t1
JOIN userrole t2 ON t1.userid = t2.userid AND t2.roleid = 2
JOIN userrole t3 ON t2.userid = t3.userid AND t3.roleid = 3
AND t1.roleid = 1

وقت الاستعلام: 0.016S

هذا صحيح. إصدار الانضمام الذي اقترحته هو عشرين مرة أسرع من النسخة الإجمالية.

آسف ، لكنني أفعل هذا من أجل لقمة العيش والعمل في العالم الحقيقي وفي العالم الحقيقي نختبر SQL والنتائج تتحدث عن نفسها.

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

يتحسن أداء إصدار JOIN مع انخفاض نسبة الإصابة بالمباريات.

إذا كان هناك 500 مستخدم فقط (من بين عينة 500 ألف أعلاه) والتي كانت لها الأدوار الثلاثة المعلنة ، فإن إصدار Join سوف يصبح أسرع بكثير. لن يكون الإصدار الإجمالي (وأي تحسين في الأداء نتيجة لنقل 500 مستخدم بدلاً من 25 ألفًا ، وهو ما يحصل عليه إصدار Join أيضًا).

كنت فضوليًا أيضًا لمعرفة كيف ستتعامل قاعدة بيانات حقيقية (أي أوراكل) مع هذا. لذلك كررت بشكل أساسي نفس التمرين على Oracle XE (تعمل على نفس جهاز سطح المكتب Windows XP مثل MySQL من المثال السابق) والنتائج متطابقة تقريبًا.

يبدو أن الوصلات مستهلكة ولكن كما أوضحت ، يمكن أن تكون الاستعلامات الإجمالية أمرًا أبطأ.

تحديث: بعد بعض اختبار واسع النطاق, ، الصورة أكثر تعقيدًا وستعتمد الإجابة على بياناتك وقاعدة البيانات والعوامل الأخرى. أخلاقي القصة هو الاختبار ، والاختبار ، والاختبار.

الطريقة الكلاسيكية للقيام بذلك هي التعامل معها كمشكلة تقسيم علائقية.

باللغة الإنجليزية: حدد هؤلاء المستخدمين الذين لا يكون لهم أي من القيم المرغوبة.

سأفترض أن لديك جدول مستخدمين يشير إليه جدول Userrole ، وسأفترض أن القيم المرغوبة في الجدول:

create table RoleGroup(
  roleid int not null,
  primary key(roleid)
)
insert into RoleGroup values (1);
insert into RoleGroup values (2);
insert into RoleGroup values (3);

سأفترض أيضًا أن جميع الأعمدة ذات الصلة ليست قابلة للإلغاء ، لذلك لا توجد مفاجآت في أو لا توجد. إليك استعلام SQL يعبر عن اللغة الإنجليزية أعلاه:

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where not exists (
    select R.roleid from UserRole as R
    where R.roleid = G.roleid
    and R.userid = U.userid
  )
);

طريقة أخرى للكتابة هي هذا

select userid from Users as U
where not exists (
  select * from RoleGroup as G
  where G.roleid not in (
    select R.roleid from UserRole as R
    where R.userid = U.userid
  )
);

قد ينتهي هذا أو لا ينتهي به الأمر ، اعتمادًا على الفهارس والمنصة والبيانات ، إلخ.

على افتراض أن userId ، يتم احتواء ROLOID في فهرس فريد (وهذا يعني أنه لا يمكن أن يكون هناك سجلان حيث userId = x و robeid = 1

select count(*), userid from t
where roleid in (1,2,3)
group by userid
having count(*) = 3
select userid from userrole where userid = 1
intersect
select userid from userrole where userid = 2
intersect
select userid from userrole where userid = 3

ألن يحل هذا المشكلة؟ ما مدى جودة الحل على DBS العلائقية النموذجية؟ هل سيحسن هذا الاستعلام التلقائي لتحسين هذا؟

إذا كنت بحاجة إلى أي نوع من الأمواج هنا (مجموعات مختلفة من 3 دور أو مجموعات مختلفة من الدوران) ... أقترح عليك استخدام نظام إخفاء قليل لأدوارك واستخدام مشغلات Bitwise لأداء استفساراتك ...

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