Sql как найти ближайшее значение

mysql> select * from test1;
+----+-------+--------+-------+
| id | count | weight | price |
+----+-------+--------+-------+
|  1 |     1 |    0.1 |   100 |
|  2 |     2 |    0.6 |   200 |
|  3 |     3 |    1.2 |   250 |
|  4 |     5 |      2 |   300 |
+----+-------+--------+-------+
4 rows in set (0.00 sec)

mysql> select * from test1 having (count-4)>=0 order by count desc limit 1;
+----+-------+--------+-------+
| id | count | weight | price |
+----+-------+--------+-------+
|  4 |     5 |      2 |   300 |
+----+-------+--------+-------+
1 row in set (0.00 sec)

Тогда вообще вот так:

(SELECT * FROM `good` WHERE qty >= 4 ORDER BY qty LIMIT 1)
UNION ALL
(SELECT * FROM `good` WHERE qty < 4 ORDER BY qty DESC LIMIT 1)
LIMIT 1

Спасибо что про union all подсказали — я уж и забыл что это как distinct работает.

SELECT * FROM (SELECT * FROM `good` WHERE qty >= 4 ORDER BY qty >= 4, qty LIMIT 1) as g
UNION 
SELECT * FROM (SELECT * FROM `good` WHERE qty < 4 ORDER BY qty < 4, qty DESC LIMIT 1) as g

Выбираете всегда первую строчку и это будет наибольшим ближайшим от 4. Если нет большего — вернется ближайший меньший от 4.

Недавно решал похожую задачу в Access. У меня было две таблицы: таблица сделок, в которой был столбец с временем сделки, и таблица цен с колонками товар, время начала действия цены и цена.
Надо было определить по какой цене были произведены сделки. В обеих таблицах было очень много строк.
Перепробовал кучу вариантов: с подзапросами, промежуточными таблицами и пр. Но все они работали очень долго, так как для каждой строки из таблицы сделок надо было выполнять подзапрос с LIMIT 1.
В итоге самым быстрым оказалось такое решение: получить две таблицы, отсортировав их по времени, далее в цикле одновременно обходить обе таблицы и выполнять пересечение вручную.

I need to find a select statement that will return either a record that matches my input exactly, or the closest match if an exact match is not found.

Here is my select statement so far.

SELECT * FROM [myTable] 
WHERE Name = 'Test' AND Size = 2 AND PType = 'p' 
ORDER BY Area DESC

What I need to do is find the closest match to the ‘Area’ field, so if my input is 1.125 and the database contains 2, 1.5, 1 and .5 the query will return the record containing 1.

My SQL skills are very limited so any help would be appreciated.

bluish's user avatar

bluish

26k27 gold badges120 silver badges179 bronze badges

asked Feb 26, 2009 at 20:07

Tester101's user avatar

get the difference between the area and your input, take absolute value so always positive, then order ascending and take the first one

SELECT TOP 1 * FROM [myTable] 
WHERE Name = 'Test' and Size = 2 and PType = 'p'
ORDER BY ABS( Area - @input ) 

answered Feb 26, 2009 at 20:12

MikeW's user avatar

MikeWMikeW

5,6821 gold badge35 silver badges43 bronze badges

2

something horrible, along the lines of:

ORDER BY ABS( Area - 1.125 ) ASC LIMIT 1

Maybe?

answered Feb 26, 2009 at 20:13

Ryan Graham's user avatar

Ryan GrahamRyan Graham

8,0182 gold badges29 silver badges32 bronze badges

1

If you have many rows that satisfy the equality predicates on Name, Size, and PType columns then you may want to include range predicates on the Area column in your query. If the Area column is indexed this could allow efficient index-based access.

The following query (written using Oracle syntax) uses one branch of a UNION ALL to find the record with minimal Area >= your target, while the other branch finds the record with maximal Area < your target. One of these two records will be the record that you are looking for. Then you can ORDER BY ABS(Area - ?input) to pick the winner out of those two candidates. Unfortunately the query is complex due to nested SELECTS that are needed to enforce the desired ROWNUM / ORDER BY precedence.

