Is it possible to CREATE TABLE with a column that is a combination of other columns in the same table? - sql

I know that the question is very long and I understand if someone doesn't have the time to read it all, but I really wish there is a way to do this.
I am writing a program that will read the database schema from the database catalog tables and automatically build a basic application with the information extracted from the system catalogs.
Many tables in the database can be just a list of items of the form
CREATE TABLE tablename (id INTEGER PRIMARY KEY, description VARCHAR NOT NULL);
so when a table has a column that references the id of tablename I just resolve the descriptions by querying it from the tablename table, and I display a list in a combo box with the available options.
There are some tables however that cannot directly have a description column, because their description would be a combination of other columns, lets take as an example the most important of those tables in my first application
CREATE TABLE bankaccount (
bankid INTEGER NOT NULL REFERENCES bank,
officeid INTEGER NOT NULL REFERENCES bankoffice,
crc INTEGER NOT NULL,
number BIGINT NOT NULL
);
this as many would know, would be the full account number for a bank account, in my country it's composed as follows
[XXXX][XXXX][XX][XXXXXXXXXX]
^ ^ ^ ^
bank id | crc account number
|
|_ bank office id
so that's the reason of the way my bankaccount table is structured as is.
Now, I would like to have the complete bank account number in a description column so I can display it in the application without giving a special treatment to this situation, since there are some other tables with similar situation, something like
CREATE TABLE bankaccount (
bankid INTEGER NOT NULL REFERENCES bank,
officeid INTEGER NOT NULL REFERENCES bankoffice,
crc INTEGER NOT NULL,
number BIGINT NOT NULL,
description VARCHAR DEFAULT bankid || '-' || officeid || '-' || crc || '-' || number
);
Which of course doesn't work since the following error is raised1
ERROR: cannot use column references in default expression
If there is any different approach that someone can suggest, please feel free to suggest it as an answer.
1 This is the error message given by PostgreSQL.

What you want is to create a view on your table. I'm more familiar with MySQL and SQLite, so excuse the differences. But basically, if you have table 'AccountInfo' you can have a view 'AccountInfoView' which is sort of like a 'stored query' but can be used like a table. You would create it with something like
CREATE VIEW AccountInfoView AS
SELECT *, CONCATENATE(bankid,officeid,crc,number) AS FullAccountNumber
FROM AccountInfo
Another approach is to have an actual FullAccountNumber column in your original table, and create a trigger that sets it any time an insert or update is performed on your table. This is usually less efficient though, as it duplicates storage and takes the performance hit when data are written instead of retrieved. Sometimes that approach can make sense, though.

What actually works, and I believe it's a very elegant solution is to use a function like this one
CREATE FUNCTION description(bankaccount) RETURNS VARCHAR AS $$
SELECT
CONCAT(bankid, '-', officeid, '-', crc, '-', number)
FROM
bankaccount this
WHERE
$1.bankid = this.bankid AND
$1.officeid = this.officeid AND
$1.crc = this.crc AND
$1.number = this.number
$$ LANGUAGE SQL STABLE;
which would then be used like this
SELECT bankaccount.description FROM bankaccount;
and hence, my goal is achieved.
Note: this solution works with PostgreSQL only AFAIK.

Related

Creating an Identifier that Combines Multiple Other Columns

I'm working on a DB and would like to implement a system where a tables unique ID is generated by combining several other IDs/factors. Basically, I'd want an ID that looks like this:
1234 (A reference to a standard incrementing serial ID from another table)
10 (A reference to a standard incrementing serial ID from another table)
1234 (A number that increments from 1000-9999)
So the ID would look like:
1234101234
Additionally, each of those "entries" will have multiple time sensitive instances that are stored in another table. For these IDs I want to take the above ID and append a time stamp, so it'll look like:
12341012341234567890123
I've looked a little bit at PSQL sequences, but they seem like they're mostly used for simply incrementing up or down at certain levels, I'm not sure how to do this sort of concatenation in creating an ID string or whether it's even possible.
Don't do it! Just use a serial primary key id and then have three different columns:
otherTableID
otherTable2ID
timestamp
You can uniquely identify each row using your serial id. You can look up the other information. And -- even better -- you can create foreign key constraints to represent the relationships among the tables.
I'm not sure what do you want to achive, but
SELECT col_1::text || col_2::text || col_3::text || now()::text
should work. You should also add UNIQUE constraint on the column, i.e.
ALTER TABLE this_table ADD UNIQUE INDEX (this_new_column);
But the real question is: why do you want to do this? If you just want a unique meaningless ID, you need just to create column of type serial.
create procedure f_return_unq_id(
CONDITIONAL_PARAMS IN INTEGER,
v_seq in out integer
)
is
QUERY_1 VARCHAR2(200);
RESP INTEGER;
BEGIN
QUERY_1:='SELECT TAB1.SL_ID||TAB2.SL_ID||:v_seq||SYSTIMESTAMP FROM TABLE1 TAB1,TABLE2 TAB2 WHERE TAB1.CONDITION=:V_PARAMS';
BEGIN
EXECUTE IMMEDIATE QUERY_1 INTO RESP USING v_seq,CONDITIONAL_PARAMS;
EXCEPTION
when others then
DBMS_OUTPUT.PUT_LINE(SQLCODE);
END;
v_seq:=RESP;
EXCEPTION
when others then
DBMS_OUTPUT.PUT_LINE(SQLCODE);
END;
pass the v_seq to this procedure as your sequence number 1000-9999 and conditional parameters if any are there.

