C++ Builder
| Главная | Уроки | Статьи | FAQ | Форум | Downloads | Литература | Ссылки | RXLib | Диски |

 
хитрая задачка по SQL: надо решить в один запрос !, взять last данные с каждой машины...
Tertium
Отправлено: 04.06.2005, 18:44


Машинист паровоза

Группа: Почетный участник
Сообщений: 192



вот какая задачка. Всю башку сломал ибо в ней (башке) очень мало данных по SQL:)
есть таблица в MSSQL:

CODE

CREATE TABLE Interim_Stats (
id int IDENTITY (1, 1) NOT NULL ,
machine_id int NULL ,
currentIn int NULL ,
currentOut int NULL ,
last_query datetime NULL
)

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

Теперь вопрос:
надо одним запросом вынуть последние данные по каждой машине (уникальному machine_id), если известно, что записей с одним и тем же machine_id — полно.

Я решал так.
Запросом
CODE

select (machine_id, currentIn), max(last_query)  from interim_stats group by machine_id

получаем кортеж, состоящий из действительно максимальных дат по каждому из machine_id.
Мне это в приливе идеализма показалось кусками строк, на которых для каждого machine_id максимален last_query. И захотелось получить _остальную_ часть этих строк:)
Но это, очевидно, не так. Так что решение зашло в тупик.
А хочется именно одним запросом, потому что иначе — это запрос для каждого machine_id, которых — туева хуча.
** avtoritet
Отправлено: 05.06.2005, 00:29


Не зарегистрирован







А если так:

select TOP1 (machine_id, currentIn), max(last_query) from interim_stats order by machine_id DESC;

Попробуй, и получишь, как я думаю, одну самую последнюю запись.
** avtoritet
Отправлено: 05.06.2005, 01:12


Не зарегистрирован







лИЧНО мне вопрос не совсем понятен! Можно как-нибудь иначе сформирвать вопрос? Что занчить остальная часть этих строк?
Tertium
Отправлено: 05.06.2005, 13:53


Машинист паровоза

Группа: Почетный участник
Сообщений: 192



остальная часть строк — это currentIn, где machine_id и last_query — те что получены.
Кстати, запрос когда писал, ошибся, так сказать выдал желаемое за действительное smile.gif :
CODE

select machine_id, max(last_query)  from interim_stats group by machine_id

вот так работает, но выдаёт только последние даты по каждому из идов.

Надо же вот что:
- выделить группы по machine_id
- внутри каждой найти строку, старшую по last_query
- вывести для каждой группы найденную строку целиком

таким образом, получатся ПОСЛЕДНИЕ ДАННЫЕ ДЛЯ КАЖДОЙ МАШИНЫ!!

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

Первоначальный вариант был такой. Первым запросом находим distinct(machine_id) as mid, затем по каждому пишем такой запросище:
CODE

use gambling select currentIn,currentOut from stats where machine_id=mid[i] and last_query=(select max(last_query) from stats where machine_id=mid[i])

Тоже работает, но в три-четыре раза медленнее (запрос выполняемый в цикле силшком загружен — вложенный запрос, да ещё и с max).

вот. Пока я научился решать задачу в два прохода. Двумя спсобами даже. А вот одним запросом не могу.

Кстати, твой запрос сервер материт. Да и если б не материл, не то это
** avtoritet
Отправлено: 06.06.2005, 02:29


Не зарегистрирован







Слушай, будь добор, пришли саму базу данных, только если она не тяжелая ( ну не более 1 м)?

avtoritet@fromru.com
full_lamer
Отправлено: 06.06.2005, 08:12


Машинист паровоза

Группа: Участник
Сообщений: 225



2Tertium
я конечно дико извиняюсь, но что то я не пойму чего Вам нужно от базы? честно говоря я не совсем понял... нужно получить именно последнее обнавление для каждой машины, или некоторое последнее n-нное количество записей по каждой машине, или просто отделить записи по каждой машине друг от друга?
AVC
Отправлено: 06.06.2005, 09:44


Ветеран

Группа: Модератор
Сообщений: 1583



Вариант, проверен на Oracle, старался соблюдать стандарт синтаксиса SQL
(просто в Oracle такое делается много проще и эффективнее)
CODE

Select *
From avc.Int_S
Where ID in (
Select Max (i1.id)
From
avc.Int_S i1
,(Select machine_id, max(last_query) as max_last_query
From avc.Int_S
Group by machine_id
) t1
Where 1 = 1
and t1.machine_id = i1.machine_id
and t1.max_last_query = i1.last_query
Group by i1.machine_id, i1.last_query
)


Отбирает ровно по одной строке для каждой machine_id с максимальным last_query и ID (id это для "ровно одной строки")
Если вы гарантируете что для одного machine_id нет записей с одинаковым last_query то от верхнего запроса можно отказаться

Отредактировано AVC — 06/06/2005, 08:52
Tertium
Отправлено: 06.06.2005, 13:35


Машинист паровоза

Группа: Почетный участник
Сообщений: 192



2 avtoritet:
сама прога тяжелее метра и намного, ибо БД это только её малая часть. а структура БД — простенькая:

SQL
create database Gambling
go

use Gambling
go

