select all rows which satisfy constraints from another table - mysql

my database (MySQL) contains 5 tables.
It should describe certain actions, states and constraints.
For any action to be executable all of its constraints must be met.
Each state assures that certain constraints are met:
Actions (ID, name)
States (ID, name)
Constraints (ID, name)
has_constraints (Action_ID, Constraint_ID)
assures_constraints (State_ID, Constraint_ID)
My Question is: how do I select all actions, which are executable for a given state?
Thanks in advance,
Jan

SELECT a.*
FROM actions a
WHERE NOT EXISTS
(
SELECT 1
FROM has_constraints hc
WHERE hc.action_id = a.id
AND NOT EXISTS
(
SELECT 1
FROM assure_constraints ac
WHERE ac.state_id = $my_state_id
AND ac.constraint_id = hc.constraint_id))
Retrieves all actions with no constraints that a state wouldn't allow.
Includes actions without constraints at all.

You can do a select using JOINs, I cannot tell the exact table schema fro your example.
But take a look in MySQLs documentation and learn about JOINs, they will take you where you want.

I don't don't know the columns you from each of those tables but according to your statement perhaps we could also have the statement done like this:
SELECT
<columns>
FROM
has_constraints `constraint` INNER JOIN actions action ON `constraint`.action_id = action.id
RIGHT JOIN assure_constraints assure ON assure.constraint_id = `constraint`.id
WHERE constraint.state_id = <id of state>
If you want to get some columns from the state table you may add it on the join statements just inside the from.

Related

I'm trying to let a user who has been assigned something be able to view it with PHP and MYSQL

I've tried searching an answer for this and I can't realy find the one that fits what I'm looking for.
I'm making a small web application that allows an admin to create course material and a test, that a user will be assigned.
What I am trying to figure out, is how a user, who has been assigned a course, can access that course by clicking a button.
I have created one table which stores the user information, with a user ID being a primary key. I have a course table, which stores all materials and the course's test, with a course id being that table's primary key.
I made a third table which is an assigned_courses table, it contains two foreign keys. One referencing the user id from the user table, and one referencing the course id on the course table.
I've inserted a few images to show what you mean.
I can't quite figure out the Sql syntax to pull course materials and test based on the assigned_course table.
All feedback appreciated. This is the two foreign keys that reference the user id (id) and the course id(u_id)
Not sure what column names you have so I used * instead. You can use JOIN (AKA INNER JOIN) to link the tables together. You join on the primary keys of the 2 tables.
I have created one table which stores the user information, with a
user ID being a primary key. I have a course table, which stores all
materials and the course's test, with a course id being that table's
primary key.
I used id for your user table and course_id for the course table. Change as needed.
Find all courses for a given user.
SELECT a.*
FROM course a
JOIN assigned_course b ON a.course_id = b.id
WHERE b.u_id = :userId
;
Find all users for a given course.
SELECT a.*
FROM user a
JOIN assigned_course b ON a.id = b.u_id
WHERE b.id = :courseId
;
EDIT #1 - Subquery without JOIN
Thanks for your feedback, as I stated above, I was hoping to avoid a
join.
I highly recommend you learn joins. They are essential when you need to query more than 1 table. In this case, you can use a sub-query to find matching courses for a user but you won't be able to access that user data at the same time; that is the power of a join. I'll include a final example underneath showing how to get courses and user data at the same time.
Find all courses for a given user (using sub-query)
SELECT a.*
FROM course a
WHERE course_id IN (
SELECT id
FROM assigned_course b
WHERE u_id = :userId)
;
Final example showing courses related to users (many-to-many)
SELECT * -- You now have access to a, b, and c data
FROM course a
JOIN assigned_course b ON a.course_id = b.id -- joined by PKs
JOIN user c ON c.id = b.u_id -- joined by PKs
WHERE c.id = :userId
;
It sounds to me like you want to use SQL Join syntax which goes as follows:
select column1, column2, column3 etc...
from table1
join course on table1.id = course.id
join assigned_course on table1.id = assigned_course.id
replace the column1,2,3 etc with the actual column names, and replace the foreign_key_name with the actual name of the foreign key in the sub-table and the same for the user_primary_key
you can find some other examples of mysql joins here
as far as how to implement them into your html or php file, that is a very different question and would require knowing alot more about how your code is setup in the form
a place that is pretty user friendly is that might help with pulling the data is here just make sure you are aware of using prepared statements for your code

Lazily evaluate MySQL view

