it-swarm.asia

حدد SQL الصفوف فقط ذات القيمة القصوى في عمود

لدي هذا الجدول للمستندات (الإصدار المبسط هنا):

+------+-------+--------------------------------------+
| id   | rev   | content                              |
+------+-------+--------------------------------------+
| 1    | 1     | ...                                  |
| 2    | 1     | ...                                  |
| 1    | 2     | ...                                  |
| 1    | 3     | ...                                  |
+------+-------+--------------------------------------+

كيف أحدد صفًا واحدًا لكل معرّف وأكبر لفة؟
مع البيانات المذكورة أعلاه ، يجب أن تحتوي النتيجة على صفين: [1, 3, ...] و [2, 1, ..]. أنا أستخدمMySQL.

أستخدم حاليًا عمليات الفحص في حلقة while لاكتشاف وفحص الكتابة القديمة من مجموعة النتائج. ولكن هل هذه هي الطريقة الوحيدة لتحقيق النتيجة؟ ليس هناكSQLالحل؟

تحديث
كما تشير الإجابات ، يوجد is حل SQL ، و هنا sqlfiddle demo .

التحديث 2
لقد لاحظت بعد إضافة ما ورد أعلاه {sqlfiddle ، أن معدل إجابة السؤال قد تجاوز معدل التصويت الزائد للإجابات. هذا لم يكن القصد! يعتمد الكمان على الإجابات ، وخاصة الإجابة المقبولة.

1040
Majid Fouladpour

للوهلة الأولى...

كل ما تحتاجه هو جملة GROUP BY مع الدالة التجميعية MAX:

SELECT id, MAX(rev)
FROM YourTable
GROUP BY id

الأمر ليس بهذه البساطة ، أليس كذلك؟

لقد لاحظت أنك بحاجة إلى العمود content أيضًا.

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

من الشائع جدًا أن ينشئ مجتمع StackOverflow علامة واحدة فقط للتعامل مع أسئلة مثل: أعظم ن لكل مجموعة .

في الأساس ، لديك طريقتان لحل هذه المشكلة:

الانضمام إلى group-identifier, max-value-in-group استعلام فرعي بسيط

في هذا النهج ، ستجد أولاً group-identifier, max-value-in-group (سبق حلها أعلاه) في استعلام فرعي. ثم تنضم إلى الجدول الخاص بك في الاستعلام الفرعي مع المساواة في كل من group-identifier و max-value-in-group:

SELECT a.id, a.rev, a.contents
FROM YourTable a
INNER JOIN (
    SELECT id, MAX(rev) rev
    FROM YourTable
    GROUP BY id
) b ON a.id = b.id AND a.rev = b.rev

غادر الانضمام مع النفس ، التغيير والتبديل شروط الصلة والمرشحات

في هذا النهج ، تركت الانضمام إلى الجدول مع نفسه. المساواة ، بطبيعة الحال ، يذهب في group-identifier. ثم ، 2 التحركات الذكية:

  1. شرط الصلة الثاني هو وجود قيمة الجانب الأيسر أقل من القيمة الصحيحة
  2. عندما تقوم بالخطوة الأولى ، سيكون للصف (الصفوف) التي تحتوي على الحد الأقصى لقيمة القيمة فعليًا NULL في الجانب الأيمن (رمز LEFT JOIN ، تذكر؟). بعد ذلك ، نقوم بتصفية النتيجة المرتبطة ، مع إظهار الصفوف فقط حيث يكون الجانب الأيمن NULL.

لذلك ينتهي بك الأمر بـ:

SELECT a.*
FROM YourTable a
LEFT OUTER JOIN YourTable b
    ON a.id = b.id AND a.rev < b.rev
WHERE b.id IS NULL;

استنتاج

كلا النهجين يجلب نفس النتيجة بالضبط.

إذا كان لديك صفين مع max-value-in-group لـ group-identifier ، فسيكون كلا الصفين في النتيجة في كلا الاتجاهين.

كلا الأسلوبين متوافق مع SQL ANSI ، وبالتالي ، ستعمل مع RDBMS المفضلة لديك ، بغض النظر عن "نكهتها".

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

1630
Adrian Carneiro

التفضيل هو استخدام رمز أقل عدد ممكن ...

يمكنك القيام بذلك باستخدام IN جرب هذا:

SELECT * 
FROM t1 WHERE (id,rev) IN 
( SELECT id, MAX(rev)
  FROM t1
  GROUP BY id
)

في رأيي أنه أقل تعقيداً ... أسهل في القراءة والمحافظة عليه.

209
Kevin Burton

حل آخر هو استخدام استعلام فرعي مرتبط:

select yt.id, yt.rev, yt.contents
    from YourTable yt
    where rev = 
        (select max(rev) from YourTable st where yt.id=st.id)

يؤدي وجود فهرس على (id ، rev) إلى جعل الاستعلام الفرعي كبحث بسيط تقريبًا ...

فيما يلي مقارنات للحلول الواردة في إجابة @ AdrianCarneiro (استعلام فرعي ، leftjoin) ، استنادًا إلى قياسات MySQL مع جدول InnoDB من سجلات ~ 1 مليون ، حجم المجموعة هو: 1-3.

بينما يتعلق الجدول الكامل بمسح الاستعلام الفرعي/leftjoin/توقيتات مرتبطة ببعضها البعض مثل 6/8/9 ، عندما يتعلق الأمر بالبحث المباشر أو الدُفعة (id in (1,2,3)) ، يكون الاستعلام الفرعي أبطأ بكثير ثم الآخر (بسبب إعادة تشغيل الاستعلام الفرعي). ومع ذلك ، لم أتمكن من التمييز بين الحلول المتصلة باليسار والحلول اليسرى المرتبطة بالسرعة.

ملاحظة أخيرة ، حيث أن leftjoin ينشئ n * (n + 1)/2 ينضم إلى مجموعات ، يمكن أن يتأثر أدائها بشدة بحجم المجموعات ...

68
Vajk Hermecz

أنا أشعر بالذهول لعدم تقديم إجابة حل وظيفة نافذة SQL:

SELECT a.id, a.rev, a.contents
  FROM (SELECT id, rev, contents,
               ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
          FROM YourTable) a
 WHERE a.rank = 1 

أضيف في SQL ANSI/ISO قياسي SQL: 2003 وتم تمديده لاحقًا مع ANSI/ISO Standard SQL: 2008 ، وظائف النافذة (أو النافذة) متوفرة مع جميع البائعين الرئيسيين الآن. هناك المزيد من أنواع وظائف الترتيب المتاحة للتعامل مع مشكلة التعادل: RANK, DENSE_RANK, PERSENT_RANK.

61
topchef

لا يمكنني أن أضمن الأداء ، لكن هذه خدعة مستوحاة من قيود Microsoft Excel. لديها بعض الميزات الجيدة

أشياء جيدة

  • يجب أن يفرض إعادة "سجل أقصى" واحد فقط حتى لو كان هناك رابط (مفيد في بعض الأحيان)
  • لا يتطلب صلة

مقاربة

إنه قبيح قليلاً ويتطلب منك أن تعرف شيئًا ما عن نطاق القيم الصالحة للعمود rev . دعنا نفترض أننا نعرف أن rev العمود هو رقم يتراوح بين 0.00 و 999 بما في ذلك الكسور العشرية ولكن لن يكون هناك سوى رقمين على يمين العلامة العشرية (على سبيل المثال ، 34.17 سيكون قيمة صالحة).

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

وإليك كيف يبدو مع المثال أعلاه ، مكتوبة في SQL

SELECT id, 
       CAST(SUBSTRING(max(packed_col) FROM 2 FOR 6) AS float) as max_rev,
       SUBSTRING(max(packed_col) FROM 11) AS content_for_max_rev 
FROM  (SELECT id, 
       CAST(1000 + rev + .001 as CHAR) || '---' || CAST(content AS char) AS packed_col
       FROM yourtable
      ) 
GROUP BY id

تبدأ التعبئة بإجبار rev على أن يكون عددًا من طول الأحرف المعروفة بغض النظر عن قيمة rev بحيث على سبيل المثال

  • 3.2 يصبح 1003.201
  • 57 تصبح 1057.001
  • أصبح 923.88 1923.881

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

45
David Foster

شيء من هذا القبيل؟

SELECT yourtable.id, rev, content
FROM yourtable
INNER JOIN (
    SELECT id, max(rev) as maxrev FROM yourtable
    WHERE yourtable
    GROUP BY id
) AS child ON (yourtable.id = child.id) AND (yourtable.rev = maxrev)
19
Marc B

نظرًا لأن هذا هو السؤال الأكثر شيوعًا فيما يتعلق بهذه المشكلة ، سأعيد نشر إجابة أخرى عليها هنا أيضًا:

يبدو أن هناك طريقة أبسط للقيام بذلك (ولكن فقط في MySQL ):

select *
from (select * from mytable order by id, rev desc ) x
group by id

يرجى الرجوع إلى إجابة المستخدم البوهيمي في هذا السؤال لتقديم هذه الإجابة المختصرة والأنيقة لهذه المشكلة.

تحرير: على الرغم من أن هذا الحل يعمل لكثير من الأشخاص ، إلا أنه قد لا يكون مستقرًا على المدى الطويل ، لأن MySQL لا تضمن أن تُرجع عبارة GROUP BY قيمًا ذات معنى للأعمدة غير المدرجة في قائمة GROUP BY. لذلك استخدم هذا الحل على مسؤوليتك الخاصة!

6
Yuriy Nakonechnyy

أحب استخدام حل NOT EXIST- لهذه المشكلة:

SELECT 
  id, 
  rev
  -- you can select other columns here
FROM YourTable t
WHERE NOT EXISTS (
   SELECT * FROM YourTable t WHERE t.id = id AND rev > t.rev
)

سيؤدي هذا إلى تحديد جميع السجلات ذات القيمة القصوى داخل المجموعة ويسمح لك بتحديد الأعمدة الأخرى.

6
Bulat

الحل الثالث الذي بالكاد أراه ذكره هو MySQL المحدد ويبدو كما يلي:

SELECT id, MAX(rev) AS rev
 , 0+SUBSTRING_INDEX(GROUP_CONCAT(numeric_content ORDER BY rev DESC), ',', 1) AS numeric_content
FROM t1
GROUP BY id

نعم يبدو الأمر فظيعة (التحويل إلى سلسلة والعودة وما إلى ذلك) ولكن في تجربتي عادة ما يكون أسرع من الحلول الأخرى. ربما هذا فقط لحالات الاستخدام الخاصة بي ، لكنني استخدمتها في جداول بها ملايين السجلات والعديد من المعرفات الفريدة. ربما يرجع السبب في ذلك إلى أن MySQL سيئ جدًا في تحسين الحلول الأخرى (على الأقل في 5.0 أيام عندما توصلت إلى هذا الحل).

شيء واحد مهم هو أن GROUP_CONCAT يبلغ الحد الأقصى لطول السلسلة التي يمكن تكوينها. ربما تريد رفع هذا الحد عن طريق تعيين المتغير group_concat_max_len. ضع في اعتبارك أن هذا سيكون حدًا للتوسع إذا كان لديك عدد كبير من الصفوف.

على أي حال ، ما سبق لا يعمل بشكل مباشر إذا كان حقل المحتوى الخاص بك نصًا بالفعل. في هذه الحالة ، ربما تريد استخدام فاصل مختلف ، مثل\0 ربما. سيتم أيضًا تشغيل الحد group_concat_max_len بشكل أسرع.

5
Jannes

ليس mySQL ، ولكن بالنسبة للأشخاص الآخرين الذين يجدون هذا السؤال ويستخدمون SQL ، هناك طريقة أخرى لحل أعظم ن لكل مجموعة problem تستخدم Cross Apply في MS SQL

WITH DocIds AS (SELECT DISTINCT id FROM docs)

SELECT d2.id, d2.rev, d2.content
FROM DocIds d1
CROSS APPLY (
  SELECT Top 1 * FROM docs d
  WHERE d.id = d1.id
  ORDER BY rev DESC
) d2

فيما يلي مثال في SqlFiddle

5
KyleMit

أعتقد ، أنت تريد هذا؟

select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)  

SQL Fiddle: تحقق هنا

4
Abhishek Rana

إذا كان لديك العديد من الحقول في عبارة تحديد وتريد أحدث قيمة لجميع هذه الحقول من خلال رمز محسن:

select * from
(select * from table_name
order by id,rev desc) temp
group by id 
4
seahawk

هناك طريقة أخرى للقيام بالمهمة وهي استخدام MAX() ظيفة التحليلية في جملة PARTITION

SELECT t.*
  FROM
    (
    SELECT id
          ,rev
          ,contents
          ,MAX(rev) OVER (PARTITION BY id) as max_rev
      FROM YourTable
    ) t
  WHERE t.rev = t.max_rev 

الحل ROW_NUMBER() OVER PARTITION الآخر الموثق بالفعل في هذا المنشور هو

SELECT t.*
  FROM
    (
    SELECT id
          ,rev
          ,contents
          ,ROW_NUMBER() OVER (PARTITION BY id ORDER BY rev DESC) rank
      FROM YourTable
    ) t
  WHERE t.rank = 1 

هذا 2 SELECT تعمل بشكل جيد على أوراكل 10g.

يعمل الحل MAX () بالتأكيد بشكل أسرع على حل ROW_NUMBER() لأن MAX() تعقيد O(n) بينما ROW_NUMBER() تعقيد O(n.log(n)) على الأقل حيث n يمثل عدد السجلات في الجدول!

