I have 2 tables :
table1 t_SearchCriteria:
------------------------------
ID | VALUE | IDParent |
-----|------------|------------|
0 | root | -1 |
-----|------------|------------|
1 | JAMES | 0 |
-----|------------|------------|
2 | ISAC | 0 |
-----|------------|------------|
3 | LISA | 1 |
-----|------------|------------|
4 | Andrew | 3 |
-----|------------|------------|
5 | LISA | 2 |
-----|------------|------------|
6 | EZREAL | 5 |
-----|------------|------------|
10 | twitch | 2 |
-----|------------|------------|
13 | LUX | 0 |
-----|------------|------------|
14 | LISA | 13 |
-----|------------|------------|
15 | EZREAL | 14 |
-----|------------|------------|
EDIT: here is a representation of the tree:
_______root_______
/ | \
JAMES ISAC LUX
| / \ |
LISA TWITCH LISA LISA
| | |
Andrew EZREAL EZREAL
and my second table is like the following :
table t_Path
idPath|Person1| Son |grandSon|grandgrandSon|
------|-------|-------|--------|-------------|
1 |root |JAMES | LISA |ANDREW |
------|-------|-------|--------|-------------|
2 |root |ISAC | LISA |EZREAL |
------|-------|-------|--------|-------------|
3 |root |ISAC | NULL |TWITCH |
------|-------|-------|--------|-------------|
4 |root |LUX | NULL | NULL |
------|-------|-------|--------|-------------|
5 |root |LUX | LISA | NULL |
------|-------|-------|--------|-------------|
6 |root |LUX | LISA | EZREAL |
------|-------|-------|--------|-------------|
My need is to figure out a way (function or procedure) that starts from table 2 (t_Path) and find each leaf (value of grandgrandSon if not null otherwise grandson if not null etc...) id in t_searchCriteria table:
Since we can have the same value of node in the t_search criteria table then the unicity of a node is its value and its parent value and its grandParentValue (and we have another rule; a parent can't have 2 childs with same name)
I have tried to make a function but i didn't find a way to do a function inside another function besides working with objects like in c# or another programming laguage.
I need to make a function that takes an int ID wich is the ID of a path from table t_Path and figure out the leaf of the path (this is done), the problem here is how to get the id of that leaf from t_searchCriteria table since we can have multiple criterias with same value (name) even with same parent name, the grandParent Value will make difference.
for example of execution:
Select FunctionGetCriteriaId(6)
will return 15
where 6 is the id of the path : 6 |root |LUX | LISA | EZREAL |
and 15 is the id of the criteria : 15 | EZREAL | 14 |
Can anyone help me to figure this out please?
EDIT: to be more specific the fucntion takes the id of the path in table 2, for example 5 ( 5 |root |LUX | LISA | NULL |) and returns the id of "LISA" (the leaf not the others ;) ) in table 1 that is 14. (ofcourse taking note of the rules set before.)
EDIT 2:
updated unicity condition in the tree
You can do this easily using LEFT JOINS and MAX and COALESCE. This is full working example. You can play with it:
DECLARE #t_SearchCriteria TABLE
(
[ID] SMALLINT
,[Value] VARCHAR(12)
,[IDParent] SMALLINT
);
INSERT INTO #t_SearchCriteria ([ID], [Value], [IDParent])
VALUES (0, 'root', -1)
,(1, 'JAMES', 0)
,(2, 'ISAC', 0)
,(3, 'LISA', 1)
,(4, 'Andrew', 3)
,(5, 'LISA', 2)
,(6, 'EZREAL', 5)
,(10, 'twitch', 2)
,(13, 'LUX', 0)
,(14, 'LISA', 13)
,(15, 'EZREAL', 14);
DECLARE #t_Path TABLE
(
[idPath] SMALLINT
,[Person1] VARCHAR(12)
,[Son] VARCHAR(12)
,[grandSon] VARCHAR(12)
,[grandgrandSon] VARCHAR(12)
);
INSERT INTO #t_Path ([idPath], [Person1], [Son], [grandSon], [grandgrandSon])
VALUES (1, 'root', 'JAMES', 'LISA', 'ANDREW')
,(2, 'root', 'ISAC', 'LISA', 'EZREAL')
,(3, 'root', 'ISAC', 'TWITCH', NULL)
,(4, 'root', 'LUX', NULL, NULL)
,(5, 'root', 'LUX', 'LISA', NULL)
,(6, 'root', 'LUX', 'LISA', 'EZREAL');
-- the function input parameter
DECLARE #idPath SMALLINT = 5;
-- the function body
DECLARE #Person1 VARCHAR(12)
,#Son VARCHAR(12)
,#grandSon VARCHAR(12)
,#grandgrandSon VARCHAR(12);
SELECT #Person1 = [Person1]
,#Son = [Son]
,#grandSon = [grandSon]
,#grandgrandSon = [grandgrandSon]
FROM #t_Path P
WHERE P.[idPath] = #idPath;
SELECT COALESCE(MAX(S5.[ID]), MAX(S4.[ID]), MAX(S3.[ID]), MAX(S2.[ID]), MAX(S1.[ID]))
FROM #t_SearchCriteria S1
LEFT JOIN #t_SearchCriteria S2
ON S1.[ID] = S2.[IDParent]
AND S1.[Value] = #Person1
LEFT JOIN #t_SearchCriteria S3
ON S2.[ID] = S3.[IDParent]
AND S2.[Value] = #Son
AND #Person1 IS NOT NULL
LEFT JOIN #t_SearchCriteria S4
ON S3.[ID] = S4.[IDParent]
AND S3.[Value] = #grandSon
AND #grandgrandSon IS NOT NULL
LEFT JOIN #t_SearchCriteria S5
ON S4.[ID] = S5.[IDParent]
AND S4.[Value] = #grandgrandSon
WHERE S1.[Value] = #Person1;
Related
I built this sample table in Fiddle: https://www.db-fiddle.com/f/pu1PkjzKCM9VRciznkJc4L/7
I am working on relabeling territories for account numbers and serial numbers. You can see the table schema and the query I have tried to change the data and add a new column that would have the correct territories. The problem is my result that you'll see at the end. I want to find a way to first go through serial_number, I have determined serial_numbers are the best way to track territory, then acct_num when serial_numbers are null to add proper territory names.
However, while my program largely works for this purpose. When there is an acct_num AND no serial_number it won't just throw in the last "proper" territory it found. I need the program to do that. I was thinking maybe a nested case statement? I have tried doing this in the preexisting CASE and PARTITION BY statements but I have been unsuccessful it keeps throwing a syntax error.
I was thinking... (PARTITION BY WHEN serial_number='NULL' THEN acct_num ELSE serial_number), but this always throws an error.
Any help is appreciated!
SQL SCHEMA
'''
CREATE TABLE t (
"row" INTEGER,
"acct_num" TEXT,
"serial_number" TEXT,
"site_address" TEXT,
"city" TEXT,
"territory_name" TEXT
);
INSERT INTO t
("row", "acct_num", "serial_number", "site_address", "city", "territory_name")
VALUES
('1', '049403', 'TH-530', '344 Clinton Str', 'Metropolis', 'Central'),
('2', '049403', 'TH-530', '344 Clinton Str', 'Metropolis', 'Central'),
('3', '049206', 'GO-650', '1007 Mountain Dr', 'Gotham City', 'Gotham City'),
('4', '049206', 'GO-650', '1007 Mountain Dr', 'Gotham City', 'Gotham City'),
('5', '049206', 'GO-650', 'NULL', 'NULL', 'N/A'),
('6', '049206', 'TJO-560', 'NULL', 'NULL', 'NULL'),
('7', '049291', 'SM-708', '1938 Sullivan Pl', 'Smallville', 'No Territory'),
('8', '049293', 'TH-533', '700 Hamilton Str', 'Central City', 'Central'),
('9', '049396', 'AL-670', '800 Ellsmore Way', 'Alberty Township', 'South'),
('10', '049396', 'KILO-680', '600 Nukem Drive', 'Duke City', 'Southeast'),
('11', '049396', 'JALO-605', '5806 London Way', 'Hampshire', 'Northeast'),
('12', '049396', 'NULL', 'NULL', 'NULL', 'No Territory');
'''
SQL QUERY
'''
select row,
acct_num,
serial_number,
site_address,
MAX(
CASE
WHEN territory_name <> 'N/A' AND territory_name <> 'No Territory'
THEN territory_name
WHEN serial_number = 'NULL'
THEN territory_name
END)
over (PARTITION BY serial_number) as imputed_place
from t
order by row
'''
SQL Query Result
| row | acct_num | serial_number | site_address | imputed_place
| ----|----------|----------------|-----------------|---------------
| 1 | 049403 | TH-530 | 344 Clinton Str | Central
| 2 | 049403 | TH-530 | 344 Clinton Str | Central
| 3 | 049206 | GO-650 | 1007 Mountain Dr| Gotham City
| 4 | 049206 | GO-650 | 1007 Mountain Dr| Gotham City
| 5 | 049206 | GO-650 | NULL | Gotham City
| 6 | 049206 | TJO-560 | NULL | NULL
| 7 | 049291 | SM-708 | 1938 Sullivan Pl| null
| 8 | 049293 | TH-533 | 700 Hamilton Str| Central
| 9 | 049396 | AL-670 | 800 Ellsmore Way| South
| 10 | 049396 | KILO-680 | 600 Nukem Drive | Southeast
| 11 | 049396 | JALO-605 | 5806 London Way | Northeast
| 12 | 049396 | NULL | NULL | No Territory <- should be Northeast!!!
'''
DB Fiddle – Crafted with ♥ by Status200 in the United Kingdom.
Terms of Use • Privacy / Cookie Policy • Status200 Ltd © 2018
I tried to recreate your result (row 12 included), is the column row something that is part of the solution / data?
The approach I took is to create a mapping table of valid values by serial number, therefore key+value must be unique or we explode the results from 12 rows to... a lot more! That works fine for Serial number, when I look at account number that is not the case. There is no unique mapping, what is the rule that determines that line 12 should be 'Northeast'?
If it is based on row then the outcome is easy and I'll provide the solution below,
with map_by_serial_no as (
select distinct "serial_number"
, "territory_name"
from t
where "serial_number" <> 'NULL'
and "territory_name" <> 'N/A'
and "territory_name" <> 'NULL'
)
select t."row"
, t."acct_num"
, t."serial_number"
, t."site_address"
, t."city"
, t."territory_name"
, case when t."serial_number" = 'NULL' then lag(t."territory_name") over (order by "row")
when t."territory_name" in ('NULL', 'No Territory', 'N/A') then u."territory_name"
else t."territory_name" end as imputed_territory
from t
left join map_by_serial_no u
on t."serial_number" = u."serial_number"
order by "row";
Note however that NULL should be the absence of a value, whereas in this sample it is a quoted string.
Hi i am trying to do a stored procedure in postgresql,
and I have to fill a table (vol_raleos), from 3 others, these are the tables:
super
zona | sitio | manejo
1 | 1 | 1
2 | 2 | 2
datos_vol_raleos
zona | sitio | manejo |vol_prodn
1 | 1 | 10 | 0
2 | 2 | 15 | 0
datos_manejos
manejoVR | manejoSuper
10 | 1
15 | 2
table to fill
vol_raleos
zona | sitio | manejo |vol_prodn
1 | 1 | 1 | 0
2 | 2 | 2 | 0
So, what I do is take the data that is in datos_vol_raleos, verify that it is in super, but first I must convert the manejoVR value according to the table datos_manejos
INSERT INTO vol_raleos
(zona, sitio, manejo, edad, densidad, vol_prod1, vol_prod2, ..., vol_prod36)
select zona, sitio, manejo, edad, densidad, vol_prod1, vol_prod2, ..., vol_prod36
from (
select volr.*, sup.zona, sup.sitio, sup.manejo, dm.manejo,
from datos_vol_raleos volr
left join super sup on (sup.zona = volr.zona and sup.sitio = volr.sitio and sup.manejo = volr.manejo) selrs
order by zona, sitio, manejo, edad, densidad
) sel_min_max;
so here I don't know how to get the manejoSuper value from datos_manejos, to later compare
You can insert from a select with a couple of joins. For example:
insert into vol_raleos
select s.zona, s.sitio, s.manejo, m.manejoSuper
from super s
join datos_vol_raleos d on (d.zona, d.sitio) = (s.zona, s.sitio)
join datos_manejos m on m.manejoVR = d.manejo
I'm trying to write a query where I want to return the user that has the highest number of contributions to a certain table. To keep the case simple let's imagine many users can write many paragraphs that are eventually shown as a complete story. Below is the table structure (I'm given this structure, not my design but I have structured the column names at least ;))
paragraphs
-----
id (PK)
story_id (imaginary FK that I need to group the results by)
user_id (FK to userprop.id)
body
date
userprop
----
id (PK)
supervisor_id (FK to supervisors.id)
username (FK to users.name)
users
----
name
full_name
supervisors
----
id
full_name
I'm trying to write a query where I get each paragraph but with two extra columns showing the supervisors name that is used most times and the actual count of the supervisors' supervisions.
I've been fiddling with inner join subqueries, trying to subquery my way in the main select to the result and trying to group by a subquery. Some of the approaches are not even valid SQL and others didn't work out yet since I'm unsure how to group by supervisors.id through userprop.supervisor_id which is linked to paragraphs.user_id by userprop.id.
Can this be done in a single query, or do I have to incorporate some PHP and use multiple queries and some loops?
I have read only access to the database.
Sample data:
paragraphs
id story_id user_id body
----------------------------
1 1 1 Sample data
2 1 1 Sample data
3 2 1 Sample data
4 1 2 Sample data
5 1 3 Sample data
6 5 1 Sample data
userprop
id supervisor_id username
----------------------------
1 1 user_abc
2 1 user_def
3 2 user_ghi
users
name full_name
---------------------
user_abc Jack Jackson
user_def Bill Winters
user_ghi Sharon Staples
supervisors
id full_name
1 Steve Doppler
2 Frank Frampton
expected output
id story_id user_id body main_supervisor_count main_supervisor
---------------------------------------------------------------------------
1 1 1 Sample data 3 Steve Doppler
2 1 1 Sample data 3 Steve Doppler
3 2 1 Sample data 1 Steve Doppler
4 1 2 Sample data 3 Steve Doppler
5 1 3 Sample data 1 Frank Frampton
6 5 1 Sample data 1 Steve Doppler
It looks like a simple partitioned COUNT is all you need.
Sample data
DECLARE #paragraphs TABLE (id int, story_id int, user_id int, body nvarchar(max));
INSERT INTO #paragraphs(id, story_id, user_id, body) VALUES
(1, 1, 1, 'Sample data'),
(2, 1, 1, 'Sample data'),
(3, 2, 1, 'Sample data'),
(4, 1, 2, 'Sample data'),
(5, 1, 3, 'Sample data'),
(6, 5, 1, 'Sample data');
DECLARE #userprop TABLE (id int, supervisor_id int, username nvarchar(50));
INSERT INTO #userprop (id, supervisor_id, username) VALUES
(1, 1, 'user_abc'),
(2, 1, 'user_def'),
(3, 2, 'user_ghi');
DECLARE #supervisors TABLE (id int, full_name nvarchar(50));
INSERT INTO #supervisors (id, full_name) VALUES
(1, 'Steve Doppler'),
(2, 'Frank Frampton');
Query
SELECT
paragraphs.id
,paragraphs.story_id
,paragraphs.user_id
,paragraphs.body
,COUNT(*) OVER (PARTITION BY paragraphs.story_id, supervisors.id) AS main_supervisor_count
,supervisors.full_name AS main_supervisor
FROM
#paragraphs AS paragraphs
INNER JOIN #userprop AS userprop ON userprop.id = paragraphs.user_id
INNER JOIN #supervisors AS supervisors ON supervisors.id = userprop.supervisor_id
ORDER BY
paragraphs.id;
Result
+----+----------+---------+-------------+-----------------------+-----------------+
| id | story_id | user_id | body | main_supervisor_count | main_supervisor |
+----+----------+---------+-------------+-----------------------+-----------------+
| 1 | 1 | 1 | Sample data | 3 | Steve Doppler |
| 2 | 1 | 1 | Sample data | 3 | Steve Doppler |
| 3 | 2 | 1 | Sample data | 1 | Steve Doppler |
| 4 | 1 | 2 | Sample data | 3 | Steve Doppler |
| 5 | 1 | 3 | Sample data | 1 | Frank Frampton |
| 6 | 5 | 1 | Sample data | 1 | Steve Doppler |
+----+----------+---------+-------------+-----------------------+-----------------+
I'm trying to merge tables where rows correspond to a many:1 relationship with "real" things.
I'm writing a blackjack simulator that stores game history in a database with a new set of tables generated each run. The tables are really more like templates, since each game gets its own set of the 3 mutable tables (players, hands, and matches). Here's the layout, where suff is a user-specified suffix to use for the current run:
- 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
Only one cards table is created because its values are constant.
So after running the simulator twice you might have:
hands_firstrun
players_firstrun
matches_firstrun
hands_secondrun
players_secondrun
matches_secondrun
I want to be able to combine these tables if you used the same AI parameters for both of those runs (i.e. players_firstrun and players_secondrun are exactly the same). The problem is that the way I'm inserting hands makes this really messy: whichHand can't be a BIGSERIAL because the relationship of hands_suff rows to "actual hands" is many:1. matches_suff is handled the same way because a blackjack "game" actually consists of a set of games: the set of pairs of each player vs. the dealer. So for 3 players, you actually have 3 rows for each round.
Currently I select the largest whichHand in the table, add 1 to it, then insert all of the rows for one hand. I'm worried this "query-and-insert" will be really slow if I'm merging 2 tables that might both be arbitrarily huge.
When I'm merging tables, I feel like I should be able to (entirely in SQL) query the largest values in whichHand and whichGame once then use them combine the tables, incrementing them for each unique whichHand and whichGame in the table being merged.
(I saw this question, but it doesn't handle using a generated ID in 2 different places). I'm using Postgres and it's OK if the answer is specific to it.
* sadly postgres doesn't allow parameterized table names so this had to be done by manual string substitution. Not the end of the world since the program isn't web-facing and no one except me is likely to ever bother with it, but the SQL injection vulnerability does not make me happy.
** matches_suff(whichPlayersHand) was originally going to reference hands_suff(whichHand) but foreign keys must reference unique values. whichHand isn't unique because a hand is made up of multiple rows, with each row "holding" one card. To query for a hand you select all of those rows with the same value in whichHand. I couldn't think of a more elegant way to do this without resorting to arrays.
EDIT:
This is what I have now:
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)
I'd like to combine them to have:
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
Each value of "thiscard" represents a playing card in the range [1..104]--52 playing cards with an extra bit representing if it's face up or face down. I didn't post the actual table for space reasons.
So player 0 (aka the dealer) had a hand of (Seven of Spades, Queen of Spaces, 3 of Clubs) in the first game.
I think you're not using PostgreSQL the way it's intended to be used, plus your table design may not be suitable for what you want to achieve. Whilst it was difficult to understand what you want your solution to achieve, I wrote this, which seems to solve everything you want using a handful of tables only, and functions that return recordsets for simulating your requirement for individual runs. I used Enums and complex types to illustrate some of the features that you may wish to harness from the power of PostgreSQL.
Also, I'm not sure what parameterized table names are (I have never seen anything like it in any RDBMS), but PostgreSQL does allow something perfectly suitable: recordset returning functions.
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;
Wouldn't using the UNION operator work?
For the hands relation:
SELECT * FROM hands_first
UNION ALL
SELECT * FROM hands_second
For the matches relation:
SELECT * FROM matches_first
UNION ALL
SELECT * FROM matches_second
As a more long term solution I'd consider restructuring the DB because it will quickly become unmanageable with this schema. Why not improve normalization by introducing a games table?
In other words Games have many Matches, matches have many players for each game and players have many hands for each match.
I'd recommend drawing the UML for the entity relationships on paper (http://dawgsquad.googlecode.com/hg/docs/database_images/Database_Model_Diagram(Title).png), then improving the schema so it can be queried using normal SQL operators.
Hope this helps.
EDIT:
In that case you can use a subquery on the union of both tables with the rownumber() PG function to represent the row number:
SELECT
row_number() AS id,
whichplayer,
whichhand,
thiscard
FROM
(
SELECT * FROM hands_first
UNION ALL
SELECT * FROM hands_second
);
The same principle would apply to the matches table. Obviously this doesn't scale well to even a small number of tables, so would prioritize normalizing your schema.
Docs on some PG functions: http://www.postgresql.org/docs/current/interactive/functions-window.html
to build new table with all rows of two tables, do:
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
after that, to insert data of new matche, create sequence with start on current last + 1
CREATE SEQUENCE matche START 3;
before insert read sequence value, and use it in inserts:
SELECT nextval('matche');
Your database structure is not great, and I know for sure it is not scalable approach creating tables on fly. There are performance drawbacks creating physical tables instead of using an existing structure. I suggest you refactor your db structure if can.
You can however use the UNION operator to merge your data.
I've got a database that manages files - some files contain/reference other files, and my goal is to design a query that can give me the whole "tree" for a given document.
For example the structure may look like this:
File 1
File 2
File 3
File 4
File 5
File 6
File 7
File 8
File 9
File 10
etc., where File 1 effectively contains all of the files following it
These are broken out in my database between two tables - lets call them the "Files" table and the "References" table
The "Files" table has information about the files themselves - FileID, Filename, etc.
The "References" table shows the relationship of the above structure using the FileIDs of the files. My issue is that, for example, File 6 is not referenced by File 1 - it is only referenced by File 5.
e.g.:
[ParentFileID] [ChildFileID]
1 2
1 3
1 4
1 5
5 6
5 7
5 8
8 9
8 10
Ideally I'd like to be able to check the position in the entire structure for any given FileID I pass in
Any ideas? I've been reading up on CTEs and it feels like some sort of recursive common table expression is what I'm after, though every example I can find is using one table and involves NULLs to track down the top level elements.
Yes, it can be done using a recursive CTE.
USE tempdb
GO
CREATE TABLE files
(
[file_id] int PRIMARY KEY,
[file_name] varchar(128) NOT NULL
);
INSERT INTO files VALUES
(1, 'File 1'),
(2, 'File 2'),
(3, 'File 3'),
(4, 'File 4'),
(5, 'File 5'),
(6, 'File 6'),
(7, 'File 7'),
(8, 'File 8'),
(9, 'File 9'),
(10, 'File 10');
CREATE TABLE [references]
(
parent_file_id int NOT NULL,
child_file_id int NOT NULL,
PRIMARY KEY (child_file_id)
);
INSERT INTO [references] VALUES
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(5, 6),
(5, 7),
(5, 8),
(8, 9),
(8, 10);
GO
CREATE FUNCTION dbo.get_file_with_path(#file_id int)
RETURNS TABLE
AS
RETURN WITH h
AS
(
SELECT
f.file_id, f.file_id as child_file_id,
f.file_name, 0 as reverse_level,
CAST( '/' + f.file_name as varchar(8000)) as path
FROM
dbo.files f
WHERE
f.file_id = #file_id
UNION ALL
SELECT
h.file_id, r.parent_file_id as child_file_id,
h.file_name, h.reverse_level + 1 as reverse_level,
CAST('/' + f.file_name + h.path as varchar(8000)) as path
FROM
h
INNER JOIN [references] r
ON h.child_file_id = r.child_file_id
INNER JOIN dbo.files f
ON f.file_id = r.parent_file_id
)
SELECT TOP(1) h.file_id, h.file_name, h.path
FROM h
ORDER BY h.reverse_level DESC;
GO
SELECT *
FROM dbo.get_file_with_path(1)
UNION ALL
SELECT *
FROM dbo.get_file_with_path(3)
UNION ALL
SELECT *
FROM dbo.get_file_with_path(6)
UNION ALL
SELECT *
FROM dbo.get_file_with_path(10)
Output:
| file_id | file_name | path |
|---------|-----------|-------------------------------|
| 1 | File 1 | /File 1 |
| 3 | File 3 | /File 1/File 3 |
| 6 | File 6 | /File 1/File 5/File 6 |
| 10 | File 10 | /File 1/File 5/File 8/File 10 |
I assume you mean path when you say position
EDIT:
Answering the question in comments, you can also create a table valued function that returns the sub-tree below a given node:
CREATE FUNCTION dbo.get_file_subtree_excluding_self(#file_id int)
RETURNS TABLE
AS RETURN
WITH h AS
(
SELECT r.parent_file_id, r.child_file_id
FROM [references] r
WHERE r.parent_file_id = #file_id
UNION ALL
SELECT r.parent_file_id, r.child_file_id
FROM
h INNER JOIN [references] r
ON h.child_file_id = r.parent_file_id
)
SELECT h.child_file_id as [file_id]
FROM h
GO
SELECT * FROM dbo.get_file_subtree_excluding_self(5)
Output:
+---------+
| file_id |
+---------+
| 6 |
| 7 |
| 8 |
| 9 |
| 10 |
+---------+
The table references describes a graph. One node can have only one parent because of the primary key, but nothing prevents cycles. For example, consider the following data:
+-------+--------+
| child | parent |
+-------+--------+
| 1 | 2 |
| 2 | 3 |
| 3 | 1 |
+-------+--------+
As you can see, there are cycles on this graph.