Check logical constraints through SQL nested references - sql

Let a SQL schema with 4 tables and nested references
For example modeling a restaurant, with a different menu every weekday, and some customer who have booked a table for one day and want to order a meal :
+------+
| days |
+----------+
| day |
+----------+
| friday |
| saturday |
| sunday |
+----------+
┐ ┌
+-------+ / \
| meals | +-----------+
+------------+----------+ | customers |
| meal | day (*) | +----------+---------+
+------------+----------+ | customer | day (*) |
| pizza | friday | +----------+---------+
| tacos | friday | | Joe | friday |
| chicken | saturday | | Alice | sunday |
| fish&chips | sunday | | Oscar | sunday |
| paella | sunday | +----------+---------+
+------------+----------+
┌ ┐
\ /
+--------+
| orders |
+--------------+----------+
| customer (*) | meal (*) |
+--------------+----------+
| Joe | pizza |
| Oscar | paella |
+--------------+----------+
(Foreign keys are marked with (*), and I tried to express references with arrows)
I want to prevent Alice from ordering some chicken, since she is planned on sunday, and chicken is planned on saturday.
In other words, for each record in orders,
orders.meal refers to a record of meals, and meals.day refers to a record of days
orders.customer refers to a record of customers, and customers.day refers to a record of days
and the days referred to must be the same.
Is there any SQL-way to check this constraint at INSERT / UPDATE ?

What you can do is declare "super keys" in your meals and customers tables, a key declared across both the meal/customer column and the day column.
That makes those pairs of columns valid references for a foreign key constraint. In turn, that then means that you can add the day column to the orders table and include day in your foreign key references.
If needs be, one can then declare a view over the orders table to conceal the day column, and make that view, rather than the orders table, be the way that applications interact with the database, closely mimicking your existing structure. The only complexity is in populating the day column during insert and update. One way to deal with that is via triggers on the view.

Related

SQL - specific requirement to compare tables

