لدي هذا الجدول للمستندات (الإصدار المبسط هنا):
+------+-------+--------------------------------------+
| id | rev | content |
+------+-------+--------------------------------------+
| 1 | 1 | ... |
| 2 | 1 | ... |
| 1 | 2 | ... |
| 1 | 3 | ... |
+------+-------+--------------------------------------+
كيف أحدد صفًا واحدًا لكل معرّف وأكبر لفة؟
مع البيانات المذكورة أعلاه ، يجب أن تحتوي النتيجة على صفين: [1, 3, ...]
و [2, 1, ..]
. أنا أستخدمMySQL.
أستخدم حاليًا عمليات الفحص في حلقة while
لاكتشاف وفحص الكتابة القديمة من مجموعة النتائج. ولكن هل هذه هي الطريقة الوحيدة لتحقيق النتيجة؟ ليس هناكSQLالحل؟
تحديث
كما تشير الإجابات ، يوجد is حل SQL ، و هنا sqlfiddle demo .
التحديث 2
لقد لاحظت بعد إضافة ما ورد أعلاه {sqlfiddle ، أن معدل إجابة السؤال قد تجاوز معدل التصويت الزائد للإجابات. هذا لم يكن القصد! يعتمد الكمان على الإجابات ، وخاصة الإجابة المقبولة.
كل ما تحتاجه هو جملة 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 التحركات الذكية:
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 ، فهارس ، إلخ). لذلك عند اختيار نهج واحد على الآخر ، معيار . وتأكد من اختيار الشخص الذي يجعلك أكثر منطقية.
التفضيل هو استخدام رمز أقل عدد ممكن ...
يمكنك القيام بذلك باستخدام IN
جرب هذا:
SELECT *
FROM t1 WHERE (id,rev) IN
( SELECT id, MAX(rev)
FROM t1
GROUP BY id
)
في رأيي أنه أقل تعقيداً ... أسهل في القراءة والمحافظة عليه.
حل آخر هو استخدام استعلام فرعي مرتبط:
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 ينضم إلى مجموعات ، يمكن أن يتأثر أدائها بشدة بحجم المجموعات ...
أنا أشعر بالذهول لعدم تقديم إجابة حل وظيفة نافذة 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
.
لا يمكنني أن أضمن الأداء ، لكن هذه خدعة مستوحاة من قيود 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 بحيث على سبيل المثال
إذا قمت بذلك بشكل صحيح ، فيجب أن تسفر مقارنة السلسلة المكونة من رقمين عن نفس "الحد الأقصى" مقارنةً بالرقمين ومن السهل تحويلها مرة أخرى إلى الرقم الأصلي باستخدام وظيفة السلسلة الفرعية (المتوفرة في شكل أو آخر إلى حد كبير في كل مكان).
شيء من هذا القبيل؟
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)
نظرًا لأن هذا هو السؤال الأكثر شيوعًا فيما يتعلق بهذه المشكلة ، سأعيد نشر إجابة أخرى عليها هنا أيضًا:
يبدو أن هناك طريقة أبسط للقيام بذلك (ولكن فقط في MySQL ):
select *
from (select * from mytable order by id, rev desc ) x
group by id
يرجى الرجوع إلى إجابة المستخدم البوهيمي في هذا السؤال لتقديم هذه الإجابة المختصرة والأنيقة لهذه المشكلة.
تحرير: على الرغم من أن هذا الحل يعمل لكثير من الأشخاص ، إلا أنه قد لا يكون مستقرًا على المدى الطويل ، لأن MySQL لا تضمن أن تُرجع عبارة GROUP BY قيمًا ذات معنى للأعمدة غير المدرجة في قائمة GROUP BY. لذلك استخدم هذا الحل على مسؤوليتك الخاصة!
أحب استخدام حل 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
)
سيؤدي هذا إلى تحديد جميع السجلات ذات القيمة القصوى داخل المجموعة ويسمح لك بتحديد الأعمدة الأخرى.
الحل الثالث الذي بالكاد أراه ذكره هو 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
بشكل أسرع.
ليس 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
أعتقد ، أنت تريد هذا؟
select * from docs where (id, rev) IN (select id, max(rev) as rev from docs group by id order by id)
SQL Fiddle: تحقق هنا
إذا كان لديك العديد من الحقول في عبارة تحديد وتريد أحدث قيمة لجميع هذه الحقول من خلال رمز محسن:
select * from
(select * from table_name
order by id,rev desc) temp
group by id
هناك طريقة أخرى للقيام بالمهمة وهي استخدام 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
يمثل عدد السجلات في الجدول!
أود استخدام هذا:
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 لكل سجل تم مسحه ضوئيًا ، مما يجعل الاستعلام يستغرق وقتًا طويلاً للغاية.
وماذا عن هذا:
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
هذا الحل يجعل اختيار واحد فقط من 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
SELECT *
FROM Employee
where Employee.Salary in (select max(salary) from Employee group by Employe_id)
ORDER BY Employee.Salary
أي من هذه الإجابات قد عملت بالنسبة لي.
هذا هو ما عملت لي.
with score as (select max(score_up) from history)
select history.* from score, history where history.score_up = score.max
تم فرز حقل المراجعة بترتيب عكسي ثم تم التجميع حسب المعرف الذي أعطى الصف الأول من كل مجموعة وهو أعلى قيمة مراجعة.
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
إليك طريقة لطيفة للقيام بذلك
استخدم الكود التالي:
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)
أحب القيام بذلك عن طريق ترتيب السجلات حسب بعض الأعمدة. في هذه الحالة ، قم بتصنيف قيم 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
مرتين.
إليك حل آخر لاسترداد السجلات فقط من خلال حقل يحتوي على الحد الأقصى لقيمة هذا الحقل. هذا يعمل مع 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)
هنا حل آخر آمل أن يساعد شخص ما
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
هذه ليست 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.
لقد استخدمت أدناه لحل مشكلة خاصة بي. قمت أولاً بإنشاء جدول مؤقت وأدرجت القيمة القصوى للقيمة لكل معرف فريد.
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
يمكنك تحديد الاختيار دون صلة عندما تقوم بدمج 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
و id
INT UNSIGNED
(32 bit) وتناسب القيمة المدمجة BIGINT UNSIGNED
(64 bit). عندما يكون id
& rev
أكبر من قيم 32 بت أو مصنوعًا من أعمدة متعددة ، فإنك تحتاج إلى دمج القيمة في على سبيل المثال قيمة ثنائية مع حشوة مناسبة لـ MAX()
.