SELECT *
FROM
  (SELECT * FROM
    (SELECT * FROM
       (SELECT * FROM [myTable]
         WHERE Name = 'Test' AND Size = 2 AND PType = 'p' AND Area >= ?target
         ORDER BY Area)
       WHERE ROWNUM < 2
     UNION ALL
     SELECT * FROM 
       (SELECT * FROM [myTable]
         WHERE Name = 'Test' AND Size = 2 AND PType = 'p' AND Area < ?target
         ORDER BY Area DESC)
       WHERE ROWNUM < 2)
     ORDER BY ABS(Area - ?target))
WHERE rownum < 2

A good index for this query would be (Name, Size, PType, Area), in which case the expected query execution plan would be based on two index range scans that each returned a single row.

answered Feb 27, 2009 at 17:13

George Eadon's user avatar

SELECT * 
  FROM [myTable] 
  WHERE Name = 'Test' AND Size = 2 AND PType = 'p' 
  ORDER BY ABS(Area - 1.125)
  LIMIT 1

— MarkusQ

answered Feb 26, 2009 at 20:14

MarkusQ's user avatar

MarkusQMarkusQ

21.8k3 gold badges56 silver badges67 bronze badges

How about ordering by the difference between your input and [Area], such as:

DECLARE @InputValue DECIMAL(7, 3)

SET @InputValue = 1.125

SELECT TOP 1 * FROM [myTable]
WHERE Name = 'Test' AND Size = 2 AND PType = 'p'
ORDER BY ABS(@InputValue - Area)

answered Feb 26, 2009 at 20:13

boflynn's user avatar

boflynnboflynn

3,5041 gold badge27 silver badges28 bronze badges

0

Note that although ABS() is supported by pretty much everything, it’s not technically standard (in SQL99 at least). If you must write ANSI standard SQL for some reason, you’d have to work around the problem with a CASE operator:

SELECT * FROM myTable
WHERE Name='Test' AND Size=2 AND PType='p'
ORDER BY CASE Area>1.125 WHEN 1 THEN Area-1.125 ELSE 1.125-Area END

answered Feb 26, 2009 at 20:29

bobince's user avatar

bobincebobince

526k107 gold badges649 silver badges829 bronze badges

If using MySQL

SELECT * FROM [myTable] ... ORDER BY ABS(Area - SuppliedValue) LIMIT 1

answered Feb 26, 2009 at 20:13

Alnitak's user avatar

AlnitakAlnitak

333k70 gold badges404 silver badges492 bronze badges

Здравствуйте.
Суть задачи получить товары с близкой ценой от текущего товара (Рекомендуемые товары). Сайт на OpenCart.

К примеру текущая цена товара 50$, и нужно что бы показывались товары с самой близкой ценой, а не 100$ и не 10$.

Нашел запрос, получающий id товаров с рубрики. Однако нужно получить близкие товары по цене.
Запрос:

$query = $this->db->query("SELECT DISTINCT(p2c.product_id) FROM `" . DB_PREFIX . "product_to_category` p2c LEFT JOIN `" . DB_PREFIX . "product` p ON (p2c.product_id = p.product_id) LEFT JOIN `" . DB_PREFIX . "product_to_store` p2s ON (p.product_id = p2s.product_id) WHERE p2c.category_id IN ('" . $category_list . "') AND p2c.product_id != '" . (int)$product_id . "' AND p.status = '1' AND p.date_available <= NOW() AND p2s.store_id = '" . (int)$this->config->get('config_store_id') . "' ORDER BY RAND() LIMIT " . $limit);

Цена находится в таблице product и столбец price

vikolyada's user avatar

vikolyada

1,3998 серебряных знаков16 бронзовых знаков

задан 5 мая 2017 в 14:59

Юрий Александрович's user avatar

1

Суть задачи сводится к трем подзадачам:

  1. Получить товар с ценой больше текущей.
  2. Получить товар с ценой меньше текущей.
  3. Объединить оба результата в одно решение через union all.

    select ...
    where  ...
          p.price > current_price order by p.price limit 1
    union all 
    select ...
    where  ...
          p.price < current_price order by p.price desc limit 1
    

Если надо еще как-то ограничить, то можно еще использовать математические операции, о чем тоже не следует забывать. К примеру если цена выше не должна превышать 10% от текущей стоимости.

    and p.price > current_price and p.price < (current_price + current_price * 0.1)  

ответ дан 5 мая 2017 в 15:24

Alex Krass's user avatar

Alex KrassAlex Krass

17.4k2 золотых знака24 серебряных знака52 бронзовых знака