CREATE TABLE Automats (
machine_id int primary key,
creation datetime NULL ,
change datetime NULL ,
last_query datetime NULL ,
club_creator_id int NULL ,
game_id int NULL ,
box_name varchar (255) COLLATE Cyrillic_General_CI_AS NULL ,
prog_ver varchar (50) COLLATE Cyrillic_General_CI_AS NULL ,
inventory varchar (50) COLLATE Cyrillic_General_CI_AS NULL ,
type tinyint NULL ,
absolute_val bit NULL ,
comments varchar (255) COLLATE Cyrillic_General_CI_AS NULL ,
initIn bigint NULL ,
initOut bigint NULL
)
GO

CREATE TABLE Games (
game_id int IDENTITY (1, 1) primary key,
name char (255) COLLATE Cyrillic_General_CI_AS NOT NULL ,
description char (255) COLLATE Cyrillic_General_CI_AS NULL
)
GO

CREATE TABLE Stats (
id int IDENTITY (1, 1) primary key,
machine_id bigint NULL ,
currentIn bigint NULL ,
currentOut bigint NULL ,
last_query datetime NULL
)
GO




2AVS:
двойное smile.gif спасибо. будем разбираться
** avtoritet
Отправлено: 06.06.2005, 13:49


Не зарегистрирован







Как раз и нужно эту самую маленкую часть — не программу а БД. Так меньше возни будет!
Tertium
Отправлено: 06.06.2005, 16:02


Машинист паровоза

Группа: Почетный участник
Сообщений: 192



2 AVS:
работает, классный запрос. Правда, пришлось ещё поддерживать mySQL, а уж там-то и запросов вложенных нет... так что удовольствие получилось чисто теоретическое.
Но запрос!.. Я уж успел и забыть про вложенные селекты... Спасибо.

Отредактировано Tertium — 06/06/2005, 16:02
Gedeon
Отправлено: 06.06.2005, 16:55


Ветеран

Группа: Модератор
Сообщений: 1742



2 Tertium
Вы чего безобразничаете и AVC AVSом обзываете? smile.gif
AVC
Отправлено: 06.06.2005, 17:04


Ветеран

Группа: Модератор
Сообщений: 1583



QUOTE

пришлось ещё поддерживать mySQL, а уж там-то и запросов вложенных нет

Действительно? А у меня получилось smile.gif и именно в таком синтаксисе.

А то еще можно написать так
CODE

Select
i21.*
From
 avc.Int_S i21
,(Select Max (i1.id) as max_id
 From
   avc.Int_S i1
 ,(Select machine_id, max(last_query) as max_last_query
   From avc.Int_S
   Group by machine_id
  ) t1
 Where 1 = 1
   and t1.machine_id = i1.machine_id
   and t1.max_last_query = i1.last_query
 Group by i1.machine_id, i1.last_query
)t22
Where i21.ID = t22.max_id

Удачи. И как заметил Gedeon мой ник AVC.
Tertium
Отправлено: 08.06.2005, 03:33


Машинист паровоза

Группа: Почетный участник
Сообщений: 192



2AVC
Пардон за обзывательства smile.gif
И нет — в mySQL ниже 4.1 то, что у вас написано — преступление:
SQL
select ... from table1, (select ... from ...) as t1


2all
После некоторой туевой хучи времени, убитой на вникание в запрос, написание тестового софта и скачивание mySQL 4.1 с поддержкой вложенных запросов (38 M!!!), привожу результаты опытов и конечный запрос.
Предложенный выше запрос всем хорош, кроме скорости, а именно — он выполняется чуть дольше заполнения базы (по крайней мере на 1000 позициях).
Сгарантировал уникальность пар machine_id-last_query, как опять же было предложено. Потом выяснилось, что надо не только последний вход выход, но и данные об устройстве, лежащие в отдельной таблице с machine_id в кач-ве неинкрементного ключа. И вот что получилось:
SQL
Select a1.*, i1.currentIn, i1.currentOut from stats as i1,
Automats as a1,
(Select machine_id, max(last_query) as max_last_query From stats Group by machine_id) as t1
where (i1.machine_id=t1.machine_id) and i1.last_query=t1.max_last_query) and (a1.machine_id=t1.machine_id)

Эта красота выполняется (на 1000 записях при 100 допускаемых уникальных machine_id) около 30-40 мс причём из одной или из двух таблиц (stats и automats) почти неважно. Способ, который я использовал ранее (двухэтапный) — 344-360 мс. Полный же запрос с учётом неуникальности пар machine_id-last_query (исходный предложенный AVC) — 18540 -18766 smile.gif !!
Завтра будет генеральный кипеть за задержку, но зато я доволен. Копать нужно до конца smile.gif Даже когда это не необходимо smile.gifsmile.gifsmile.gif

Ещё раз хоца скать агромное спасибо AVC. У меня в рез-те даже угол зрения на SQL другой стал smile.gif Не так часто приходится с ним сталкиваться.
Это было как открытие для себя регулярных выражений лет в 18 smile.gif

Отредактировано Tertium — 08/06/2005, 03:36
Tertium
Отправлено: 08.06.2005, 20:39


Машинист паровоза

Группа: Почетный участник
Сообщений: 192



И знаете, что самое обидное? Эволюция проекта пришла к тому, что данные In и OUT постоянно пишутся в таблицу с данными машин. Где каждый machine_id — уникальный. Где всё тихо и просто, и нет десятитысячных позиций.
Где хватает запроса:
SQL
select * from Automats order by machine_id !!!!!!!

Абыдно, да...

Ваше здоровье!..

Отредактировано Tertium — 08/06/2005, 20:40

Вернуться в Работа с базами данных в C++Builder