Specify the format of a string with letters and numbers in triggers SQL

I am actually writing a trigger with Oracle and I want to specify the format of a variable but I'm not finding the proper syntax to do that.
I have a table 'Person' which gathers students and professors and all of them have a matricule. Professors have a "pmatricule" which means a string with "p" followed by a chain of 3 to 6 numbers. Example : p456123
Students have only a matricule with 3 to 7 numbers such as : 1234567
My triggers has to compare the new matricule I want to input in my table 'Person' to check if it respects the format I described.
Has someone the syntax to do such a thing ? I have searched for a long time but my trigger is not working whatever I try.
You can do this with a table level CHECK constraint. You haven't provided a table description so column names are guesses.
alter table person add constraint person_matricule_ck check
( ( person_type = 'PROFESSOR' and regexp_like(matricule, '^p[0-9]{6}$') )
or ( person_type = 'STUDENT' and regexp_like(matricule, '^[0-9]{7}$') )
)
/
We shouldn't use triggers for rules which can be enforced with constraints: constraints are both idiomatic SQL and more efficient.

I want to show how many of the alumni attended the social event

I want an output to show how many alumni attended the event
I got Event Table
CREATE TABLE EVENT
(EVENTID INTEGER CONSTRAINT EVENT_ID_PK PRIMARY KEY,
EDATEANDTIME VARCHAR2(20),
EVENUE VARCHAR2(30),
EAUDIENCE VARCHAR2(30),
EATTENDED VARCHAR2(30),
EVENT_ALUMNI INTEGER,
CONSTRAINT EVENT_ALUMNI_FK FOREIGN KEY (EVENT_ALUMNI) REFERENCES
ALUMNI(ALUMNIID));
Here is what I inserted in Event
INSERT INTO EVENT VALUES(25993, 'Jan 14, 2015', 'Concorde Hall', 'Tobias, Tucker, Felix, Nicole, Desiree, Taylor, Frant, Ifeoma, Forrest, Stewart, Cole, Arthur, Thomas, Bo, Lucian',
'Tobias, Tucker, Felix, Nicole, Desiree, Taylor, Frant, Ifeoma, Forrest, Stewart, Cole, Arthur, Thomas, Bo',17337);
INSERT INTO EVENT VALUES(23823, 'July 18 2015', 'Rochester Hotel', 'Joan, Thalia, Haleeda', 'Joan, Haleeda'
,19927);
And I have a View Statement to view how many attended
CREATE VIEW VIEWH AS SELECT ETYPE, EDATEANDTIME, COUNT (*) EATTENDED FROM EVENT
WHERE EDATEANDTIME LIKE '%2015%' AND ETYPE = 'Social'
GROUP BY ETYPE, EDATEANDTIME, EATTENDED;
Here is where I got problem. When i run the query, the output is I got only one who attended the event instead of like 10 or 15
I want to know where i went wrong.
There are several potential problems with your setup, I mentioned a couple in a Comment to your question.
Regarding the count specifically: your first problem is you have EATTENDED in the GROUP BY clause. Why? This almost surely means that each group is exactly one row.
Then your next problem is counting tokens out of a comma-separated string. One way is using regexp_count() as TheGameiswar has shown. (err... his/her solution has disappeared? anyway, I was saying...)
Another is to use length() and translate():
select ... , 1 + length(EATTENDED) - length(translate(EATTENDED, ',', '') as ATT_CNT ...
A couple of notes about this:
The length difference, just like regexp_count(), counts commas in the string. You add one to count tokens (you don't care how many commas there are, you care how many names are separated by commas). translate() works faster than regexp_count() (all regexp functions are very powerful, but slower, so they should be used only when they are really needed, IF performance is important); length doesn't add much overhead since in Oracle the length of VARCHAR2 is always calculated and stored as part of the internal representation of a string.
I gave a different alias to the resulting column; in your original query you use the same name, EATTENDED, for a column in your base table and as an alias for this numeric column in your view. You can do it, it's legal, but it is almost certain to cause problems in the future.
It is because COUNT gives number of "rows" in the result and not number of "values in column".
To give you a simple example
example1_table:
col1
--------
a
b
c
SELECT COUNT(col1) FROM example1_table; // The result is 3, as there are three rows
example2_table:
col1
--------
a,b,c
SELECT COUNT(col1) FROM example1_table; // The result is 1, as there is only one row (it is not about number of values in it)
Note:
As stated by #mathguy you could have used a better Database structure.
Instead of using only one table EVENT create two tables let they be: table1 table2 so you could avoid redundancy.
CREATE TABLE table1
(EVENTID INTEGER CONSTRAINT EVENT_ID_PK PRIMARY KEY,
EDATEANDTIME VARCHAR2(20),
EVENUE VARCHAR2(30),
EVENT_ALUMNI INTEGER,
CONSTRAINT tab1_FK FOREIGN KEY (EVENT_ALUMNI) REFERENCES
ALUMNI(ALUMNIID));
CREATE TABLE table2
(
EAUDIENCE VARCHAR2(30),
EATTENDED VARCHAR2(30),
tab2ID INTEGER
CONSTRAINT tab2_FK FOREIGN KEY (tab2ID) REFERENCES
tab1(EVENT_ALUMNI));
If you don't want to make any changes in your table, assuming there is always a comma present between two attendees names you could try:
CREATE VIEW VIEWH AS SELECT ETYPE, EDATEANDTIME, regexp_count(EATTENDED,',')+1 FROM EVENT WHERE EDATEANDTIME LIKE '%2015%' AND ETYPE = 'Social' GROUP BY ETYPE, EDATEANDTIME;

Database design for a template based evaluation system

We are working on a database to store some evaluations we conduct. There are a few different types of evaluations and some have changed over time. Because of this we need to keep a record of exactly what an evaluation looked like when it was undertaken.
I figured that the best way to support this would be through a template style system.
With:
A table saving all possible options;
A table mapping options to a template;
An evaluations table mapping a participant to a template on a date/time; and
A table mapping evaluator comments to an option of an evaluation.
This is a skeleton for the design:
CREATE TABLE options (
id SERIAL PRIMARY KEY,
option TEXT NOT NULL
);
CREATE TABLE templates (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE template_options (
template INTEGER NOT NULL REFERENCES templates( id ),
option INTEGER NOT NULL REFERENCES options( id ),
UNIQUE ( template, option )
);
CREATE TABLE participants (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE evaluations (
id SERIAL PRIMARY KEY,
template INTEGER NOT NULL REFERENCES templates( id ),
participant INTEGER NOT NULL REFERENCES participants( id ),
date TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE TABLE evaluation_data (
template INTEGER NOT NULL REFERENCES templates( id ),
option INTEGER NOT NULL REFERENCES options( id ),
evaluator_comments TEXT NOT NULL,
);
The design is able to capture our data but doesn't restrict the options saved in evaluation_data to the subset specified in the evaluation's template's option mapping. We could probably enforce it with a trigger (we can definitely do it with application logic [we are doing so at the moment]) but are we going down the wrong path with this design?
Can anybody think of a better way to do it?
Edit:
Added an example of a potential trigger we would need to use to ensure valid options are enforced with this design.
CREATE FUNCTION valid_option() RETURNS trigger as $valid_option$
BEGIN
IF NOT NEW.option IN ( SELECT template_options.option
FROM template_options
INNER JOIN templates
ON template_options.template = templates.id
WHERE templates.id = ( SELECT evaluations.template
FROM evaluations
WHERE evaluations.id = NEW.evaluation ) ) THEN
RAISE EXCEPTION 'This option is not mapped for this evaluations template.';
END IF;
RETURN NEW;
END
$valid_option$ LANGUAGE plpgsql;
CREATE TRIGGER valid_option BEFORE INSERT ON evaluation_data FOR EACH ROW EXECUTE PROCEDURE valid_option();
Remember that you need two sets of tables. The first set containing the assessment, questions, answer alternatives, categories(?) needed to display the assessment to the participant. The second set of tables to record data about the evaluation (ie. the participant taking the assessment): which assessment, which questions, which answer alternatives and in which order they were presented, which answer they entered (are they allowed to answer the same question multiple times?), etc.
We're using the following structure (I've removed topic scoring since you didn't ask about it):
Models for presenting an assessment:
Assessment: assessment_name, passing_status, version
Question: assessment, question_number, question_type, question_text
AnswerAlternative: question, correct?, answer_text, points
Models for recording an evaluation (participant taking an assessment):
Progress: started_timestamp, finished_timestamp, last_activity, status (includes "finished")
Result: user, assessment, progress, currently_active, score, passing_grade?
Answer: result, question, selected_answer_alternative, answer_text, score
To achieve your goal, I would augment this by writing the generated evaluation to a table and pointing to it from Reault. You could also record the selection and presentation criteria so you can re-generate the assessment programmatically (ie. if you're selecting the questions from a larger question db and re-ordering the answer alternatives before presenting them to the participant).

What the best way to self-document "codes" in a SQL based application?

Q: Is there any way to implement self-documenting enumerations in "standard SQL"?
EXAMPLE:
Column: PlayMode
Legal values: 0=Quiet, 1=League Practice, 2=League Play, 3=Open Play, 4=Cross Play
What I've always done is just define the field as "char(1)" or "int", and define the mnemonic ("league practice") as a comment in the code.
Any BETTER suggestions?
I'd definitely prefer using standard SQL, so database type (mySql, MSSQL, Oracle, etc) should't matter. I'd also prefer using any application language (C, C#, Java, etc), so programming language shouldn't matter, either.
Thank you VERY much in advance!
PS:
It's my understanding that using a second table - to map a code to a description, for example "table playmodes (char(1) id, varchar(10) name)" - is very expensive. Is this necessarily correct?
The normal way is to use a static lookup table, sometimes called a "domain table" (because its purpose is to restrict the domain of a column variable.)
It's up to you to keep the underlying values of any enums or the like in sync with the values in the database (you might write a code generator to generates the enum from the domain table that gets invoked when the something in the domain table gets changed.)
Here's an example:
--
-- the domain table
--
create table dbo.play_mode
(
id int not null primary key clustered ,
description varchar(32) not null unique nonclustered ,
)
insert dbo.play_mode values ( 0 , "Quiet" )
insert dbo.play_mode values ( 1 , "LeaguePractice" )
insert dbo.play_mode values ( 2 , "LeaguePlay" )
insert dbo.play_mode values ( 3 , "OpenPlay" )
insert dbo.play_mode values ( 4 , "CrossPlay" )
--
-- A table referencing the domain table. The column playmode_id is constrained to
-- on of the values contained in the domain table playmode.
--
create table dbo.game
(
id int not null primary key clustered ,
team1_id int not null foreign key references dbo.team( id ) ,
team2_id int not null foreign key references dbo.team( id ) ,
playmode_id int not null foreign key references dbo.play_mode( id ) ,
)
go
Some people for reasons of "economy" might suggest using a single catch-all table for all such code, but in my experience, that ultimately leads to confusion. Best practice is a single small table for each set of discrete values.
add a foreign key to "codes" table.
the codes table would have the PK be the code value, add a string description column where you enter in the description of the value.
table: PlayModes
Columns: PlayMode number --primary key
Description string
I can't see this as being very expensive, databases are based on joining tables like this.
That information should be in database somewhere and not on comments.
So, you should have a table containing that codes and prolly a FK on your table to it.
I agree with #Nicholas Carey (+1): Static data table with two columns, say “Key” or “ID” and “Description”, with foreign key constraints on all tables using the codes. Often the ID columns are simple surrogate keys (1, 2, 3, etc., with no significance attached to the value), but when reasonable I go a step further and use “special” codes. Following are a few examples.
If the values are a sequence (say, Ordered, Paid, Processed, Shipped), I might use 1, 2, 3, 4, to indicate sequence. This can make things easier if you want to find all “up through” a give stages, such as all orders that have not yet been shipped (ID < 4). If you are into planning ahead, make them 10, 20, 30, 40; this will allow you to add values “in between” existing values, if/when new codes or statuses come along. (Yes, you cannot and should not try to anticipate everything and anything that might have to be done some day, but a bit of pre-planning like this can make some changes that much simpler.)
Keys/Ids are often integers (1 byte, 2 byte, 4 byte, whatever). There’s little cost to make them character values (1 char, 2 char, 3, char, 4 char). That’s character, not variable character. Done this way, you can have mnemonics on your codes, such as
O, P, R, S
Or, Pd, Pr, Sh
Ordr, Paid, Proc, Ship
…or whatever floats your boat. Done this way, I have found that it can save a lot of time when analyzing or debugging. You still want the lookup table, for relational integrity as well as a reminder for the more obscure codes.