2016-03-25 12 views
10

Sto cercando di unire tabelle in cui le righe corrispondono a un rapporto molti: 1 con cose "reali".Unione di tabelle complesse

Sto scrivendo un simulatore di blackjack che memorizza la cronologia di gioco in un database con una nuova serie di tabelle generate ogni corsa. I tavoli sono molto più simili a modelli, dal momento che ogni gioco ottiene il proprio set dei 3 tavoli mutabili (giocatori, mani e partite). Ecco il layout, in cui suff è un suffisso specificato dall'utente da utilizzare per l'esecuzione corrente:

- cards 
    - id INTEGER PRIMARY KEY 
    - cardValue INTEGER NOT NULL 
    - suit INTEGER NOT NULL 
- players_suff 
    - whichPlayer INTEGER PRIMARY KEY 
    - aiType TEXT NOT NULL 
- hands_suff 
    - id BIGSERIAL PRIMARY KEY 
    - whichPlayer INTEGER REFERENCES players_suff(whichPlayer) * 
    - whichHand BIGINT NOT NULL 
    - thisCard INTEGER REFERENCES cards(id) 
- matches_suff 
    - id BIGSERIAL PRIMARY KEY 
    - whichGame INTEGER NOT NULL 
    - dealersHand BIGINT NOT NULL 
    - whichPlayer INTEGER REFERENCES players_suff(whichPlayer) 
    - thisPlayersHand BIGINT NOT NULL ** 
    - playerResult INTEGER NOT NULL --AKA who won 

Solo una tabella carte è creato perché i suoi valori sono costanti.

Così, dopo aver eseguito il simulatore per due volte si potrebbe avere:

hands_firstrun 
players_firstrun 
matches_firstrun 
hands_secondrun 
players_secondrun 
matches_secondrun 

Voglio essere in grado di combinare queste tabelle se è stato utilizzato gli stessi parametri di AI per entrambe le corse (cioè players_firstrun e players_secondrun sono esattamente le stesso). Il problema è che il modo in cui sto inserendo le mani rende questo molto disordinato: cheHand non può essere un BIGSERIAL perché la relazione tra le righe hands_suff e "mani effettive" è molte: 1. matches_suff è gestito allo stesso modo perché un "gioco" di blackjack consiste in realtà in un insieme di giochi: l'insieme di coppie di ogni giocatore contro il mazziere. Quindi per 3 giocatori, hai effettivamente 3 file per ogni round.

Attualmente seleziono il più grande cheHand nella tabella, aggiungi 1 ad esso, quindi inserisco tutte le righe per una mano. Sono preoccupato che questo "query-and-insert" sarà molto lento se sto unendo 2 tabelle che potrebbero essere entrambe arbitrariamente enormi.

Quando sto unendo le tabelle, ho la sensazione che dovrei essere in grado di interrogare (interamente in SQL) i valori più grandi in cuiHand e whichGame una volta li utilizzano combinando le tabelle, incrementandole per ogni unique whichHand e whichGame nel tabella che viene unita.