I have some MySQL views which define a number of extra columns based on some relatively straightforward subqueries. The database is also multi-tenanted so each row has a company ID against it.
The problem I have is my views are evaluated for every row before being filtered by the company ID, giving huge performance issues. Is there any way to lazily evaluate the view so the 'where' clause in the outer query applies to the subqueries in the view. Or is there something similar to views that I can use to add the extra fields. I want to calculate them in SQL so the calculated fields can be used for filtering/searching/sorting/pagination.
I've taken a look at the MySQL docs that explain the algorithms available and am aware that the views can't be proccessed as a 'merge' since they contain subqueries.
view
create view companies_view as
select *,
(
select count(id) from company_user where company_user.company_id = companies.id
) as user_count,
(
select count(company_user.user_id)
from company_user join users on company_user.user_id = users.id
where company_user.company_id = companies.id
and users.active = 1
) as active_user_count,
(
select count(company_user.user_id)
from company_user join users on company_user.user_id = users.id
where company_user.company_id = companies.id
and users.active = 0
as inactive_user_count
from companies;
query
select * from companies_view where company_id = 123;
I want the subqueries in the view to be evaluated AFTER applying the 'where company_id = 123' from the main query scope. I can't hard code the company ID in the view since I want the view to be usable for any company ID.
You cannot change the order of evaluation, that is set by the MySQL server.
However, in this particular case you could rewrite the whole sql statement to use joins and conditional counts instead of subqueries:
select c.*,
count(u.id) as user_count,
count(if(u.active=1, 1, null)) as active_user_count,
count(if(u.active=0, 1, null)) as inactive_user_count
from companies c
left join company_user cu on c.id=cu.company_id
left join users u on cu.user_id = u.id
group by c.company_id, ...
If you have MySQL v5.7, then you may not need to add any further fields to the group by clause since the other fields in the companies table would be functionally dependent on the company_id. In earlier versions you may have to list all fields in the companies table (depends on the sql mode settings).
Another way to optimalise such query would be using denormalisation. Your users and company_user table probably have a lot more records than your companies table. You could add a user_count, an active_user_count, and an inactive_user_count field to the companies table, add after insert / update / delete triggers to the company_user table and an after update to the users table and update these 2 fields there. This way you would not need to do the joins and the conditional counts in the view.
It is possible to convince the optimizer to handle a view with scalar subqueries using the MERGE algorithm... you just have to beat the optimizer at its own game.
This will seem quite unorthodox to some, but it is a pattern I use with success in cases where this is needed.
Create a stored function to encapsulate each subquery, then reference the stored function in the view. The optimizer remains blissfully unaware that the functions will invoke the subqueries.
CREATE FUNCTION user_count (_cid INT) RETURNS INT
DETERMINISTIC
READS SQL DATA
RETURN (SELECT count(id) FROM company_user WHERE company_user.company_id = _cid);
Note that a stored function with a single statement does not need BEGIN/END or a change of DELIMITER.
Then in the view, replace the subquery with:
user_count(id) AS user_count,
And repeat the process for each subquery.
The optimizer will then process the view as a MERGE view, select the one appropriate row from the companies table based on the outer WHERE, invoke the functions, and... problem solved.

Conditionals in WHEREs or JOINs?

Lets say I have the following query:
SELECT occurs.*, events.*
FROM occurs
INNER JOIN events ON (events.event_id = occurs.event_id)
WHERE event.event_state = 'visible'
Another way to do the same query and get the same results would be:
SELECT occurs.*, events.*
FROM occurs
INNER JOIN events ON (events.event_id = occurs.event_id
AND event.event_state = 'visible')
My question. Is there a real difference? Is one way faster than the other? Why would I choose one way over the other?
For an INNER JOIN, there's no conceptual difference between putting a condition in ON and in WHERE. It's a common practice to use ON for conditions that connect a key in one table to a foreign key in another table, such as your event_id, so that other people maintaining your code can see how the tables relate.
If you suspect that your database engine is mis-optimizing a query plan, you can try it both ways. Make sure to time the query several times to isolate the effect of caching, and make sure to run ANALYZE TABLE occurs and ANALYZE TABLE events to provide more info to the optimizer about the distribution of keys. If you do find a difference, have the database engine EXPLAIN the query plans it generates. If there's a gross mis-optimization, you can create an Oracle account and file a feature request against MySQL to optimize a particular query better.
But for a LEFT JOIN, there's a big difference. A LEFT JOIN is often used to add details from a separate table if the details exist or return the rows without details if they do not. This query will return result rows with NULL values for b.* if no row of b matches both conditions:
SELECT a.*, b.*
FROM a
LEFT JOIN b
ON (condition_one
AND condition_two)
WHERE condition_three
Whereas this one will completely omit results that do not match condition_two:
SELECT a.*, b.*
FROM a
LEFT JOIN b ON some_condition
WHERE condition_two
AND condition_three
Code in this answer is dual licensed: CC BY-SA 3.0 or the MIT License as published by OSI.

MySQL column definition

Is there a way, using MySQL 5.0, to define a column in a table that is to be calculated whenever a select is executed on that particular row? For example say I have two tables, A and B:
A:
ID
COMPUTED_FIELD = SUM(SELECT B.SOME_VALUE FROM B WHERE B.A_ID = ID)
B:
ID
A_ID
SOME_VALUE
In the example, when I run a select on A for a particular ID, I want it to return the sum of all values in Table B for that particular value of A.ID. I know how to do this using multiple separate queries and doing a group by A_ID, but I'm trying to streamline the process a little.
Yes. You cannot do that inside a table, but you can do it in a view.
CREATE VIEW A
AS
SELECT SUM(B.SOME_VALUE) AS COMPUTED_FIELD
FROM B
WHERE B.A_ID = 'id';
Obviously id needs to be whatever you are searching for.
You don't need table A in this case.
Tables cannot contain calculated values. Try using views. The manual entry has full details. Your end result will come out looking something like: CREATE VIEW A AS SELECT SUM('SOME_VALUE'), B.A_ID FROM B; (Not tested)
That said, I'm not sure how important it is for you to have an independent, unique ID for table A -- this isn't possible unless you add another table C to hold the foreign keys referenced by B.A_ID, and use table C as a reference in creating your view.
As txwikinger suggests, the best way to do this is set up A as a view, not a table. Views are, for all intents and purposes, a streamlined, reusable query. They're generally used when a)a common query has a computed column, or b)to abstract away complex joins that are often used.
To expand on the previous answer, in order to be able to query A for any ID, try this view:
CREATE VIEW A
AS
SELECT B.A_ID AS ID, SUM(B.SOME_VALUE) AS COMPUTED_FIELD
FROM B
GROUP BY B.A_ID;
Then you can select into it normally, for example:
SELECT A.ID, A.COMPUTED_FIELD
FROM A
WHERE A.ID IN (10, 30);
or
SELECT A.ID, A.COMPUTED_FIELD
FROM A
WHERE COMPUTED_FIELD < 5;

Delphi 2009, MyDAC and relational database

I have quite a problem concerning the use of relational database concepts in Delphi 2009 with MyDAC.
I have a database structure that looks somehow like the following:
Item
id
name
Storage
id
name
StorageItem
id
item_id
storage_id
place
Now when I have an active dataset from "Item" how can I display all associated Storages in for example a DBGrid?
By the way: Would it be better to not use "id" in every table but to alter it and use something like for example "id_item" or "id_storage"?
Thank you in advance :)
With StorageItem you created a
many-to-many relationship. If you
need just one-to-many (many storages
are related to one item, but you
don't need the vice versa), then you
may just add another field to the
Storage table (item_id) that would
be a foreign key for Items table.
Then you create an index on
item_id in Storage table, and
connect the two tables in
master-detail relationship.
If you do need many-to-many then you
may add a query component with SQL
(select * from StorageItem where
item_id := :current_storage_id), and
current_storage_id is your query's
parameter.
Select a.ID, b.Name, a.Place
from StorageItem a
inner join Storage b
on (a.id = b.id)
the above query will return all the items in StorageItem table with it's name, now if you want to filter it to return only items for a specific item add where clause to be like
Select a.ID, b.Name, a.Place
from StorageItem a
inner join Storage b
on (a.id = b.id)
where a.item_id = 1 -- place the item id here
you can use where with parameters such as:
MyQuery.Sql.Text := ' Select a.ID, b.Name, a.Place from StorageItem a
+ ' inner join Storage b on (a.id = b.id) '
+ ' where a.item_id = :ItemNo ';
MyQuery.ParamByName('ItemNo').asInteger := 1;
MyQuery.Open;
and assign the query above to dbGrid
also you can use MasterSource property to make the relations without using the "where" part
I'm not familiar with MyDAC personally, but most dataset components have some way to establish master-detail relationships. Check if there's a MasterSource property on your dataset, or some similar way to link a detail dataset to a master dataset. If not, you could use a TDatasetField to establish a link, and filter the nested dataset to only display the right records.
As for ID column names, it's a good idea to give a descriptive name to each field, so you can tell by looking at the code that you've got your links right. If you call your id column "id", that could be any id column, and that could get confusing if you start passing around references to datasets. But if it's called item_id every time, (not item_id sometimes and id_item sometimes) then you always know exactly what you're looking at. It makes it easier to know that your code is right, too. A filter that says "master.item_id = detail.item_id" is easier to read that "master.id = detail.item_id". That could be wrong and fail silently if master is assigned to the wrong dataset, for example.