我正在尝试合并表格,其中行与“真实”事物对应于many:1关系。

我正在写一个二十一点模拟器,将游戏历史记录存储在数据库中,每次运行都会生成一组新的表。这些表实际上更像是模板,因为每个游戏都有自己的3个可变表(玩家,手牌和比赛)集。这是布局,其中suff是用户指定的后缀,用于当前运行:

 - 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

仅创建一张卡片表,因为其值是恒定的。

因此,在两次运行模拟器之后,您可能会:
hands_firstrun
players_firstrun
matches_firstrun
hands_secondrun
players_secondrun
matches_secondrun

如果您对这两次运行都使用相同的AI参数,我希望能够合并这些表(即players_firstrun和players_secondrun完全相同)。问题是我插入手的方式确实很麻烦:whichHand不能是BIGSERIAL,因为Hands_suff行与“实际手”的关系很多:1.。 Matchs_suff的处理方式相同,因为二十一点“游戏”实际上由一组游戏组成:每个玩家对与庄家的对。因此,对于3个玩家,实际上每轮有3行。

当前,我在表中选择了最大的whichHand,向其添加1,然后插入所有行以一只手。我担心如果我合并两个可能都非常大的表,那么这种“查询并插入”操作会非常慢。

当我合并表时,我觉得我应该能够(完全在SQL中)查询whichHand和whichGame中的最大值,然后使用它们组合表,为合并表中的每个唯一的whichHand和whichGame递增它们。

(我看到了this question,但是它不能在2个不同的地方使用生成的ID进行处理)。我使用的是Postgres,如果答案是特定的,那就可以了。

*遗憾的是,postgres不允许使用参数化的表名,因此必须通过手动字符串替换来完成。因为该程序不是面向网络的,所以不是世界末日,除了我之外,没有人可能会打扰它,但是SQL注入(inject)漏洞并不能使我感到高兴。

** matchs_suff(whichPlayersHand)最初将引用hands_suff(whichHand),但引用foreign keys must reference unique values。 whichHand不是唯一的,因为一只手由多行组成,每行“持有”一张卡。要查询一手牌,请在whichHand中选择所有具有相同值的行。我想不出一种更优雅的方法来做到这一点,而不必诉诸于数组。

编辑:

这就是我现在所拥有的:
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)

我想将它们合并为:
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

“thiscard”的每个值代表[1..104]-52范围内的扑克牌,并带有一个额外的位,表示它是面朝上还是面朝下。由于空间原因,我没有发布实际的表格。
因此,玩家0(又名发牌人)在第一局中有(黑桃7,太空皇后,俱乐部3)的一手牌。

最佳答案

我认为您没有按照预期的方式使用PostgreSQL,而且您的表设计可能不适合您想要实现的目标。虽然很难理解您想要的解决方案要实现的目标,但我写了这篇文章,似乎仅使用少数几个表就可以解决您想要的所有问题,并且这些函数返回记录集以模拟您对单个运行的需求。我使用枚举和复杂类型来说明您可能希望从PostgreSQL的功能中利用的一些功能。

另外,我不确定参数化的表名是什么(在任何RDBMS中我从未见过类似的表名),但是PostgreSQL确实允许某些完全合适的东西:记录集返回函数。

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;

09-25 16:11