Add a tuple-based check to this relation - sql

In relation Person (SSN, State, Name), add a tuple-based check: if a
person’s State is NY, then the first three digits of his SSN has to be
between ‘050’ and ‘134’. (Hint: use LEFT function in SQL).
CREATE TABLE Person (
SSN INT PRIMARY KEY,
State CHAR(50),
Name CHAR(100),
CHECK (
SELECT LEFT (SSN, 3) AS FirstThreeDigits, SSN FROM Person,
WHERE Person.State == ‘NY’) between '050' and between '134'
);
I am not comfortable with the CHECK condition here. I am not sure if this is the right way doing conditional check. Can someone please verify this? If not, how do we do a conditional check?
We need this:
if state == 'NY', perform CHECK
Do we need a trigger? I was thinking that if a new insert / update occurs, check the value. But the question doesn't ask that.

Ok so its generic but DDL typically isn't but lets use the doc on SQL Server CHECK Constraints as a guide anyway
You can create a CHECK constraint with any logical (Boolean)
expression that returns TRUE or FALSE based on the logical operators.
For the previous example, the logical expression is:
salary >= 15000 AND salary <= 100000.
Note that the above example from the docs is not a SELECT statement but and expression. This means all you need to do is come up with a expression that returns TRUE
when all of the following are true
State is NY
The Left three digits of the SSN is => ‘050’
The Left three digits of the SSN is <= 134.
Or
Sate is not NY
Remembering that you can Group a set of logical expressions in a Parens
e.g.
(Group of logical expressions) OR (Group of logical expressions)
It shouldn't be too hard

Related

Using sql EXCEPT to build an EAVT fact store

I'm exploring the datomic database, and in so doing having a go at taking some of its ideas and implementing them in sql in an incremental way so as to adjust to the new ways of data modelling. This question is really entirely about SQL though, I just mention that for background, to explain the why of what I'm doing here (though might be interesting for those interested in datomic too, which is why I also added the datomic tag to the question).
Generally we are getting rid of separate tables per type, but I will retain a users table for this example, rather than simply use an entities table (may try that later, but not yet).
create table users (
id uuid,
identity text -- e.g. 'the yankees', 'man born as john in birmingham on date x/y/z'
);
Then we have an EAVT store, also with an added boolean to specify add or retract. This table is append only. We will never issue update or delete on it.
create table eavt_log (
user_id uuid,
attribute text,
value text,
added boolean,
created_at timestamp
);
Now some data to illustrate usage intended
-- insert person number 12345 (imagine as national identity or birth certificate no.)
insert into users(id, identity) values (uuid_generate_v4(), 'p-12345');
-- lets insert some facts about a person previously known as john smith:
insert into eavt_log(user_id, attribute, value, added, created_at) values
((select id from users where identity='p-12345'),
'name', 'John Smith', true, '1911-01-01'),
((select id from users where identity='p-12345'),
'name', 'John Smith', false, '1931-01-01'),
((select id from users where identity='p-12345'),
'name', 'John Bontine Smith', true, '1931-01-01');
To make this useful (any database must provide leverage, as Hickey says), lets try to find all the unretracted names for the person previously known as John Smith.
Here's my (bad) attempt
-- find all currently unretracted names for person previously known as John Smith. This could
-- be 0, 1 (we hope), or more - it just depends though, and should, on what data has been input.
(select attribute, value from eavt_log
where user_id = (select id from users where identity='p-12345')
and attribute = 'name'
and added = true
order by created_at desc) -- <- can sneak this in w/o upsetting the except, as it's not in the select.
except
(select attribute, value from eavt_log
where user_id = (select id from users where identity='p-12345')
and attribute = 'name'
and added = false);
That gives:
attribute | value
-----------+--------------------
name | John Bontine Smith
(1 row)
Which is correct for the test data we gave it.
Then we can try to generalise to
create view unretracted as (
(select user_id, attribute, value from eavt_log
where added = true
order by created_at)
except
(select user_id, attribute, value from eavt_log
where added = false)
);
Problem is, both of these are flawed, because this simple except will give incorrect result for the case when a fact has been added, retracted, then added again. i.e. if we add
((select id from users where identity='p-12345'),
'name', 'John Smith', false, '1941-01-01');
to the facts inserted above, to denote that person-12345, in 1941, adopted the name John Smith again (without retracting the name 'John Bontine Smith', so in this case we want the system to return two values for his name).
With this data, the earlier retract of this identical value will cause this later re-assertion of the same value to be excluded from the result set, even though its been reasserted, due to the way EXCEPT is working (we did not do a linear table scan which i think may be required here?)
My question (finally!) -- is there a way to achieve this in SQL? Can SQL give us more leverage here?
It seems as if we need a where after the except which reaches back into the first select... but that seems impossible in set theory terms, so I wonder what else SQL can do here.
This is edited for your update, although I think there is still something wrong. You added an additional retracted row, which seems to contradict your text. Assuming that the row is actually added instead of retracted, we can use the below query.
You can use DISTINCT ON in postgres to get the last value per user. If you use that in a sub-select, you can only select the rows for which added = true:
SELECT attribute, value
FROM (
SELECT distinct on (eavt_log.user_id, attribute, value)
attribute, value, added
FROM eavt_log
JOIN users ON eavt_log.user_id = users.id
WHERE attribute = 'name'
ORDER BY eavt_log.user_id, attribute, value, created_at desc) sub
WHERE added = 't';
Edit: here's a fiddle

SQL fundamentals practise select statement

I am preparing for the Oracle SQL Fundamentals I exam and am having trouble with this question:
Examine the structure of the EMP table:
Name Null? Type
EMPNO NOT NULL NUMBER(3)
ENAME VARCHAR2(25)
SALARY NUMBER(10,2)
COMM_PCT NUMBER(4,2)
I want to generate a report that fulfills the following requirements:
1. Displays employees' names and commission amounts
2. Excludes employees who do not have a commission
3. Displays a zero for employees whose SALARY does not have a value
You issue the following SQL statement:
SQL>SELECT ename, NVL(salary * comm_pct, 0)
FROM emp
WHERE comm_pct <> NULL;
What is the outcome?
ANSWER is It executes successfully but displays no result.
Why does nothing display though?
Also if you guys have any resources for good SQL fundamentals practise questions please let me know.
Be careful with nulls and comparisons. If you want rows where there is some data, you would have "WHERE comm_pct IS NOT NULL". Using the comparison operator <> with NULL will return nothing.
select * from dual where dummy <> NULL
is not the same as
select * from dual where dummy is not null;
The other stuff in the question is just a distraction from this point. You can't compare a NULL to something else (null really means "I don't know what the heck the value is").
Same thing with = comparison. You don't say "WHERE comm_pct = NULL", you say "WHERE comm_pct IS NULL".
As a reference, here's the online SQL Reference Guide. Here is the link that refers to NULLs and comparisons.

SQL Query giving error

What is wrong with this query :
SELECT count() as total_students
FROM student_information;
SELECT
(
SELECT student_project_marks
from students_project
WHERE student_project_id=student_information.student_project_id
)
+ student_assignment_marks
+ student_exam_marks AS total_marks
FROM student_information
ORDER BY total_marks DESC;
UPDATE student_information
SET (
SELECT student_grade
FROM student_information
LIMIT 0.1*total_students
ORDER BY total_marks DESC
)="A";
Iam trying to select 0.1*total_students number of students ordered by their total marks obtained and update their grades.... Top 10% will be awarded as A.
I am getting error :
syntax error near '('
I have 2 tables :
created them via following query:
create table if not exists student_information (
student_name varchar(80),
student_roll_num int primary key,
student_email varchar(64),
student_assignment_marks int(2) check(student_assignment_marks<=30),
student_exam_marks int(2) check(student_exam_marks<=50),
student_project_id varchar(25),
student_grade varchar(2)
)
create table if not exist students_project (
student_project_id varchar(25),
student_project_title varchar(25),
student_project_marks int(2) check(student_project_marks>=0 and student_project_marks<=20)
)
Marks in the project is accessed from the student_project table via the student_project_id.
Now how do I award the grade based on the total marks...
Top 10% have to be awarded A, next 10% B and so on...
I know how to calculate total marks...
I have writen a query like this:
select student_roll_num,
(SELECT student_project_marks
from students_project
WHERE student_project_id=student_information.student_project_id )+
student_assignment_marks+student_exam_marks as total_marks from student_information;
It works.
This is not even approximately correct SQL. Also, it's not clear how you're executing these statements (from a console, from a program, etc).
A few comments to help get you on your way:
SQL does not "remember" the results of previous statements from one statement to the next. Therefore, your calculation of total_students in the first SQL statement has no connection with your attempt to actually use that value in your third statement. Similarly, your attempt to derive total_marks in the second query is not available in your third query.
Your second statement only makes sense if the internal query is guaranteed to produce only a single record for each row in student_information. I'm reasonable certain that what you're attempting to do would be better done using a JOIN rather than a sub-query.
The third query (UPDATE) is the one that's furthest away from SQL. UPDATE operates on one or more columns in a table. Each column is assigned a new value. The columns it operates on must be literally named with the correct identifiers. You can use a sub-query on the right side of the equals sign, but not on the left (although you don't have any reason to use one here). The conditions for the set of rows on which to operate belong in a WHERE clause at the end of the UPDATE statement, not within any kind of sub-query.
Guessing at your intent, I think you probably need to SUM or AVG the grades from student_projects (assuming each student has several projects to consider), and there's no aggregation in any of your queries.

How can I insert a value in a determined column with and statment?

Please someone could fix that question for me? don't know how to ask =/
I have 2 main columns:
CONTEST(PK) and RESULT
and other column named RESULTCHECK
I need to insert the value "1" in the column RESULTCHECK where the RESULT has some statement like.
for example
CONTEST RESULT
1 1,2,3,4,5
2 2,3,4,5,6
I want something like
INSERT INTO RESULTCHECK VALUES 1 WHERE RESULT LIKE '%2,3%'
how can I do that?
and in the end I have the currently result:
CONTEST RESULT RESULTCHECK
1 1,2,3,4,5 1
2 2,3,4,5,6 1
3 5,6,7,8,9 NULL
You should listen to #teresko's advice about modelling your data correctly (apart from the English bit) , but otherwise the answer is
update TABLENAME set RESULTCHECK = 1 where RESULT like "%2,3%"
It's because your DB structure is WRONG.
There is this thing , called many-to-many relation and junction tables.
Basically, what you need is a
CREATE TABLE ContestResults(
contest_id INT NOT NULL,
result_id INT NOT NULL,
PRIMARY KEY ( contest_id , result_id ),
FOREIGN KEY ( contest_id ) REFERENCES Contests( contest_is ) ,
FOREIGN KEY ( result_id ) REFERENCES Results( result_is )
)
Where you keep data about the relationship between many contests and many possible results.
P.S. and, please, use english names for the things in your code.
If you use Firebird 2.5, I think you can also use SIMILAR TO for regular expression
A new SIMILAR TO predicate is
introduced to support regular
expressions. The predicate's function
is to verify whether a given
SQL-standard regular expression
matches a string argument. It is valid
in any context that accepts Boolean
expressions, such as WHERE clauses,
CHECK constraints and PSQL IF() tests

unique pair in a "friendship" database

I'm posting this question which is somewhat a summary of my other question.
I have two databases:
1) db_users.
2) db_friends.
I stress that they're stored in separate databases on different servers and therefore no foreign keys can be used.
In 'db_friends' I have the table 'tbl_friends' which has the following columns:
- id_user
- id_friend
Now how do I make sure that each pair is unique at this table ('tbl_friends')?
I'd like to enfore that at the table level, and not through a query.
For example these are invalid rows:
1 - 2
2 - 1
I'd like this to be impossible to add.
Additionally - how would I seach for all of the friends of user 713 while he could be mentioned, on some friendship rows, at the second column ('id_friend')?
You're probably not going to be able to do this at the database level -- your application code is going to have to do this. If you make sure that your tbl_friends records always go in with (lowId, highId), then a typical PK/Unique Index will solve the duplicate problem. In fact, I'd go so far to rename the columns in your tbl_friends to (id_low, id_high) just to reinforce this.
Your query to find anything with user 713 would then be something like
SELECT id_low AS friend FROM tbl_friends WHERE (id_high = ?)
UNION ALL
SELECT id_high AS friend FROM tbl_friends WHERE (id_low = ?)
For efficiency, you'd probably want to index it forward and backward -- that is by (id_user, id_friend) and (id_friend, id_user).
If you must do this at a DB level, then a stored procedure to swap arguments to (low,high) before inserting would work.
You'd have to use a trigger to enforce that business rule.
Making the two columns in tbl_friends the primary key (unique constraint failing that) would only ensure there can't be duplicates of the same set: 1, 2 can only appear once but 2, 1 would be valid.
how would I seach for all of the friends of user 713 while he could be mentioned, on some friendship rows, at the second column ('id_friend')?
You could use an IN:
WHERE 713 IN (id_user, id_friend)
..or a UNION:
JOIN (SELECT id_user AS user
FROM TBL_FRIENDS
UNION ALL
SELECT id_friend
FROM TBL_FRIENDS) x ON x.user = u.user
Well, a unique constraint on the pair of columns will get you half way there. I think the easiest way to ensure you don't get the reversed version would be to add a constraint ensuring that id_user < id_friend. You will need to compensate for this ordering at insertion time, but it will get you the database Level constraint you desire without duplicating data or relying on foreign keys.
As for the second question, to find all friends for id=1 you could select id_user, id_friend from tbl_friend where id_user = 1 or id_friend = 1 and then in your client code throw out all the 1's regardless of column.
One way you could do it is to store the two friends on two rows:
CREATE TABLE FriendPairs (
pair_id INT NOT NULL,
friend_id INT NOT NULL,
PRIMARY KEY (pair_id, friend_id)
);
INSERT INTO FriendPairs (pair_id, friend_id)
VALUES (1234, 317), (1234, 713);
See? It doesn't matter which order you insert them, because both friends go in the friend_id column. So you can enforce uniqueness easily.
You can also query easily for friends of 713:
SELECT f2.friend_id
FROM FriendPairs AS f1
JOIN FriendPairs AS f2 ON (f1.pair_id = f2.pair_id)
WHERE f1.friend_id = 713