I'm trying to merge 2 queries into 1 (cuts the number of daily queries in half): I have 2 tables, I want to do a query against 1 table, then the same query against the other table that has the same list just less entries.
Basically its a list of (let's call it for obfuscation) people and hobby. One table is ALL people & hobby, the other shorter list is people & hobby that I've met. Table 2 would all be found in table 1. Table 1 includes entries (people I have yet to meet) not found in table 2
The tables are synced up from elsewhere, what I'm looking to do is print a list of ALL people in the first column then print the hobby ONLY of people that are on both lists. That way I can see the lists merged, and track the rate at which the gap between both lists is closing. I have tried a number of SQL combinations but they either filter out the first table and match only items that are true for both (i.e. just giving me table 2) or just adding table 2 to table 1.
Example of what I'm trying to do below:
+---------+----------+--+----------+---------+--+---------+----------+
| table1 | | | table2 | | | query | |
+---------+----------+--+----------+---------+--+---------+----------+
| name | hobby | | activity | person | | name | hobby |
| bob | fishing | | fishing | bob | | bob | fishing |
| bill | vidgames | | hiking | sarah | | bill | |
| sarah | hiking | | planking | sabrina | | sarah | hiking |
| mike | cooking | | | | | mike | |
| sabrina | planking | | | | | sabrina | planking |
+---------+----------+--+----------+---------+--+---------+----------+
Normally I'd just take the few days to learn SQL a bit better however I'm stretched pretty thin at work as it is!
I should mention the table 2 is flipped and the headings are all unique (don't think this matters)!
I think you just want a left join:
select t1.name, t2.activity as hobby
from table1 t1 left join
table2 t2
on t1.name = t2.person;

Calculate Equation From Seperate Tables Data

I'm working on my senior High School Project and am reaching out to the community for help! (As my teacher doesn't know the answer to my question).
I have a simple "Products" table as shown below:
I also have a "Orders" table shown below:
Is there a way I can create a field in the "Orders" table named "Total Cost", and make that automaticly calculate the total cost from all the products selected?
Firstly, I would advise against storing calculated values, and would also strongly advise against using calculated fields in tables. In general, calculations should be performed by queries.
I would also strongly advise against the use of multivalued fields, as your images appear to show.
In general, when following the rules of database normalisation, most sales databases are structured in a very similar manner, containing with the following main tables (amongst others):
Products (aka Stock Items)
Customers
Order Header
Order Line (aka Order Detail)
A good example for you to learn from would be the classic Northwind sample database provided free of charge as a template for MS Access.
With the above structure, observe that each table serves a purpose with each record storing information pertaining to a single entity (whether it be a single product, single customer, single order, or single order line).
For example, you might have something like:
Products
Primary Key: Prd_ID
+--------+-----------+-----------+
| Prd_ID | Prd_Desc | Prd_Price |
+--------+-----------+-----------+
| 1 | Americano | $8.00 |
| 2 | Mocha | $6.00 |
| 3 | Latte | $5.00 |
+--------+-----------+-----------+
Customers
Primary Key: Cus_ID
+--------+--------------+
| Cus_ID | Cus_Name |
+--------+--------------+
| 1 | Joe Bloggs |
| 2 | Robert Smith |
| 3 | Lee Mac |
+--------+--------------+
Order Header
Primary Key: Ord_ID
Foreign Keys: Ord_Cust
+--------+----------+------------+
| Ord_ID | Ord_Cust | Ord_Date |
+--------+----------+------------+
| 1 | 1 | 2020-02-16 |
| 2 | 1 | 2020-01-15 |
| 3 | 2 | 2020-02-15 |
+--------+----------+------------+
Order Line
Primary Key: Orl_Order + Orl_Line
Foreign Keys: Orl_Order, Orl_Prod
+-----------+----------+----------+---------+
| Orl_Order | Orl_Line | Orl_Prod | Orl_Qty |
+-----------+----------+----------+---------+
| 1 | 1 | 1 | 2 |
| 1 | 2 | 3 | 1 |
| 2 | 1 | 2 | 1 |
| 3 | 1 | 1 | 4 |
| 3 | 2 | 3 | 2 |
+-----------+----------+----------+---------+
You might also opt to store the product description & price on the order line records, so that these are retained at the point of sale, as the information in the Products table is likely to change over time.

Get sequential dates in query

I am attempting to query a Postgres database to retrieve records for a specific customer that are from dates that are adjacent to the current day.
So for example:
If today is Wednesday, it will pull the records from Monday and Tuesday if they exist.
If today is Wednesday, it will not pull a record from Monday if Tuesday does not have a record.
If pulling a record from last Wednesday, it will pull Tuesday and Thursday if they exist.
The database is currently structured with a Dates table that contains:
Id----------|------DateTime--------
And the Id has a foreign key relationship with the Customers table containing:
CustomerId-------|-----DateId(fk)-----|----other columns.....
I have thought to simply query the database if the date exists before and after the queried date (and pull that record if exists), and then loop such that a success will query the next date before or after..... but that seems horribly inefficient.
Is there a better method to obtain these records?
EDIT:
Example Dates table:
Id | DateTime
1 | '2018-01-23 16:19:17.600305'
2 | '2018-01-24 00:03:11.213492'
3 | '2018-01-25 03:10:14.911771'
Sample Customers table:
Id | DateId | CustomerId | Location1 | Location2 | Transport
66 | 2 | 2 | Market | Library | Car
67 | 2 | 3 | Bookstore | Library | Car
68 | 3 | 3 | Pool | Town | Car
69 | 3 | 3 | Pool | Town | Bus
Join the tables and use the lag and lead window functions with a window clause like OVER (ORDER BY dates.datetime).

Many-to-one with table records requiring one another

I have two tables: Talks and Days. Talks looks something like:
+----+----------------------------------+--------+
| Id | Name | Leader |
+----+----------------------------------+--------+
| 1 | How to improve revenue for tacos | Tacob |
| 2 | Improving sales potential | Bocat |
+----+----------------------------------+--------+
and the Days:
+--------+-----+
| TalkId | Day |
+--------+-----+
| 1 | Mon |
| 1 | Tue |
| 1 | Thu |
| 2 | Mon |
| 2 | Tue |
+--------+-----+
TalkId is a foreign key referencing the Talks table.
The foreign key enforces the relationship of "A Day requires a Talk". However, I would like to also enforce the reverse relationship "A Talk requires at least a Day".
I know that this constraint is similar to a Many-to-many relationship, where both records depend on each other. However, in this case, many days reference a talk but only one talk references many days.
Another problem is that after creating such a constraint, how would one insert both records at once?
I have searched for other questions and only found cases of Many-to-many relationships which will turn out like so:
+----+----------------------------------+--------+
| Id | Name | Leader |
+----+----------------------------------+--------+
| 1 | How to improve revenue for tacos | Tacob |
| 2 | Improving sales potential | Bocat |
+----+----------------------------------+--------+
+----+-----+
| Id | Day |
+----+-----+
| 1 | Mon |
| 2 | Tue |
| 3 | Thu |
| 4 | Mon |
| 5 | Tue |
+----+-----+
+--------+-------+
| TalkId | DayId |
+--------+-------+
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 4 |
| 2 | 5 |
+--------+-------+
Where TalkId references Talks's Id and DayId references Days's Id.
Edit:
Ignore what I requested for above.
What I hope to be able to do:
SELECT all valid Talks
SELECT all valid Days
What I hope to be unable to do:
INSERT a Talk without a Day
INSERT a Day without a Talk
It sounds like you want a simple foreign key relationship:
alter table days add constraint fk_days_talkid foreign key (talkid) references talks(talkid);
This guarantees that talkid is valid. Then you declare days.talkid to be not null and you are guaranteeing the relationship you describe.
-- Day named (TheDay) exists.
--
Calendar {TheDay}
PK {TheDay}
-- Talk (TalkID) titled (TalkName), presented by (Leader) is by default
-- scheduled on (DefaultDay).
Talk {TalkID, TalkName, Leader, DefaultDay}
PK {TalkID}
AK {TalkName}
FOREIGN KEY {DefaultDay} REFERENCES Calendar {TheDay}
--Talk (TalkID) is also scheduled on (TheDay).
--
TalkDay {TalkID, TheDay}
PK {TalkID, TheDay}
FOREIGN KEY {TalkID} REFERENCES Talk {TalkID}
FOREIGN KEY {TheDay} REFERENCES Calendar {TheDay}
Note PK = primary key
AK = alternate key (unique)
All attributes (columns) NOT NULL
Select all days for a specific talk:
select TalkName, DefaultDay as TalkDay
from Talk
where TalkName = 'How to improve revenue for tacos'
union
select TalkName, b.TheDay as TalkDay
from Talk as a
join TalkDay as b on b.TalkID = a.TalkID
where a.TalkName = 'How to improve revenue for tacos'
Select all talks on a specific day:
select TalkName, DefaultDay as TalkDay
from Talk
where DefaultDay = 'Tue'
union
select TalkName, b.TheDay as TalkDay
from Talk as a
join TalkDay as b on b.TalkID = a.TalkID
where b.TheDay = 'Tue'

How should I create an SQL table with stock information so that I can add new stocks and new fields easily?

I want to create an SQL table, where I can have any number of stocks (ie. MSFT, GOOG, IBM) and any number of fields (ie. Full Name, Sector, Country). But I want the flexibility to add new stocks and new fields as I go along. Say I want to add a new stock like AAPL, or I want a new boolean field for whether they pay dividends or not. I don't expect to store dynamic fields like CurrentStockPrice, but the information will have to change periodically. For instance, when a company changes its dividend policy. How do I design the table so that I don't have to change its structure?
I had one idea where I could have a new table for each stock, and a master table that has all the stocks, and a pointer to each individual stock's table. That way, I can freely add new stocks, and new fields easily. But I'm not very familiar with SQL, and would like an expert opinion on how it should be implemented.
The simple answer is that your requirements are not a good fit for SQL. The most important concern is not how to store the data, but how you will retrieve it - what kind of query will you need to run?
EAV allows you to store data whose schema you don't know in advance - but has lots of drawbacks when querying. Even moderately complex queries (find all stocks where the dividend was paid between 1 and 12 Jan, in the tech sector, whose CEO is female) run into a lot of compexity.
Creating a new table for each type of record very quickly gets crazy too - imagine the query above if you have to search dozens or hundreds of type-specific tables.
The relational model works best when you know the schema of the information in advance.
If you don't know the schema, consider using a NoSQL solution, or use SQL Server's support for XML or JSON. Store the fixed data in rows & columns, and the variable data in XML or JSON. Performance for searching is pretty good, and it's much less convoluted as a solution.
Just to expand on my comment, because the question itself begs for a couple of common schema anti-patterns. Some hybrid of EAV may actually be a good fit if you are willing to give up some flexibility and simplicity in your SQL and you aren't looking for fast queries.
EAV
EAV, or Entity-Attribute-Value is a design where, in your case, you would have a master table of stocks with some common attributes, or maybe even ticker info with a datetime. Something like:
+---------+--------+--------------+
| stockid | symbol | name |
+---------+--------+--------------+
| 1 | goog | Google |
| 2 | msft | Microsoft |
| 3 | gpro | GoPro |
| 4 | xom | Exxon Mobile |
+---------+--------+--------------+
And a second table (the EAV table) to store ever changing attributes:
+---------+-----------+------------+
| stockid | attribute | value |
+---------+-----------+------------+
| 1 | country | us |
| 1 | favorite | TRUE |
| 1 | startyear | 2004 |
| 3 | favorite | |
| 3 | bobspick | TRUE |
| 4 | country | us |
| 3 | country | us |
| 2 | startyear | 1986 |
| 2 | employees | 18000 |
| 3 | marketcap | 1850000000 |
+---------+-----------+------------+
And perhaps a third table to get that minute by minute ticker info stored:
+---------+----------------+--------+
| stockid | datetime | value |
+---------+----------------+--------+
| 1 | 9/21/2016 8:15 | 771.41 |
| 1 | 9/21/2016 8:14 | 771.39 |
| 1 | 9/21/2016 8:12 | 771.37 |
| 1 | 9/21/2016 8:10 | 771.35 |
| 1 | 9/21/2016 8:08 | 771.33 |
| 1 | 9/21/2016 8:06 | 771.31 |
| 1 | 9/21/2016 8:04 | 771.29 |
| 2 | 9/21/2016 8:15 | 56.81 |
| 2 | 9/21/2016 8:14 | 56.82 |
| 2 | 9/21/2016 8:12 | 56.83 |
| 2 | 9/21/2016 8:10 | 56.84 |
+---------+----------------+--------+
Generally this is considered not great design since stitching data back together in a format like:
+-------------+-----------+---------+-----------+----------+--------------+
| stocksymbol | stockname | country | startyear | bobspick | currentvalue |
+-------------+-----------+---------+-----------+----------+--------------+
causes you to write a query that is not fun to look at:
SELECT
stocks.stocksymbol,
stocks.name,
country.value,
bobspick.value,
startyear.value,
stockvalue.stockvalue
FROM
stocks
LEFT OUTER JOIN (SELECT stockid, value FROM fieldsTable WHERE attribute = 'country') as country ON
stocks.stockid = country.stockid
LEFT OUTER JOIN (SELECT stockid, value FROM fieldsTable WHERE attribute = 'Bobspick') as bobspick ON
stocks.stockid = bobspick.stockid
LEFT OUTER JOIN (SELECT stockid, value FROM fieldsTable WHERE attribute = 'startyear') as startyear ON
stocks.stockid = startyear.stockid
LEFT OUTER JOIN (SELECT max(value) as stockvalue, stockid FROM ticketTable GROUP BY stockid) as stockvalue ON
stocks.stockid = stockvalue.stockid
WHERE symbol in ('goog', 'msft')
You can see that every "field" in the EAV table gets its own subquery, which means we read that table from storage three times. We gain the flexibility on the front end over the database design, but we lose flexibility when querying.
Imagine a more traditional schema:
+---------+--------+--------------+---------+----------+----------+-----------+------------+-----------+
| stockid | symbol | name | country | bobspick | favorite | startyear | marketcap | employees |
+---------+--------+--------------+---------+----------+----------+-----------+------------+-----------+
| 1 | goog | Google | us | | TRUE | 2004 | | |
| 2 | msft | Microsoft | | | | 1986 | | 18000 |
| 3 | gpro | GoPro | us | TRUE | | | 1850000000 | |
| 4 | xom | Exxon Mobile | us | | | | | |
| | | | | | | | | |
+---------+--------+--------------+---------+----------+----------+-----------+------------+-----------+
and
+---------+----------------+--------+
| stockid | datetime | value |
+---------+----------------+--------+
| 1 | 9/21/2016 8:15 | 771.41 |
| 1 | 9/21/2016 8:14 | 771.39 |
| 1 | 9/21/2016 8:12 | 771.37 |
| 1 | 9/21/2016 8:10 | 771.35 |
| 1 | 9/21/2016 8:08 | 771.33 |
| 1 | 9/21/2016 8:06 | 771.31 |
| 1 | 9/21/2016 8:04 | 771.29 |
| 2 | 9/21/2016 8:15 | 56.81 |
| 2 | 9/21/2016 8:14 | 56.82 |
| 2 | 9/21/2016 8:12 | 56.83 |
| 2 | 9/21/2016 8:10 | 56.84 |
+---------+----------------+--------+
To get the same results:
SELECT
stocks.stocksymbol,
stocks.name,
stocks.country,
stocks.bobspick,
stocks.startyear,
stockvalue.stockvalue
FROM
stocks
LEFT OUTER JOIN (SELECT max(value) as stockvalue, stockid FROM ticketTable GROUP BY stockid) as stockvalue ON
stocks.stockid = stockvalue.stockid
WHERE symbol in ('goog', 'msft')
Now we have the flexibility in the query where we can quickly change out fields without monkeying around in subqueries, but we have to hassle our DBA every time we want to add a field.
There is a further abstraction from EAV that is definitely something to avoid. I don't know if it has a name, but I call it "Database in a database". Here you have a table of tables, table of fields, and a table of values. The entire schema is kept as records as our the values that would be stored in the schema. Ultimatele flexibility is gained, but the sql you will write to get at your data will be nightmarish and your query speeds will degrade at a fast rate as you add to your data/schema/data/schema mess.
As for your last idea of adding a new table for each stock, if the fields you are going to track for each stock are different (startyear, employees, and market cap for one stock and marketmax, country, address, yearsinbusiness in another) and you aren't planning on adding new stocks often, then it may be a good fit. I'm betting though that the attributes/fields that you track on stock1 are going to also be tracked on stock2, and therefore suggest that your should have a single stock table with all those common attributes and maybe an EAV to track attributes that are particular to each stock so you can have the flexibility you need.
In each of these schemas I would also suggest that you put your ticker data in it's own table. Whether you are capturing ticket data by the minute, hour, day, week, or month, because it's datetime level data, it deserves it's own table. (Unless you are only going to track the most current value, then it becomes a field).
If you want to add fields dynamically, but without actually altering the schema of the table, then you should use a vertical schema for the table and retrieve the data via a PIVOT statement.
In this manner you can add as many Field/Value pairs as you wish for each stock/customer pairing.
The basic table would have 5 columns perhaps:
ID (Identity); StockName; AttributeName; Value; Timestamp;
If you take a look at how SQL organizes it's table schema in INFORMATION_SCHEMA.COLUMNS, it provides this very same vertical schema layout for you.