Diskuze: Náhodné vylosování soupeře
V předchozím kvízu, Online test znalostí SQL a databází, jsme si ověřili nabyté zkušenosti z kurzu.


Martin Konečný (pavelco1998):11.6.2020 15:09
Ahoj, podle mě by mělo stačit na konec dotazu přidat LIMIT 5, tedy:
INSERT INTO `tab2` (`id_player_one`) SELECT (`id_player`) FROM `tab1` ORDER BY RAND() LIMIT 5;
BuBu6:11.6.2020 15:19
To jasně není problém, ale pak mi to vypíše a doplní do tab2 pouze
id_player_one a pouze 5 hráčů, ale už mi to k nim nepřiřadí do sloupce
id_player_two soupeře. Ta druhá tabulka by měla vypadat takto:
Sloupec 1: id_player_one = id_player
Sloupec 2: id_player_two = id_player
A takto bych měl při 10 hráčích vytvořeno 5 řádků.
Pozn.: Navíc 10 hráčů je příklad, ale množství je proměnlivé.
Alesh:11.6.2020 15:22
No, jestli ho dobře chápu, tak chce vylosovat 5 dvojic, nikoli jen 5 náhodných hráčů. To co navrhuješ mu dá 5 hráčů (bez soupeře).
BuBu6 Alesh Jasné, už asi rozumím. Z hlavy takový dotaz bohužel nevymyslím, mám ale pocit, že by to mělo jít pomocí JOIN na stejnou tabulku, něco ve smyslu:
INSERT INTO `tab2` (`id_player_one`, `id_player_two`)
SELECT tab1.id_player, tab1_2.id_player
FROM tab1
JOIN tab1 AS tab1_2
WHERE tab1.id_player != tab1_2.id_player
ORDER BY RAND()
LIMIT 5;
Pokud sem nepřijde někdo chytřejší než já, tak si to pak zkusím,
jestli mě nenapadne správné řešení
BuBu6:11.6.2020 16:08
Super, tohle funguje Jen to
má drobnou, v tomto případě dost podstatnou, vadu.
V tom rozlosování mi to opakuje některé hráče, tzn., že tam nejsou
všichni a někteří jsou tam zase dvakrát. Toto lze nějak ošetřit?
Díky.
Martin Konečný (pavelco1998):11.6.2020 17:20
Určitě to ošetřit jde, ale bohužel mě nenapadá jak zkoušel jsem i udělat
podmínku, aby to vybralo jen ty, kteří již v tabulce "tab2" nejsou. Problém
ale je, že prvně běží WHERE a teprve pak ty INSERTy, takže to nebere v
úvahu ty, které se přidaly tím příkazem.
Také by mě zajímalo, zda se to dá pořešit jedním INSERT dotazem.
Kdyby slo o to ziskat jen pary, tak
- vemes seznam hracu A, B, C, D
- zamichas ho C B A D
- rozdelis na pulky C B | A D
- a spojis dvojice CA BD
Jenze tento postup nemuzes pouzit pro dalsi kolo, pokud se ti nema opakovat
par. Ani ty postupy, ktere tu zminujete
ORDER BY RAND() neni nahodne promichani, ale prirazeni nahodneho cisla a
serazeni. Kvalita toho zavisi na tom, jak nahodne cislo to je a kolik ma deset.
mist. Protoze treba rantom 1-10 znamena, ze se muzou cisla opakovat, a to treba
xx1x111x11 a order to pak seradi tak, ze jednicky pujdou po sobe, cili cisla na
pozici 5, 6, 7 budou po sobe. Coz se pri nahodnem zamichani muze stat, ano. Ale
vam se to bude stavat mnohem casteji, nez aby to byla nahoda.
Cili, ja bych to seradil nejdriv DESC, pak RAND, pak DESC (mysleno jako obracene
poradi posledniho vysledku, ne radit to podle sloupce) a opet RAND.
Nějak jsem se do toho zamotal.
Mám tabulku odehraných zápasů a tabulku všech možných zápasů (každý s
kadým).
Dále následující select:
SELECT DISTINCT vsichni.id_hrac_a, vsichni.id_hrac_b FROM vsichni
LEFT JOIN zapasy
ON vsichni.id_hrac_a = zapasy.id_hrac_a AND vsichni.id_hrac_b = zapasy.id_hrac_b
WHERE zapasy.id_hrac_a IS NULL
ORDER BY RAND() LIMIT 4
Jak mám docílit toho, aby se mi neopakovali v jednom losování soupeři?
Resp jako hráč A aby byl jednou v levém sloupci, stejně tak hráč 2 v
pravém sloupci a aby nebyl stejný hráč i křížem?
Studoval jsem příkaz DISTINCT, ale nějak mi to nefunguje
A co zkusit upravit sql dotaz pro me reseni?
Vsechny zamichas, pole rozdelis napul. A muzes to udelat pres sql nebo php.
$list <- 'SELECT * FROm players'
shuffle($list);
$list_sude = array()
$list_liche = array()
foreach ($list as $key=>$value)
if ($key&1==0) {$list_sude[] = $value;} else {$list_liche[] = $value;}
//if ($key and 0x01==0) {$list_sude[] = $value;} else {$list_liche[] = $value;}
//if ($key%2==0) {$list_sude[] = $value;} else {$list_liche[] = $value;}
//if ($a=$key/2 && floor($a)==$a) {$list_sude[] = $value;} else {$list_liche[] = $value;}
SELECT
GROUP_CONCAT (`p2`.`id`) -- id1, id2
FROM
(
SELECT `id`, FLOOR((COUNT(*)+1)/2) `num` FROM
(SELECT `id` FROM `players` ORDER BY RAND()) `p1`
) `p2`
GROUP BY `p2`.`num`
- Vyberu celou tabulku
- Nahodne ji seradim
- priradim kazdemu radku cislo 1, 2, 3
- toto cislo upravim floor((x+1)/2)
(1+1)/2 = 1; floor = 1
(2+1)/2 = 1.5; floor = 1
(3+1)/2 = 2; floor = 2
(4+1)/2 = 2.5; floor = 2
(5+1)/2 = 3; floor = 3
(6+1)/2 = 3.5; floor = 3
(docilim tim 2 po sobe jdouci id jako skupiny se stejnym cislem)
5. pak to zgrupuji podle toho cisla
Samozrejme si k tomu muzes pridat jine sloupce pres LEFT JOIN
Priklady na pouzite sql prikazy se daji vygooglovat.
Priklad toho nahodneho promichani mozna take.
Nebo by to slo pres programovy kod a ukladani do pameti, ale to sem zatim
nikdo nepouzil. Takze tohle je spis pseudokod, nez, ze by to mohlo fungovat
@a = SELECT `id` FROM `players` ORDER BY RAND()
@b = SELECT `id`, FLOOR((COUNT(*)+1)/2) `num` @a
@a = null;
SELECT b.id, c.id
FROM
@b b
LEFT JOIN @b c ON c.num=b.num AND c.id<>b.id
WHERE FLOOR(COUNT()/2)==COUNT()/2
- Ulozis si random serazene do pameti
- Pridas k tomu upravene cislo radku
- Pak si to selectem vytahnes a pripojis k tomu tu samou tabulku pres LEFT JOIN, ale pojmenujes ji aliasem c. A zadas podminku, ze se num=num, ale id<>id.
Protoze stejne cislo maji pouze 2 radky, tak rodilne id jen jeden, tak se ti
naparuji prave ty 2 id.
4. Ale vznikne to pro kazdy radek, takze je nutne odfiltrovat kazdy druhy radek,
WHERE FLOOR(COUNT()/2)==COUNT()/2
DISTINCT odstranuje duplicitni radky, pokud jsou vsechny hodnoty stejne
1, 2
2, 1 nejsou duplicity
1, 2
1, 2 jsou duplicity
BuBu6:23.6.2020 22:34
Já moc děkuji za rozsáhlou odpověď, ale já jsme úplně v koncích,
protože prostě nevím, jak to přesně do toho začlenit. Pořád mi to
píše, že GROUP_CONCAT neexistuje
Syntax error or access violation: 1630 FUNCTION soutez.GROUP_CONCAT does not exist.
Asi to vzdávám, zřejmě je to pro mě jako pro amatéra dost
složité.
Ale děkuji za pomoc.
Martin Konečný (pavelco1998):24.6.2020 2:37
A je potřeba nutně to udělat přes jeden SQL dotaz?
BuBu6:24.6.2020 3:23
Klidně bych to udělal i na třeba 10 dotazů, ale nevím jak, nějak jsem
to nepochopil
Psal jsi, ze mas MySql. Podle online dokumentace ta funkce existuje. Pokud
mas jiny sql, tak se ta funkce muze jmenovat jinak.
https://dev.mysql.com/…nctions.html
https://www.mysqltutorial.org/…roup_concat/
https://oracle-base.com/…n-techniques
https://docs.oracle.com/…tions003.htm#…
mysql: GROUP_CONCAT
oracle: RTRIM(LISTAGG(REFERENCE || ', ') WITHIN GROUP(ORDER BY 1),', ')
REFERENCE
mssql: STRING_AGG, WITHIN GROUP
Ta funkce ale dela jen to, ze z radku, ktere mas zgrupovane, veme hodnoty pro
sloupec a spoji je do stringu. Grupovat to nemusis. Jen proste jedno id bude na
jednom radku a druhe na jinem. Tak misto toho pouzijes ORDER BY
p2
.num
, abys mel ty cisla po sobe.
Nic, no, kdyz se ti nechce premyslet, ani z vysvetleni nejsi schopen
poskladat kod. Ani php kod se ti nelibi. Ani nejsi schopen s sql kodem a s
pomoci googlu si ten dotaz doladit... Co s tebou? Poradi nekdo jiny Takoveho studenta informatiky bych
teda nechtel.
BuBu6:24.6.2020 8:56
Ano mám MySql.
Nevím sice, proč by si takového studenta nechtěl, ale proseděl jsem nad
tím (ještě i předtím, než jsem poslal dotaz), nocí a miliony kombinací a
řešení a googlil kde mohl, ale nikdy jsem neobjevil konkrétní odpověď. Za
Tvé odpovědi Ti samozřejmě děkuji a vážím si toho, ale vzhledem k tomu,
že programuji pouze pro zábavu a nejsem v tom moc učený, tak jsem prostě
jen nepochopil, jak se s Tvou nápovědou poprat, protože mi to buď hází
nějaké chyby nebo nefunguje jak má.
Jsem ti popsal princip, kod si muzes poskladat sam.
- vyberes hrace
- zamichas (treba php shuffle, priklady na funkci najdes google nebo v dokmentaci)
- rozdelis na 2 poloviny
- a propojis hodnoty na stejnem radku obou polovin
Pokud na tohle nedokazes napsat kod v php, tak nevim, co ti vic poradit. Tohle umi stredoskolak po prvni hodine, treba v pascalu.
1. A B C D
2. C B A D (zamichano)
3. leva = C B, prava = A D (polovina pole)
4. vysledek = [
[ leva[0], prava[0] ],
[ leva[1], prava[1] ],
[ leva[2], prava[2] ]...
]
[C, A], [B, D]
Chapu, ze si mozna neporadis s sql, ale napsat to v php, to snad zvladnes.
A nez tam v sql do toho zacnes michat jmena, tak si to nejdriv vyres s id. Tve pokusy jsi nezverejnil, takze ti neporadime, kde delas chybu. Jenom, ze ti to pise chybu. Coz bez celeho sql prikazu se da nekdy tezko odhadnout proc. Treba tam nemas nekde zpetne apostrofy.
Martin Konečný (pavelco1998):24.6.2020 12:43
Napadají mě tři potenciální možnosti:
- Jestli víš, kolik těch insertů potřebuješ, tak jej můžeš 5x nakopírovat a pokaždé vybrat jen ty hráče, kteří ještě nejsou v té tabulce pro zápas, něco jako
INSERT INTO zapas (player1, player2)
SELECT tab1.id_player, tab1_2.id_player
FROM tab1
JOIN tab1 AS tab1_2
WHERE tab1.id_player != tab1_2.id_player AND NOT EXISTS(
SELECT 1
FROM zapas
WHERE player1 = tab1.id_player OR player2 = tab1.id_player OR player1 = tab2.id_player OR player2 = tab2.id_player
)
ORDER BY RAND()
LIMIT 1;
-- a takhle třeba 5x
- Abys nemusel duplikovat dotaz s těmi inserty, myslím, že by mělo být možné na to udělat proceduru, která umí i cyklus. Něco ve smyslu
CREATE PROCEDURE createOpponents()
BEGIN
DECLARE index INT;
SET index = 1;
cyklus: LOOP
IF index = 5 THEN
LEAVE cyklus;
END IF;
INSERT INTO zapas (player1, player2)
SELECT tab1.id_player, tab1_2.id_player
FROM tab1
JOIN tab1 AS tab1_2
WHERE tab1.id_player != tab1_2.id_player AND NOT EXISTS(
SELECT 1
FROM zapas
WHERE player1 = tab1.id_player OR player2 = tab1.id_player OR player1 = tab2.id_player OR player2 = tab2.id_player
)
ORDER BY RAND()
LIMIT 1;
SET index = index + 1;
END LOOP;
END;
-- to pak zavoláš jednoduše CALL createOpponents();
Vycházel jsem z: https://www.mysqltutorial.org/…s-loop.aspx/
- Nemít to jako databázový dotaz/skript, ale udělat si to v nějakém jazyku, např. PHP, pokud v něm děláš. Tam můžeš dělat cyklus, unset atd. o něco jednodušeji a nemusí se to hned ukládat někam do tabulky.
Asi už je to mimo mísu , ale chtěl jsem si vyřešit tento problém a
trochu se u toho něco naučit
sp: BEGIN
DECLARE counter INT DEFAULT 1;
DECLARE pocet INT DEFAULT 0;
-- nastavení čítače
SET counter = 1;
-- výpočet poloviny počtu hráčů (tabulka hráči by měla obsahovat alespoň sloupec id_hrace)
SELECT COUNT(id_hrace)/2 INTO pocet FROM hraci;
-- pokud nejsou hráči ukonči proceduru
IF pocet = 0 THEN
LEAVE sp;
END IF;
-- Vytvoření tabulky turnaj pokud neexistuje
CREATE TABLE IF NOT EXISTS `turnaj` (
`id_turnaj` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`id_hrac1` INT(11) UNSIGNED NULL DEFAULT NULL,
`id_hrac2` INT(11) UNSIGNED NULL DEFAULT NULL,
PRIMARY KEY (`id_turnaj`) USING BTREE)
COLLATE='utf8_unicode_ci'
ENGINE=InnoDB;
-- Pokud existuje její vyčištění (reset auto incrementu)
TRUNCATE TABLE turnaj;
-- vyber náhodně první polovinu hráčů a insertuj do tabulky turnaj
INSERT INTO turnaj
SELECT NULL, id_hrace, NULL FROM hraci
ORDER BY RAND()
LIMIT pocet;
-- Doplň tabulku turnaj náhodnými hráči z druhé půlky tabulky hráči
disp: LOOP
-- Vytvoř pomocnou tabulku pro výběr ( vyber všechny již použité hráče)
CREATE TABLE tmp AS
(SELECT id_hrac1 FROM turnaj
UNION
SELECT id_hrac2 FROM turnaj);
-- Doplň vždy po jednom hráči náhodně do tabulky turnaj hráče kteří ještě nebyli použiti
UPDATE turnaj AS tu SET tu.id_hrac2 =
(SELECT h.id_hrace FROM hraci AS h
LEFT JOIN tmp AS t ON h.id_hrace=t.id_hrac1
WHERE t.id_hrac1 IS NULL
ORDER BY RAND()
LIMIT 1)
WHERE tu.id_turnaj = counter ;
-- odstraň tabulku tmp pro nový běh a ukliď na konci :)
DROP TABLE tmp;
-- ukonči smyčku pokud byli doplněni všichni hráči
IF counter = pocet THEN
LEAVE disp;
END IF;
-- inkrementace čítače
SET counter = counter + 1;
END LOOP;
END
Zobrazeno 22 zpráv z 22.