(Ho visto this question, ma non gestisce l'utilizzo di un ID generato in 2 posizioni diverse). Sto usando Postgres ed è OK se la risposta è specifica.

* sadly postgres non consente nomi di tabelle con parametri, quindi è necessario eseguire la sostituzione manuale delle stringhe. Non è la fine del mondo, dal momento che il programma non è rivolto al Web e nessuno, tranne me, probabilmente si preoccupa di farlo, ma la vulnerabilità di SQL injection non mi rende felice.

** matches_suff (whichPlayersHand) originariamente stava per fare riferimento a hands_suff (whichHand) ma foreign keys must reference unique values. whichHand non è univoco perché una mano è composta da più righe, ogni riga "tiene" una sola carta. Per eseguire una query per una mano, seleziona tutte quelle righe con lo stesso valore in cuiHand. Non riuscivo a pensare a un modo più elegante per farlo senza ricorrere agli array.

EDIT:

Questo è quello che ho adesso:

thomas=# \dt 
      List of relations 
Schema |  Name  | Type | Owner 
--------+----------------+-------+-------- 
public | cards   | table | thomas 
public | hands_first | table | thomas 
public | hands_second | table | thomas 
public | matches_first | table | thomas 
public | matches_second | table | thomas 
public | players_first | table | thomas 
public | players_second | table | thomas 
(7 rows) 

thomas=# SELECT * FROM hands_first 
thomas-# \g 
id | whichplayer | whichhand | thiscard 
----+-------------+-----------+---------- 
    1 |   0 |   0 |  6 
    2 |   0 |   0 |  63 
    3 |   0 |   0 |  41 
    4 |   1 |   1 |  76 
    5 |   1 |   1 |  23 
    6 |   0 |   2 |  51 
    7 |   0 |   2 |  29 
    8 |   0 |   2 |  2 
    9 |   0 |   2 |  92 
10 |   0 |   2 |  6 
11 |   1 |   3 |  101 
12 |   1 |   3 |  8 
(12 rows) 

thomas=# SELECT * FROM hands_second 
thomas-# \g 
id | whichplayer | whichhand | thiscard 
----+-------------+-----------+---------- 
    1 |   0 |   0 |  78 
    2 |   0 |   0 |  38 
    3 |   1 |   1 |  24 
    4 |   1 |   1 |  18 
    5 |   1 |   1 |  95 
    6 |   1 |   1 |  40 
    7 |   0 |   2 |  13 
    8 |   0 |   2 |  84 
    9 |   0 |   2 |  41 
10 |   1 |   3 |  29 
11 |   1 |   3 |  34 
12 |   1 |   3 |  56 
13 |   1 |   3 |  52 



thomas=# SELECT * FROM matches_first 
thomas-# \g 
id | whichgame | dealershand | whichplayer | thisplayershand | playerresult 
----+-----------+-------------+-------------+-----------------+-------------- 
    1 |   0 |   0 |   1 |    1 |   1 
    2 |   1 |   2 |   1 |    3 |   2 
(2 rows) 

thomas=# SELECT * FROM matches_second 
thomas-# \g 
id | whichgame | dealershand | whichplayer | thisplayershand | playerresult 
----+-----------+-------------+-------------+-----------------+-------------- 
    1 |   0 |   0 |   1 |    1 |   0 
    2 |   1 |   2 |   1 |    3 |   2 
(2 rows) 

mi piacerebbe combinare in modo da avere:

hands_combined table: 
id | whichplayer | whichhand | thiscard 
----+-------------+-----------+---------- 
    1 |   0 |   0 |  6 --Seven of Spades 
    2 |   0 |   0 |  63 --Queen of Spades 
    3 |   0 |   0 |  41 --Three of Clubs 
    4 |   1 |   1 |  76 
    5 |   1 |   1 |  23 
    6 |   0 |   2 |  51 
    7 |   0 |   2 |  29 
    8 |   0 |   2 |  2 
    9 |   0 |   2 |  92 
10 |   0 |   2 |  6 
11 |   1 |   3 |  101 
12 |   1 |   3 |  8 
13 |   0 |   4 |  78 
14 |   0 |   4 |  38 
15 |   1 |   5 |  24 
16 |   1 |   5 |  18 
17 |   1 |   5 |  95 
18 |   1 |   5 |  40 
19 |   0 |   6 |  13 
20 |   0 |   6 |  84 
21 |   0 |   6 |  41 
22 |   1 |   7 |  29 
23 |   1 |   7 |  34 
24 |   1 |   7 |  56 
25 |   1 |   7 |  52 

matches_combined table: 
id | whichgame | dealershand | whichplayer | thisplayershand | playerresult 
----+-----------+-------------+-------------+-----------------+-------------- 
    1 |   0 |   0 |   1 |    1 |   1 
    2 |   1 |   2 |   1 |    3 |   2 
    3 |   2 |   4 |   1 |    5 |   0 
    4 |   3 |   6 |   1 |    7 |   2 

Ogni valore di "thiscard" rappresenta un gioco carta nella gamma [1..104] - 52 carte da gioco con un bit in più che rappresenta se è a faccia in su o a faccia in giù. Non ho pubblicato la tabella reale per motivi di spazio. Quindi il giocatore 0 (ovvero il mazziere) aveva una mano di (Seven of Spades, Queen of Spaces, 3 of Clubs) nel primo gioco.

+0

Qualcuno potrebbe spiegare il downgrade in modo da poter modificare la domanda? – Prime

+2

puoi scrivere esempi di dati che hai e i dati di cui hai bisogno. Puoi anche dare la tua domanda esistente. –

+1

Cosa dovrebbe apparire la tabella risultante lik (ovvero il risultato della combinazione)? A proposito, perché sei arrivato con uno zoo di tavoli e non hai aggiunto un 'game_id' (avresti potuto usare il valore' suff 'attuale come valore lì) per distinguere le voci che si riferiscono. Il modo attuale è un grave abuso della logica del database SQL. – rpy

risposta

4

Penso che tu non stia utilizzando PostgreSQL nel modo in cui è destinato a essere utilizzato, in più il tuo design del tavolo potrebbe non essere adatto a ciò che desideri ottenere. Mentre è stato difficile capire cosa vuoi ottenere la tua soluzione, ho scritto questo, che sembra risolvere tutto ciò che vuoi usando solo una manciata di tabelle, e funzioni che restituiscono i recordset per simulare il tuo fabbisogno per le singole esecuzioni. Ho usato Enum e tipi complessi per illustrare alcune delle caratteristiche che potreste desiderare di sfruttare dalla potenza di PostgreSQL.

Inoltre, non sono sicuro dei nomi delle tabelle con parametri (non ho mai visto nulla di simile in nessun RDBMS), ma PostgreSQL consente qualcosa di perfettamente adatto: funzioni di restituzione del recordset.

CREATE TYPE card_value AS ENUM ('1', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K'); 
CREATE TYPE card_suit AS ENUM ('Clubs', 'Diamonds', 'Hearts', 'Spades'); 
CREATE TYPE card AS (value card_value, suit card_suit, face_up bool); 

CREATE TABLE runs (
    run_id bigserial NOT NULL PRIMARY KEY, 
    run_date timestamptz NOT NULL DEFAULT CURRENT_TIMESTAMP 
); 

CREATE TABLE players (
    run_id bigint NOT NULL REFERENCES runs, 
    player_no int NOT NULL, -- 0 can be assumed as always the dealer 
    ai_type text NOT NULL, 
    PRIMARY KEY (run_id, player_no) 
); 

CREATE TABLE matches (
    run_id bigint NOT NULL REFERENCES runs, 
    match_no int NOT NULL, 
    PRIMARY KEY (run_id, match_no) 
); 

CREATE TABLE hands (
    hand_id bigserial NOT NULL PRIMARY KEY, 
    run_id bigint NOT NULL REFERENCES runs, 
    match_no int NOT NULL, 
    hand_no int NOT NULL, 
    player_no int NOT NULL, 
    UNIQUE (run_id, match_no, hand_no), 
    FOREIGN KEY (run_id, match_no) REFERENCES matches, 
    FOREIGN KEY (run_id, player_no) REFERENCES players 
); 

CREATE TABLE deals (
    deal_id bigserial NOT NULL PRIMARY KEY, 
    hand_id bigint NOT NULL REFERENCES hands, 
    card card NOT NULL 
); 

CREATE OR REPLACE FUNCTION players(int) RETURNS SETOF players AS $$ 
    SELECT * FROM players WHERE run_id = $1 ORDER BY player_no; 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION matches(int) RETURNS SETOF matches AS $$ 
    SELECT * FROM matches WHERE run_id = $1 ORDER BY match_no; 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION hands(int) RETURNS SETOF hands AS $$ 
    SELECT * FROM hands WHERE run_id = $1 ORDER BY match_no, hand_no; 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION hands(int, int) RETURNS SETOF hands AS $$ 
    SELECT * FROM hands WHERE run_id = $1 AND match_no = $2 ORDER BY hand_no; 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION winner_player (int, int) RETURNS int AS $$ 
    SELECT player_no 
    FROM hands 
    WHERE run_id = $1 AND match_no = $2 
    ORDER BY hand_no DESC 
    LIMIT 1 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION next_player_no (int) RETURNS int AS $$ 
    SELECT CASE WHEN EXISTS (SELECT 1 FROM runs WHERE run_id = $1) THEN 
     COALESCE((SELECT MAX(player_no) FROM players WHERE run_id = $1), 0) + 1 END 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION next_match_no (int) RETURNS int AS $$ 
    SELECT CASE WHEN EXISTS (SELECT 1 FROM runs WHERE run_id = $1) THEN 
     COALESCE((SELECT MAX(match_no) FROM matches WHERE run_id = $1), 0) + 1 END 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION next_hand_no (int) RETURNS int AS $$ 
    SELECT CASE WHEN EXISTS (SELECT 1 FROM runs WHERE run_id = $1) THEN 
     COALESCE((SELECT MAX(hand_no) + 1 FROM hands WHERE run_id = $1), 0) END 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION card_to_int (card) RETURNS int AS $$ 
    SELECT ((SELECT enumsortorder::int-1 FROM pg_enum WHERE enumtypid = 'card_suit'::regtype AND enumlabel = ($1).suit::name) * 13 + 
      (SELECT enumsortorder::int-1 FROM pg_enum WHERE enumtypid = 'card_value'::regtype AND enumlabel = ($1).value::name) + 1) * 
     CASE WHEN ($1).face_up THEN 2 ELSE 1 END 
$$ LANGUAGE SQL; -- SELECT card_to_int(('3', 'Spades', false)) 

CREATE OR REPLACE FUNCTION int_to_card (int) RETURNS card AS $$ 
    SELECT ((SELECT enumlabel::card_value FROM pg_enum WHERE enumtypid = 'card_value'::regtype AND enumsortorder = ((($1-1)%13)+1)::real), 
      (SELECT enumlabel::card_suit FROM pg_enum WHERE enumtypid = 'card_suit'::regtype AND enumsortorder = (((($1-1)/13)::int%4)+1)::real), 
      $1 > (13*4))::card 
$$ LANGUAGE SQL; -- SELECT i, int_to_card(i) FROM generate_series(1, 13*4*2) i 

CREATE OR REPLACE FUNCTION deal_cards(int, int, int, int[]) RETURNS TABLE (player_no int, hand_no int, card card) AS $$ 
    WITH 
    hand AS (
     INSERT INTO hands (run_id, match_no, player_no, hand_no) 
     VALUES ($1, $2, $3, next_hand_no($1)) 
     RETURNING hand_id, player_no, hand_no), 
    mydeals AS (
     INSERT INTO deals (hand_id, card) 
     SELECT hand_id, int_to_card(card_id)::card AS card 
     FROM hand, UNNEST($4) card_id 
     RETURNING hand_id, deal_id, card 
    ) 
    SELECT h.player_no, h.hand_no, d.card 
    FROM hand h, mydeals d 
$$ LANGUAGE SQL; 

CREATE OR REPLACE FUNCTION deals(int) RETURNS TABLE (deal_id bigint, hand_no int, player_no int, card int) AS $$ 
    SELECT d.deal_id, h.hand_no, h.player_no, card_to_int(d.card) 
    FROM hands h 
    JOIN deals d ON (d.hand_id = h.hand_id) 
    WHERE h.run_id = $1 
    ORDER BY d.deal_id; 
$$ LANGUAGE SQL; 

INSERT INTO runs DEFAULT VALUES; -- Add first run 
INSERT INTO players VALUES (1, 0, 'Dealer'); -- dealer always zero 
INSERT INTO players VALUES (1, next_player_no(1), 'Player 1'); 

INSERT INTO matches VALUES (1, next_match_no(1)); -- First match 
SELECT * FROM deal_cards(1, 1, 0, ARRAY[6, 63, 41]); 
SELECT * FROM deal_cards(1, 1, 1, ARRAY[76, 23]); 
SELECT * FROM deal_cards(1, 1, 0, ARRAY[51, 29, 2, 92, 6]); 
SELECT * FROM deal_cards(1, 1, 1, ARRAY[101, 8]); 

INSERT INTO matches VALUES (1, next_match_no(1)); -- Second match 
SELECT * FROM deal_cards(1, 2, 0, ARRAY[78, 38]); 
SELECT * FROM deal_cards(1, 2, 1, ARRAY[24, 18, 95, 40]); 
SELECT * FROM deal_cards(1, 2, 0, ARRAY[13, 84, 41]); 
SELECT * FROM deal_cards(1, 2, 1, ARRAY[29, 34, 56, 52]); 

SELECT * FROM deals(1); -- This is the output you need (hands_combined table) 

-- This view can be used to retrieve the list of all winning hands 
CREATE OR REPLACE VIEW winning_hands AS 
    SELECT DISTINCT ON (run_id, match_no) * 
    FROM hands 
    ORDER BY run_id, match_no, hand_no DESC; 

SELECT * FROM winning_hands; 
+1

Penso che un insieme di esempi di funzionalità pronti all'uso sia proprio ciò di cui l'op ha bisogno. È difficile per una risposta a questa domanda essere "giusta", ma questo è sicuramente un buon riferimento per qualcuno con un problema simile. – Jeffrey

3

Non utilizzare l'operatore UNION funziona?

Per la relazione mani:

SELECT * FROM hands_first 
UNION ALL 
SELECT * FROM hands_second 

Per la relazione partite:

SELECT * FROM matches_first 
UNION ALL 
SELECT * FROM matches_second 

Come soluzione più a lungo termine mi piacerebbe prendere in considerazione la ristrutturazione del DB, perché diventerà presto ingestibile con questo schema . Perché non migliorare la normalizzazione introducendo un tavolo da gioco?

In altre parole Giochi hanno molti Partite, partite avere molti giocatori per ogni gioco e giocatori hanno molte mani per ogni partita .

Si consiglia di disegnare UML per le relazioni di entità su carta (http://dawgsquad.googlecode.com/hg/docs/database_images/Database_Model_Diagram(Title).png), quindi migliorare lo schema in modo che possa essere interrogato utilizzando i normali operatori SQL.

Spero che questo aiuti.

EDIT:

In questo caso è possibile utilizzare un subquery sull'unione di entrambe le tabelle con la funzione rownumber() PG per rappresentare il numero di riga:

SELECT 
    row_number() AS id, 
    whichplayer, 
    whichhand, 
    thiscard 
FROM 
(
    SELECT * FROM hands_first 
    UNION ALL 
    SELECT * FROM hands_second 
); 

Lo stesso principio si applicherebbe la tabella delle partite. Ovviamente questo non si adatta bene anche a un numero limitato di tabelle, quindi dare la priorità alla normalizzazione dello schema.

Docs su alcune funzioni PG: http://www.postgresql.org/docs/current/interactive/functions-window.html

+0

il problema è che i valori di whichHand si sovrappongono. Ho bisogno della colonna whichHand della seconda tabella per continuare la sequenza iniziata dalla prima tabella. – Prime

+0

(e hai un buon punto per ristrutturare il database. Verrà esaminato) – Prime

+0

Vedi la mia modifica. Non l'ho provato personalmente, ma dovrebbe funzionare. –

1

di costruire nuova tabella con tutte le righe di due tabelle, fanno:

CREATE TABLE hands AS 
    select 1 as hand, id, whichplayer, whichhand, thiscard 
    from hands_first 
    union all 
    select 2 as hand, id, whichplayer, whichhand, thiscard 
    from hands_second 

dopo che, per inserire i dati della nuova matche, creare sequenza con inizio il ultimi corrente + 1

CREATE SEQUENCE matche START 3; 

prima inserto leggere il valore di sequenza, e utilizzarlo in inserti:

SELECT nextval('matche'); 
1

tua struttura del database non è grande, e so per certo che non è approccio scalabile la creazione di tabelle su fly. Esistono degli svantaggi in termini di prestazioni che creano tabelle fisiche anziché utilizzare una struttura esistente. Ti suggerisco di refactoring la tua struttura db se possibile.

È tuttavia possibile utilizzare l'operatore UNION per unire i dati.