3
schlebe

أود استخدام هذا:

select t.*
from test as t
join
   (select max(rev) as rev
    from test
    group by id) as o
on o.rev = t.rev

استعلام فرعي SELECT ليست سهلة للغاية ، ولكن في جملة JOIN يبدو أنه قابل للاستخدام. لست خبيرًا في تحسين الاستعلامات ، لكنني جربت في MySQL و PostgreSQL و FireBird وهي تعمل بشكل جيد جدًا.

يمكنك استخدام هذا المخطط في روابط متعددة ومع جملة WHERE. إنه مثال عملي (حل مشكلة مماثلة لك في جدول "firmy"):

select *
from platnosci as p
join firmy as f
on p.id_rel_firmy = f.id_rel
join (select max(id_obj) as id_obj
      from firmy
      group by id_rel) as o
on o.id_obj = f.id_obj and p.od > '2014-03-01'

يتم طرحه على الجداول التي تحتوي على مراهقين من سجلات ، ويستغرق أقل من 0،01 ثانية على آلة ليست قوية جدًا حقًا.

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

3
Marek Wysmułek

وماذا عن هذا:

SELECT all_fields.*  
FROM (SELECT id, MAX(rev) FROM yourtable GROUP BY id) AS max_recs  
LEFT OUTER JOIN yourtable AS all_fields 
ON max_recs.id = all_fields.id
3
inor