1

Задачу можно решить в рамках одного SELECT. Достаточно добавить такую сортировку:

SELECT ...
FROM ...
WHERE ...
ORDER BY
  CASE
    WHEN price < 'ваша стоимость'
      THEN price + (('ваша стоимость' - price) * 2)
      ELSE price
  END ASC
LIMIT N

ответ дан 6 мая 2017 в 21:57

vikolyada's user avatar

vikolyadavikolyada

1,3998 серебряных знаков16 бронзовых знаков

Иногда возникает необходимость поиска значений по базе данных находящихся в непосредственной близости от определенного значения. Это могут быть даты или числовые значения.

Я сейчас участвую в стартапе и возникла задача поиска заведений предоставляющих скидку близкой к той что у просматриваемого пользователем заведения.

Предположим что есть 5 заведений:

Заведение 1 10
Заведение 2 13
Заведение 3 15
Заведение 4 16
Заведение 5 16

И нужно их отсортировать по близости значения скидки. Простой  ORDER BY не подойдет т.к. он ищет ближайшие значения только в одну сторону,  либо в большую, либо в меньшую.

Предположим что мы открыли заведение 3, и нужно получить список ближайших по скидке заведений, запрос можно сделать  так:

SELECT *, ABS( discount - 15 ) AS discountDistance
FROM companys
ORDER BY discountDistance

Я тут намеренно не исключал Заведение 3 из списка результатов чтоб не усложнять пример, результатом вывода будет следующее:

Заведение 3 15
Заведение 4 16
Заведение 5 16
Заведение 2 13
Заведение 1 10

Такой запрос без проблем работает с небольшим кол-вом данных, но на больших таблицах его выполнение может оказаться неприемлемо долгим, и зачастую нам не нужно выбирать все записи из таблицы, а достаточно например трех.

Т.к. мы точно знаем что нам нужно мы можем помочь MySql c выполнением задачи ограничив набор данных которые базе данных придется обрабатывать. Мы точно знаем что интересующие нас значения находятся в пределах трех ближайших нижних или трех ближайших верхних значений.

Для этого мы объединим результаты работы двух SELECT команд и получим список состоящий максимум из 7 полей, из которых уже сможем быстро отобрать 3 ближайших.

SELECT *, ABS( discount - 15 ) AS discountDistance
FROM (
    (
        SELECT *
        FROM companys
        WHERE discount >=15
        ORDER BY discount
        LIMIT 3
    ) UNION ALL (
        SELECT *
        FROM companys
        WHERE discount < 15
        ORDER BY discount DESC
        LIMIT 3
    )
) AS n
ORDER BY discountDistance
LIMIT 3

Второй вариант еще не тестировал на больших объема данных, но должно быть в разы быстрее.

netwind:
да вы затейник.

ещё какой :) такого могу напридумывать :)

netwind:
select * …

спасибо, но уж мне прям так подробно-то не надо. мне «номера главы в мануале» достаточно, строчку я сам найду. в этом весь смысл, собственно ;)

netwind:
школьный курс математики

нда, приходится вспоминать :)

netwind:
как упражнение на подзапросы

скорей как кружевная салфеточка на телевизоре :D (меня не парит, а бабушка считает, что это красиво). итого, самое главное, чтоб красивости в итоге не парили (в данном случае базу/процессор) :)

просто есть сейчас время/возможность вкурить этот вопрос и по*эстетствовать.

* возможно — «псевдо»

netwind:
чисто как упражнение

это будет в итоге иметь реальное применение. т.е. теоретизирование не в пустую.

toxic steel добавил 12.01.2009 в 03:18

netwind:
по-прежнему лучший запрос уже озвучили во втором сообщении.

у меня тесты обратное показали.

на 1 000 запросов в первом варианте потребовалось 2,7 секунды, при том, что

для второго и третьего по 1,4 и 1,2

на 10 000 — 25 12 и 14

Понравилась статья? Поделить с друзьями:

Не пропустите также:

  • Как составить кодекс речевого поведения
  • Как найти гномов в скайриме
  • Как составить обжалование решения арбитражного суда
  • Формула как найти периметр восьмиугольника формула
  • Как найти ответы на волнующие вопросы

  • 0 0 голоса
    Рейтинг статьи
    Подписаться
    Уведомить о
    guest

    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии