Temat: SQL - zwracanie dwóch pierwszych rekordów

Witam.

Mam 2 tabele (w nawiasie pola w nich)

t1 (ID, OWNER)
t2 (t1_id, ACTION)

Chciałbym dla wybranych ownerów z t1 zwrócić 2 pierwsze wpisy z t2.

Wszystkie wpisy bym zwrócił tak:

select owner, action
from t1, t2
where id = t1_id and owner = "JA";


A jak ograniczyć to, aby tylko pierwsze 2 rekordy dla wszystkich ID zwrócił?
Marcin Preś

Marcin Preś Informatyk,
Multitechnika S.C.

Temat: SQL - zwracanie dwóch pierwszych rekordów

4s w google ... http://ludera.info/it/jak-wyciagnac-kilka-pierwszych-w...

Temat: SQL - zwracanie dwóch pierwszych rekordów

Ok, a jak chcę dla przedziału ownerów i aby mi dla każdego ownera 2 pierwsze?

konto usunięte

Temat: SQL - zwracanie dwóch pierwszych rekordów

rank_over
http://technet.microsoft.com/pl-pl/library/ms176102%28...

musisz zrobić PARTITION BY owner

całość w podzapytaniu


select t.owner, t,action, t.RANK
FROM (
select owner, action, RANK() OVER
(PARTITION BY owner ) AS 'RANK'
from t1, t2
where id = t1_id and owner = "JA"
) as t
WHERE t.RANK <= 2;

konto usunięte

Temat: SQL - zwracanie dwóch pierwszych rekordów

@Maciej: Podanie rodzaju DBMS-a byłoby bardzo na miejscu...

Temat: SQL - zwracanie dwóch pierwszych rekordów

Przepraszam. Dopiero zaznajamiam się z SQLem pod kilkoma postaciami i wszystko jest dla mnie nowością.

Ogólnie to na MS SQLu udało mi się to zrobić bez problemu poprzez przykład Przemka.
Dzięki.

Jednak głównie będę potrzebował do MySQLa i niestety nie mogę sobie poradzić, bo nie ma chyba czegoś takiego dla tej bazy :-(Maciej Popławski edytował(a) ten post dnia 14.12.12 o godzinie 14:06

konto usunięte

Temat: SQL - zwracanie dwóch pierwszych rekordów

Maciej Popławski:
Jednak głównie będę potrzebował do MySQLa i niestety nie mogę sobie poradzić, bo nie ma chyba czegoś takiego dla tej bazy :-(

jest ale inaczej ;)

http://stackoverflow.com/questions/1438764/rank-over-i...

 SELECT  @rank := @rank + (@value <> value),
@value := value
FROM (
SELECT @rank := 0,
@value := -1
) vars,
mytable
ORDER BY
value


myk polega na tym że korzystasz ze zmiennych w SELECT i zerujesz wskaźnik jak zmieni się wartość bierząca względem poprzedniej

Temat: SQL - zwracanie dwóch pierwszych rekordów

Ok, zaczynam rozumieć ;-).

W moim przykładzie mam tabelę ticket_history i jedno z pól to ticket_id.

I rzeczywiście dla takiego zapytania:

 SELECT ticket_id,  @rank := @rank + (@value <> ticket_id),
@value := ticket_id
FROM (
SELECT @rank := 0,
@value := -1
) vars,
ticket_history
WHERE ticket_id > 4000 and ticket_id < 5000
ORDER BY
ticket_id ;

Ładnie mi przypisuje dla ID 4001 rank 1, dla 4002 rank 2 etc..

Ale teraz jak to zaadoptować do tego, aby dla powiedzmy ID 4001 wyrzucił mi tylko 2 rekordy?

PS. Tamten temat widziałem, ale totalnie nie zajarzyłem, że to ogólny przykład. Myślałem, że stricto do tamtego rozwiązanie.Maciej Popławski edytował(a) ten post dnia 14.12.12 o godzinie 14:58
Marcin Mackiewicz

Marcin Mackiewicz Programista JAVA, RS
Adware Polska

Temat: SQL - zwracanie dwóch pierwszych rekordów

W pierwszej kolejności to zastanowiłbym się co kryje się pod pojęciem 'pierwsze dwa rekordy' ?

Z zapytania
select owner, action
from t1, t2
where id = t1_id and owner = "JA";
wynika, że rekordy zostaną zwrócone tak jak zechce engine bazy. Proponowałbym przesortować po polu 'action', żeby mieć ogólne pojęcie o tym które rekordy zostaną zrzucone (no chyba, że to nie jest istotne).

Z zapytania przykładowego wynika również, że stosujesz je tyle razy ile masz id do sprawdzenia. Jak chcesz ograniczyć to użyj klauzuli limit
select 
owner,
action
from t1, t2
where id = t1_id and owner = "JA"
limit 2;
Otrzymasz wtedy dla jednego id dokładnie dwa rekordy w wyniku.

Na końcu pytasz jak wyświetlić dla wszystkich id po dwa rekordy co kompletnie zmienia podejście do tematu.

Przygotowuję sobie zestaw danych do testowania
drop table if exists t2;
drop table if exists t1;

create table t1(
ID int primary key,
OWNER varchar(500)
)
engine = innodb
default character set = utf8;

create table t2(
t1_id int,
ACTION varchar(500),
constraint t1_fkey foreign key (t1_id)
references t1 (ID) match simple
on update no action on delete cascade
)
engine = innodb
default character set = utf8;

insert into t1 values ( 1, 'A' );
insert into t1 values ( 2, 'B' );
insert into t1 values ( 3, 'C' );

insert into t2 values ( 1, 'Akcja 1' );
insert into t2 values ( 1, 'Akcja 2' );
insert into t2 values ( 1, 'Akcja 3' );
insert into t2 values ( 2, 'Akcja 1' );
insert into t2 values ( 3, 'Akcja 1' );
insert into t2 values ( 3, 'Akcja 2' );
a następnie sporządzam zapytanie SQL
set @oid = -9999;
set @count = 1;

select
dane.*
from (
select
result.*,
@count := if ( @oid = result.ID, @count + 1, 1 ) as lp,
@oid := result.ID
from (
select
t1.ID,
t1.OWNER,
t2.action
from t1
join t2 on t1.ID = t2.t1_id
order by id
) as result
) as dane
where dane.lp < 3;
Zapytanie zwraca dane dla wszystkich id ograniczając jednocześnie liczbę zwracanych rekordów z tabeli t2 do 2.

Tworzę dwie zmienne: @oid która przechowuje id z poprzedniego rekordu (na starcie dziwna wartość bo od czegoś trzeba zacząć. Musi to być liczba inna od najniższego id w tabeli t1) oraz licznik @count który będzie zliczał wiersze danego id.

1. Wyszukuję id, owner, action z tabel (najgłębsze podzapytanie; alias result) pamiętając o tym, że wynik musi być posortowany po id (bo dane w tabelach mogą leżeć różnie a fakt, że chcemy zliczać wiersz po wierszu wymusza uporządkowaną strukturę na której będziemy pracowali)

2. Wyświetlam jeszcze raz dane z 'result' (w podzapytaniu alias dane) i dołączam do nich lp czyli wynik zmiennej count powiększony o 1 ale tyko wtedy kiedy bieżący oraz poprzedni rekord są takie same. Jeżeli są różne to ustawiam licznik na 1 i taką wartość wpisuje do lp. Po wykonaniu operacji wpisania liczby do kolumny lp przypisuje id z bieżącego rekordu do zmiennej @oid

3. Na samym końcu ograniczam zwracany wynik do lp < 3 czyli wyświetlam tylko dwa rekordy (gdzie lp = 1 lub lp = 2

Jeżeli masz listę id które potrzebujesz zwrócić dodaj na końcu w where kod:
 and dane.id in ('JA', 'TY', 'ONI')

Temat: SQL - zwracanie dwóch pierwszych rekordów

Dzięki wielkie za takie wytłumaczenie. Przeczytane, teraz trawię, potem spróbuję zamontować do mnie :-). Jak coś to wrócę do tematu jeszcze.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Udało się zaadoptować do moich danych :-). Dzięki wielkie za pomoc :-)

Temat: SQL - zwracanie dwóch pierwszych rekordów

Hmmm, a można takie zapytanie bez SET dać? Te zmienne czasami nie mają wartości początkowej 0?

Pytam, bo przekazuję zapytanie z poziomu VBA poprzez ADODB i wysypuje mi się na SET, gdzie zapytanie bezpośrednio w bazie działa.
Piotr Jesionek

Piotr Jesionek v-ce prezes w
Prospeo Sp. z o.o.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Spróbuję to rozgryźć na swój sposób.
w tabeli t1 masz rekordy: A, B, C
w tabeli t2 masz wpisy, które odnoszą się do rekordu A w tabeli t1: A1, A2, A3, A4, A5,...

I chciałbyś, aby zapytanie dla warunku A pokazało dwa pierwsze rekordy: A1, A2.

Mam dwie niejasności:
1) w jakim układzie chcesz prezentować te dane: rekord po rekordzie, czy w postaci tabeli przestawnej?
Rekord po rekordzie wyglądałby tak:
A, A1
A, A2
W tabeli przestawnej wyglądałby tak:
A, A1, A2

2) co jest wyznacznikiem kolejności w tabeli t2?

Wybaczcie ale rozwiązania podane wcześniej wydają mi się dość skomplikowane i mam wrażenie że da się to załatwić prościej.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Ok to od początku:

T1 (ID, Pole2, Pole3)
T2 (ID, T1_ID, P1, P2).

Połączenie T1:ID <-> T2:T1_ID

Szukam po T1 zakresu ID: Powiedzmy ID od 100 do 200.
Dla tych ID, potrzebuję znaleźć pierwsze 2 rekordy w T2.
(Sortowanie nie ma znaczenia, bo zawsze mogę dodać sobie gdzieś ORDER BY).
Zwracamy wszystkie pola obok siebie, czyli:
Select T1.*, T2.* FROM [...] 
Piotr Jesionek

Piotr Jesionek v-ce prezes w
Prospeo Sp. z o.o.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Maciej Popławski:
t1 (ID, OWNER)
t2 (t1_id, ACTION)
Pierwsze rozwiązanie jakie przychodzi mi na myśl to użycie funkcji. Miałaby ona mniej więcej taką postać:
fn (@ID, @pozycja)
DECLARE @pole1, @pole2
SELECT TOP 1 @pole1=alias1.ACTION, @pole2=alias2.ACTION
FROM t2 alias1, t2 alias2
WHERE alias2.ACTION>alias1.ACTION
IF @pozycja=1 RETURN @pole1
IF @pozycja=2 RETURN @pole2

zapytanie wywołujesz w ten sposób:
SELECT t1.ID, t1.OWNER, [pole1]=fn(t1.ID, 1), [pole2]=fn(t1.ID, 2),

Ta metoda jest trochę mało elegancka - warunek łączenia alias2.ACTION>alias1.ACTION jest nie wydajny i w pewnych okolicznościach może nie zadziałać. Gdyby jednak tabelę t2 zaopatrzyć we własny licznik rekordów i odnieść się do niego to działałoby to już dużo lepiej.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Od razu powiem, że bazy nie mogę ruszać. Jedynie mam uprawnienia SELECT.
Piotr Jesionek

Piotr Jesionek v-ce prezes w
Prospeo Sp. z o.o.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Maciej Popławski:
Od razu powiem, że bazy nie mogę ruszać. Jedynie mam uprawnienia SELECT.
Bez ruszania bazy nie ma takiego fanu, ale może spróbuj analogicznie:

SELECT * FROM t1 JOIN
--(SELECT t1_ID=alias1.t1_ID, pole1=MIN(alias1.ACTION), pole2=MIN(alias2.ACTION)
--FROM t2 alias1, t2 alias2
--WHERE alias2.ACTION>alias1.ACTION
--GROUP BY alias1.t1_ID) t2
ON t1.ID=t2.t1_ID

W moim zamyśle podzapytanie powinno zwrócić jeden rekord dla każdego t1_ID, a w tym rekordzie powinny wystąpić pola [pole1] i [pole2] o różnych wartościach. Nie testowałem, musisz sprawdzić. Symbolu "--" użyłem dla większej czytelności - nie mają żadnego znaczenia.Piotr Jesionek edytował(a) ten post dnia 18.12.12 o godzinie 16:01

konto usunięte

Temat: SQL - zwracanie dwóch pierwszych rekordów

Piotr Jesionek:
Maciej Popławski:
Od razu powiem, że bazy nie mogę ruszać. Jedynie mam uprawnienia SELECT.
Bez ruszania bazy nie ma takiego fanu, ale może spróbuj analogicznie:

SELECT * FROM t1 JOIN
--(SELECT t1_ID=alias1.t1_ID, pole1=MIN(alias1.ACTION), pole2=MIN(alias2.ACTION)
--FROM t2 alias1, t2 alias2
--WHERE alias2.ACTION>alias1.ACTION
--GROUP BY alias1.t1_ID) t2
ON t1.ID=t2.t1_ID

W moim zamyśle podzapytanie powinno zwrócić jeden rekord dla każdego t1_ID, a w tym rekordzie powinny wystąpić pola [pole1] i [pole2] o różnych wartościach. Nie testowałem, musisz sprawdzić. Symbolu "--" użyłem dla większej czytelności - nie mają żadnego znaczenia.


Poza tym, że to oznacza komentarz do końca linii to rzeczywiście nie ma znaczenia :)
Piotr Jesionek

Piotr Jesionek v-ce prezes w
Prospeo Sp. z o.o.

Temat: SQL - zwracanie dwóch pierwszych rekordów

Szymon G.:
Piotr Jesionek:
Maciej Popławski:
Od razu powiem, że bazy nie mogę ruszać. Jedynie mam uprawnienia SELECT.
Bez ruszania bazy nie ma takiego fanu, ale może spróbuj analogicznie:

SELECT * FROM t1 JOIN
--(SELECT t1_ID=alias1.t1_ID, pole1=MIN(alias1.ACTION), pole2=MIN(alias2.ACTION)
--FROM t2 alias1, t2 alias2
--WHERE alias2.ACTION>alias1.ACTION
--GROUP BY alias1.t1_ID) t2
ON t1.ID=t2.t1_ID

W moim zamyśle podzapytanie powinno zwrócić jeden rekord dla każdego t1_ID, a w tym rekordzie powinny wystąpić pola [pole1] i [pole2] o różnych wartościach. Nie testowałem, musisz sprawdzić. Symbolu "--" użyłem dla większej czytelności - nie mają żadnego znaczenia.


Poza tym, że to oznacza komentarz do końca linii to rzeczywiście nie ma znaczenia :)

Sformułuję to inaczej żeby nie było niejasności. Użyłem symbolu "--" aby zrobić wcięcie. Spacja w edytorze GoldenLine nie robi wcięć. Podany przeze mnie przykład jest w pseudo kodzie (choć zbliżonym do SQL) - nie należy go traktować dosłownie ale jako przykład poglądowy. Może niezbyt trafnym było użycie "--" bo jak wiemy w MS SQL jest to symbol komentarza.
Niech będzie więc:

SELECT * FROM t1 JOIN
/**/(SELECT t1_ID=alias1.t1_ID, pole1=MIN(alias1.ACTION), pole2=MIN(alias2.ACTION)
/**/FROM t2 alias1, t2 alias2
/**/WHERE alias2.ACTION>alias1.ACTION
/**/GROUP BY alias1.t1_ID) t2
ON t1.ID=t2.t1_ID

Dziękuję za poprawkę.Piotr Jesionek edytował(a) ten post dnia 18.12.12 o godzinie 22:24
Marcin Mackiewicz

Marcin Mackiewicz Programista JAVA, RS
Adware Polska

Temat: SQL - zwracanie dwóch pierwszych rekordów

To pisania kodu służy bbcode i znacznik code. Wtedy spacja jest traktowana jako spacja a treść posta czytelniejsza :)

Rozwiązanie w sumie prawie dobre z tą tabelą przestawną. Ale kod nie zadziała z założenia... Przerobiłem go na MySQL
SELECT * 
FROM t1
JOIN
(
SELECT alias1.t1_ID, MIN(alias1.ACTION) as pole1, MIN(alias2.ACTION) as pole2
FROM t2 alias1, t2 alias2
WHERE alias2.ACTION>alias1.ACTION
GROUP BY alias1.t1_ID) t2
ON t1.ID=t2.t1_ID;
W podzapytaniu łączysz tabelę t2 z tabelą t2 i sprawdzasz czy dla danego id (np 3) istnieje w tej samej tabeli akcja o niższej wartości. Okazuje się że taką znajdziesz np dla id 7 i porównanie zwróci true. Brakuje łączenia po kluczu głównym co oznacza, że dane dla konkretnych wierszy tabeli alias1 będą uzupełnione pierwszym pasującym rekordem z tabeli alias2. Bardzo ładnie wychodzi ten błąd dla moich danych gdzie id 2 posiada tylko jeden wiersz w tabeli t2 a po wykonaniu tego zapytania ma uzupełnione oba pola :)
W rozumowaniu zabrakło także zabezpieczenia na wypadek jednego rekordu w tabeli t2. Jeżeli zajdzie taki przypadek to wystąpi jedno porównanie np. 2 > 2 które zwróci false. W tym wypadku informacja o tej jednej akcji ogóle nie zostanie zwrócona w wyniku zapytania (po uwzględnieniu prawidłowego łączenia tabel).

Doczytałem po drodze, że kolejność nie ma znaczenia czyli idąc twoim rozumowaniem można zrobić po prostu tak:
  SELECT 
alias1.t1_ID,
t1.OWNER,
MIN(alias1.ACTION) as pole1,
(CASE WHEN MAX(alias2.ACTION) != MIN(alias2.ACTION)
THEN MAX(alias2.ACTION)
ELSE null END
) as pole2
FROM t2 alias1
JOIN t1 on alias1.t1_ID = t1.id
LEFT JOIN t2 alias2 on alias1.t1_ID = alias2.t1_ID
GROUP BY alias1.t1_ID
czyli zwracam najmniejszą i największą wartość ACTION. Nie można tu dodać sortowania. Nie wiem jakie są rozmiary tych tabel ale z konstrukcji i opisu wynika że stosunek ich rozmiaru to minimum 1 (t1) : 2 (t2). Oznacza to, że joinowanie tabeli t2 do tabeli t2 może być bardzo nieefektywne bo planer dwa razy będzie skakał po indeksach t2 a potem (jak nie ma indeksu na ACTION) będzie skanował tabele.
Jeżeli kolega mający problem nie ma możliwości korzystania ze zmiennych to jedyne rozwiązanie ale w przypadku naprawdę duży tabel to wyjęcie danych dla id które są potrzebne do tabel tymczasowych a następnie na nich wykonanie tego zapytania.

Następna dyskusja:

MS SQL - zliczanie zgrupowa...




Wyślij zaproszenie do