هذا الحل يجعل اختيار واحد فقط من YourTable ، وبالتالي فهو أسرع. إنه يعمل فقط على MySQL و SQLite (لإزالة SQLC DESC) وفقًا للاختبار على sqlfiddle.com. ربما يمكن أن يعدل للعمل على لغات أخرى لست على دراية بها.

SELECT *
FROM ( SELECT *
       FROM ( SELECT 1 as id, 1 as rev, 'content1' as content
              UNION
              SELECT 2, 1, 'content2'
              UNION
              SELECT 1, 2, 'content3'
              UNION
              SELECT 1, 3, 'content4'
            ) as YourTable
       ORDER BY id, rev DESC
   ) as YourTable
GROUP BY id
3
plavozont
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
3
guru008

أي من هذه الإجابات قد عملت بالنسبة لي.

هذا هو ما عملت لي.

with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
2
qaisjp

تم فرز حقل المراجعة بترتيب عكسي ثم تم التجميع حسب المعرف الذي أعطى الصف الأول من كل مجموعة وهو أعلى قيمة مراجعة.

SELECT * FROM (SELECT * FROM table1 ORDER BY id, rev DESC) X GROUP BY X.id;

تم اختباره في http://sqlfiddle.com/ مع البيانات التالية

CREATE TABLE table1
    (`id` int, `rev` int, `content` varchar(11));

INSERT INTO table1
    (`id`, `rev`, `content`)
VALUES
    (1, 1, 'One-One'),
    (1, 2, 'One-Two'),
    (2, 1, 'Two-One'),
    (2, 2, 'Two-Two'),
    (3, 2, 'Three-Two'),
    (3, 1, 'Three-One'),
    (3, 3, 'Three-Three')
;

أعطى هذا النتيجة التالية في MySql 5.5 و 5.6

id  rev content
1   2   One-Two
2   2   Two-Two
3   3   Three-Two
2
blokeish

إليك طريقة لطيفة للقيام بذلك

استخدم الكود التالي:

with temp as  ( 
select count(field1) as summ , field1
from table_name
group by field1 )
select * from temp where summ = (select max(summ) from temp)
2
shay

أحب القيام بذلك عن طريق ترتيب السجلات حسب بعض الأعمدة. في هذه الحالة ، قم بتصنيف قيم rev المجمعة حسب id. أولئك الذين لديهم rev سيحصلون على تصنيفات أقل. حتى أعلى rev سيكون ترتيب 1.

select id, rev, content
from
 (select
    @rowNum := if(@prevValue = id, @rowNum+1, 1) as row_num,
    id, rev, content,
    @prevValue := id
  from
   (select id, rev, content from YOURTABLE order by id asc, rev desc) TEMP,
   (select @rowNum := 1 from DUAL) X,
   (select @prevValue := -1 from DUAL) Y) TEMP
where row_num = 1;

