Search code examples
mysqlsqlsubquery

Get only first result in subquery for each row order by custom column


my brain can't resolve a simple (i think) SQL problem and i need your help. I searched a lot of Stackoverflow but can't find the right answer for my case.

What i have :

table : expense_budgets
id
per_member
maximum
from_date
organization_id
organization_type_id
created_at
updated_at


CREATE TABLE IF NOT EXISTS `expense_budgets` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `per_member` double NOT NULL,
  `maximum` double NOT NULL,
  `from_date` date DEFAULT NULL,
  `organization_id` bigint(20) unsigned DEFAULT NULL,
  `organization_type_id` bigint(20) unsigned NOT NULL,
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
)
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (1, 10, 10000, NULL, NULL, 2, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (2, 5, 5000, NULL, NULL, 3, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (3, 10, 40, NULL, NULL, 4, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (4, 5, 5000, NULL, NULL, 5, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (6, 15, 15000, '2020-09-25', NULL, 2, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (7, 15, 15000, '2020-09-29', NULL, 2, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (8, 15, 15000, '2020-09-26', NULL, 2, '2020-09-14 16:43:52', NULL);
INSERT INTO `expense_budgets` (`id`, `per_member`, `maximum`, `from_date`, `organization_id`, `organization_type_id`, `created_at`, `updated_at`) VALUES (9, 15, 15000, '2020-09-27', NULL, 2, '2020-09-14 16:43:52', NULL);

What i want : I have to get the closest expense_budget record for each of my organization types for a given date. So i apply a weight custom column to order them. If i have only one record with a null from_date that means that is the default value, if i have a date i want the closest lower from_date from now.

SELECT id, organization_type_id, case when from_date IS null then 1 when CURDATE() >= from_date THEN 3 ELSE 2 END AS weight
FROM expense_budgets
ORDER BY weight desc,from_date ASC;

But i can't find out how to integrate this in a subquery or a join to get a result to get this (if the current date is 2020-09-28) :

id;per_member;maximum;from_date;organization_id;organization_type_id;created_at;updated_at
2;5;5000;NULL;NULL;3;2020-09-14 16:43:52;NULL
3;10;40;NULL;NULL;4;2020-09-14 16:43:52;NULL
4;5;5000;NULL;NULL;5;2020-09-14 16:43:52;2020-09-28 09:46:24
9;15;15000;2020-09-27;NULL;2;2020-09-14 16:43:52;NULL

You got it ? only one record for each organization_type_id with the selected value based on the order made fron the case subquery.

In advance, thank you for your answer


Solution

  • Assuming the combination organization_type_id + from_date is unique:

    with lastbudgets as (select eb2.organization_type_id, max(eb2.from_date) as from_date
                           from expense_budgets eb2
                          where eb2.from_date is null or eb2.from_date <= now()
                          group by eb2.organization_type_id)
    select eb.* 
      from expense_budgets eb 
         inner join lastbudgets lb 
                 on lb.organization_type_id = eb.organization_type_id
                and ((lb.from_date is null and eb.from_date is null) or lb.from_date = eb.from_date)
    

    UPDATED: extra logic on null case