لست متأكداً مما إذا كان إدخال المتغيرات يجعل الأمر برمته أبطأ. لكن على الأقل لا أستعلم YOURTABLE مرتين.

2
user5124980

إليك حل آخر لاسترداد السجلات فقط من خلال حقل يحتوي على الحد الأقصى لقيمة هذا الحقل. هذا يعمل مع SQL400 وهو النظام الأساسي الذي أعمل عليه. في هذا المثال ، سيتم استرداد السجلات ذات القيمة القصوى في الحقل FIELD5 بواسطة عبارة SQL التالية.

SELECT A.KEYFIELD1, A.KEYFIELD2, A.FIELD3, A.FIELD4, A.FIELD5
  FROM MYFILE A
 WHERE RRN(A) IN
   (SELECT RRN(B) 
      FROM MYFILE B
     WHERE B.KEYFIELD1 = A.KEYFIELD1 AND B.KEYFIELD2 = A.KEYFIELD2
     ORDER BY B.FIELD5 DESC
     FETCH FIRST ROW ONLY)
2
Cesar

هنا حل آخر آمل أن يساعد شخص ما

Select a.id , a.rev, a.content from Table1 a
inner join 
(SELECT id, max(rev) rev FROM Table1 GROUP BY id) x on x.id =a.id and x.rev =a.rev
2
Abdul Samad

تفسير

هذه ليست SQL نقية. سيستخدم هذا SQLAlchemy ORM.

جئت إلى هنا بحثًا عن مساعدة SQLAlchemy ، لذا سأكرر إجابة Adrian Carneiro مع إصدار python/SQLAlchemy ، وتحديداً جزء الصلة الخارجي.

يجيب هذا الاستعلام عن سؤال:

"هل يمكنك إرجاع السجلات في مجموعة السجلات هذه (بناءً على نفس المعرف) التي تحتوي على أعلى رقم إصدار".

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

الشفرة

MyTableAlias = aliased(MyTable)
newest_records = appdb.session.query(MyTable).select_from(join(
    MyTable, 
    MyTableAlias, 
    onclause=and_(
        MyTable.id == MyTableAlias.id,
        MyTable.version_int < MyTableAlias.version_int
    ),
    isouter=True
    )
).filter(
    MyTableAlias.id  == None,
).all()

تم اختباره على قاعدة بيانات PostgreSQL.

0
Ian A McElhenny

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

CREATE TABLE #temp1
(
    id varchar(20)
    , rev int
)
INSERT INTO #temp1
SELECT a.id, MAX(a.rev) as rev
FROM 
    (
        SELECT id, content, SUM(rev) as rev
        FROM YourTable
        GROUP BY id, content
    ) as a 
GROUP BY a.id
ORDER BY a.id

ثم انضمت إلى هذه القيم القصوى (# temp1) لجميع مجموعات المعرف/المحتوى المحتملة. من خلال القيام بذلك ، أقوم بطبيعة الحال بتصفية مجموعات المعرف/المحتوى غير الحد الأقصى ، وتركت مع قيم المراجعة القصوى فقط لكل منها.

SELECT a.id, a.rev, content
FROM #temp1 as a
LEFT JOIN
    (
        SELECT id, content, SUM(rev) as rev
        FROM YourTable
        GROUP BY id, content
    ) as b on a.id = b.id and a.rev = b.rev
GROUP BY a.id, a.rev, b.content
ORDER BY a.id
0
Richard Ball

يمكنك تحديد الاختيار دون صلة عندما تقوم بدمج rev و id في قيمة maxRevId واحدة لـ MAX() ومن ثم تقسيمها إلى القيم الأصلية:

SELECT maxRevId & ((1 << 32) - 1) as id, maxRevId >> 32 AS rev
FROM (SELECT MAX(((rev << 32) | id)) AS maxRevId
      FROM YourTable
      GROUP BY id) x;

يكون هذا سريعًا بشكل خاص عند وجود صلة معقدة بدلاً من جدول واحد. مع النهج التقليدية سيتم ربط معقدة مرتين.

تكون التركيبة أعلاه بسيطة مع وظائف البت عندما يكون rev و idINT UNSIGNED (32 bit) وتناسب القيمة المدمجة BIGINT UNSIGNED (64 bit). عندما يكون id & rev أكبر من قيم 32 بت أو مصنوعًا من أعمدة متعددة ، فإنك تحتاج إلى دمج القيمة في على سبيل المثال قيمة ثنائية مع حشوة مناسبة لـ MAX().

0
zovio