WHERE statement with dynamic input - mysql

I have two tables. The first one (item) is listing apartments. The second (feature) is a list of features that an apartment could have. Currently we list about 25 different features.
As every apartment can have a different set of features, I think it makes sense to have a 1:1 relationship between items and features table.
If in feature table for one the features the value is '1', this means that the linked apartment has this feature.
+-------------+------------+--------------+-------------+------------+
| table: item | | | | |
+-------------+------------+--------------+-------------+------------+
| id | created_by | titel | description | address |
+-------------+------------+--------------+-------------+------------+
| 10 | user.id | Nice Flat | text | address.id |
+-------------+------------+--------------+-------------+------------+
| 20 | user.id | Another Flat | text | address.id |
+-------------+------------+--------------+-------------+------------+
| 30 | user.id | Bungalow | text | address.id |
+-------------+------------+--------------+-------------+------------+
| 40 | user.id | Apartment | text | address.id |
+-------------+------------+--------------+-------------+------------+
+----------------+---------+--------------+----------------+--------------+------+
| table: feature | | | | | |
+----------------+---------+--------------+----------------+--------------+------+
| id | item_id | key_provided | security_alarm | water_supply | lift |
+----------------+---------+--------------+----------------+--------------+------+
| 1 | 10 | 1 | 0 | 0 | 1 |
+----------------+---------+--------------+----------------+--------------+------+
| 2 | 20 | 0 | 1 | 1 | 0 |
+----------------+---------+--------------+----------------+--------------+------+
| 3 | 30 | 1 | 1 | 0 | 1 |
+----------------+---------+--------------+----------------+--------------+------+
| 4 | 40 | 1 | 1 | 1 | 1 |
+----------------+---------+--------------+----------------+--------------+------+
I want to build a filter functionality so user can select to show only apartments with certain features.
e.g.:
$key_provided = 1;
$security_alarm = 1;
$water_supply = 0;
Does this database approach sounds reasonable for you?
What’s the best way to build a MySQL query to retrieve only apartments where the filter criteria match, keeping in mind that the number of features can be grow in future?

A better approach is to have a features table. In your case, they all seem to be binary -- yes or no -- so you can get away with:
create table item_features (
item_feature_id int auto_increment primary key,
item_id int not null,
feature varchar(255)
foreign key item_id references items(item_id)
);
The data would then have the positive features, so the first item would be:
insert into item_features (item_id, feature)
values (1, 'key_provided'), (1, 'lift');
This makes it easy to manage the features, particularly adding new ones. You might want to use a trigger, check constraint, or reference table to validate the feature names themselves, but I don't want to stray too far from your question.
Then checking for features is a little more complicated, but not that much more so. One method is explicitly using exists and not exists for each desired/undesired one:
select i.*
from items i
where exists (select 1
from item_features itf
where itf.item_id = i.item_id and
itf.feature = 'key_provided'
) and
exists (select 1
from item_features itf
where itf.item_id = i.item_id and
itf.feature = 'security_alarm'
) and
not exists (select 1
from item_features itf
where itf.item_id = i.item_id and
itf.feature = 'water supply'
);

For your existing data structure, you can filter as follows:
select i.*
from item i
inner join feature f
on f.item_id = i.id
and f.key_provided = 1
and f.security_alarm = 1
and f.water_supply = 0
This will give you all the apartments that satisfy the given criteria. For more criterias, you can just add more conditions to the on part of the join.
As a general comment about your design:
since you are creating a 1-1 relationship between apartments and features, you might as well consider having a single table to store them (spreading the information over two tables does not have any obvious advantages)
your design is OK as long as features do not change too often, since, basically, everytime a new feature is created, you need to add more columns to your table. If features are added (or removed) frequently, this can become heavy to manage; in that case, you could consider having a separated table where each (item, feature) tuple is stored in a different row, which will make this of things easier to do (with the downside that queries will get more complicated to write)

Related

SQL - get records from many-to-many relations by the user itself -OR- his group

I have two database tables, one as the main table and the other as the relation table.
The first table is a table of contents and the second table is a table that connects to users or groups.
Some data may also be modified in this second table.
I'm not sure about the structure and performance.
for example, we have User Id 160 which is under group id 7
So for the first, we have a post Table.
id | title | content | cover | status
------------------------------------------------
1 | first | content 1 | /img/... | 1
2 | second | content 2 | /img/... | 1
3 | another | content 3 | /img/... | 1
4 | four | content 4 | /img/... | 1
5 | five | content 5 | /img/... | 1
and for the second we have a post_rel Table:
id | group_id | user_id | post_id | title | cover | sort | status
---------------------------------------------------------------------------
1 | 7 | NULL | 1 | g title | img/... | 1 | 1
2 | NULL | 160 | 1 | u title | NULL | 2 | 1 *** selected for user_id
3 | 7 | NULL | 2 | NULL | img/... | 6 | 0
4 | NULL | 160 | 2 | NULL | img/... | 4 | 1 *** selected for user_id
5 | NULL | 160 | 3 | some | img/... | 3 | 1 *** selected for user_id
6 | 7 | NULL | 4 | NULL | img/... | 9 | 1 *** selected for group_id
7 | NULL | 165 | 5 | NULL | img/... | 5 | 0
This is the basic query we have.
select
`post_rel`.`title` as `custom_title`,
`post_rel`.`cover` as `custom_cover`,
`post_rel`.`group_id`,
`post_rel`.`user_id`,
`post`.*
from
`post`
inner join `post_rel` on `post`.`id` = `post_rel`.`post_id`
where
`post`.`status` = 1
and `post_rel`.`status` = 1
and (
`post_rel`.`user_id` = 160
or (
`post_rel`.`group_id` = 7
and `post_rel`.`post_id` not in (
select
`post_rel`.`post_id`
from
`post_rel`
where
`post_rel`.`user_id` = 160
)
)
)
order by
`post_rel`.`sort` asc
So, what you think about the basic query? Especially in the subquery, won't performance drop in a large table? Is it possible to write a better and simpler query or change the structure?
Edit: this is sqlfiddle example of my code and structure http://sqlfiddle.com/#!9/ed9d4b/1
I would change it to use "not exists" instead of "not in" and would use aliases so I could pull it off like so:
select
b.`title` as `custom_title`,
b.`cover` as `custom_cover`,
b.`group_id`,
b.`user_id`,
a.*
from
`post` a
inner join `post_rel` b on a.`id` = b.`post_id`
where
a.`status` = 1
and b.`status` = 1
and (
b.`user_id` = 160
or (
b.`group_id` = 7
and not exists (
select
'x'
from
`post_rel` c
where
c.`user_id` = 160 and c.`post_id`=b.`post_id`
)
)
)
order by
b.`sort` asc
Typically when managing users and group, there's this notion of an exception user who directly can get assigned to assets just like the whole group. This seems to be an example of that.
From a modeling-only perspective, there are 2 ways to deal with that:
Ensure that every user exists in a group and that you only assign assets to groups. For the exception user, create a group. You could even enforce that every user belongs to only one group. This way your post_rel table deals with only groups. Unfortunately, the relationship between group and user is not understood well enough to weigh in appropriately.
Driven only by the need to eliminate null values towards a good model which also reduces overhead, the other option is to use name value pairs and allows the User and Group to exist in the same field with another field besides it, denoting Group or User.
These are the SQL Fiddle:
NOT EXISTS version: http://sqlfiddle.com/#!9/1af8cf/2
NOT IN version: http://sqlfiddle.com/#!9/1af8cf/1
Some reading on nulls https://dev.mysql.com/doc/refman/5.6/en/data-size.html
Specifically:
Declare columns to be NOT NULL if possible. It makes SQL operations faster, by enabling better use of indexes and eliminating overhead for testing whether each value is NULL. You also save some storage space, one bit per column. If you really need NULL values in your tables, use them. Just avoid the default setting that allows NULL values in every column.

How to fill a SQL column with data (calculated) from another table

I have a question and don't know how to approach the problem exactly.
I have two tables as following:
Clients
| c_id | name | reference |
| ---- | ------- | --------- |
| 1 | ClientA | 1 |
| 2 | ClientB | 1 |
| 3 | ClientC | 2 |
| 4 | ClientD | 2 |
| 5 | ClientE | 1 |
| 1 | ClientF | 3 |
Tour
| t_id | name | count |
| ---- | ------- | ----- |
| 1 | TourA | 3 |
| 2 | TourB | 2 |
| 3 | TourC | 1 |
"Reference" in the "Client" table is defined as foreign key.
Is it possible to fill the column "count" in the table "Tour" with an automated formula where it counts how many times the t_id appears in the "Client" table?
Something like: COUNT(c_id) FROM clients WHERE reference = t_id
I have read about to create a view but not sure how to fetch the data correctly.
Thanks for your help,
Raphael
UPDATE #1:
The workflow as described with the view works perfectly. I'm trying now to fill the column via a trigger but I'm getting an SQL error with the following code:
CREATE TRIGGER client_count
AFTER UPDATE
ON clients FOR EACH ROW
SELECT t.*,
(
SELECT COUNT(*) FROM clients c where c.tour_id = t.tour_id
) AS tours.tour_bookedspace
FROM tours t
The view you have referred to is indeed the way to go here. The view you need to create needs to join the two tables and perform a count aggregation as follows:
CREATE VIEW vwTour
AS
SELECT t.t_id,
t.name,
COUNT(t.name) AS Cnt
FROM tour t
JOIN Clients c
ON t.t_id = c.reference
GROUP BY t_id,
t.name
No you can't. Generated columns can only use data from the same table.
The options you have are:
1. Use a view
You can select from a view that computes the extra value(s) you want. For example:
create view tour_data as
select t.*,
(
select count(*) from clients c where c.reference = t.t_id
) as number_of_clients
from your t
2. Use a trigger
Alternatively, you can add the extra column number_of_clients and populate it using a trigger every time a row is added, modified, or deleted from the table clients.

MySql add relationships without creating dupes

I created a table (t_subject) like this
| id | description | enabled |
|----|-------------|---------|
| 1 | a | 1 |
| 2 | b | 1 |
| 3 | c | 1 |
And another table (t_place) like this
| id | description | enabled |
|----|-------------|---------|
| 1 | d | 1 |
| 2 | e | 1 |
| 3 | f | 1 |
Right now data from t_subject is used for each of t_place records, to show HTML dropdowns, with all the results from t_subject.
So I simply do
SELECT * FROM t_subject WHERE enabled = 1
Now just for one of t_place records, one record from t_subject should be hidden.
I don't want to simply delete it with javascript, since I want to be able to customize all of the dropdowns if anything changes.
So the first thing I though was to add a place_id column to t_subject.
But this means I have to duplicate all of t_subject records, I would have 3 of each, except one that would have 2.
Is there any way to avoid this??
I thought adding an id_exclusion column to t_subject so I could duplicate records only whenever a record is excluded from another id from t_place.
How bad would that be?? This way I would have no duplicates, so far.
Hope all of this makes sense.
While you only need to exclude one course, I would still recommend setting up a full 'place-course' association. You essentially have a many-to-many relationship, despite not explicitly linking your tables.
I would recommend an additional 'bridging' or 'associative entity' table to represent which courses are offered at which places. This new table would have two columns - one foreign key for the ID of t_subject, and one for the ID of t_place.
For example (t_place_course):
| place_id | course_id |
|----------|-----------|
| 1 | 1 |
| 1 | 2 |
| 1 | 3 |
| 2 | 1 |
| 2 | 2 |
| 2 | 3 |
| 3 | 1 |
| 3 | 3 |
As you can see in my example above, place 3 doesn't offer course 2.
From here, you can simply query all of the courses available for a place by querying the place_id:
SELECT * from t_place_course WHERE place_id = 3
The above will return both courses 1 and 3.
You can optionally use a JOIN to get the other information about the course or place, such as the description:
SELECT `t_course`.`description`
FROM `t_course`
INNER JOIN `t_place_course`
ON `t_course`.`id` = `t_place_course`.`course_id`
INNER JOIN `t_place`
ON `t_place`.`id` = `place_id`

How to occupy something (in this case a wardrobe slot) in MySQL

I'm developing a wardrobe application that uses a database table called "entrances".
The program is used to organize a normal wardrobe storage where the storage can have different amount of numbers/slots to hang clothes on. When a customer comes up to the merchant, the merchant scans the customer's bar code and will then get a free number from the system to hang the customer's clothes on. But there can of course only be one entry for each number.
My entrances db could look something like:
ID | wardrobeNo | storeID | customerBarcode | deliveredTime | collectedTime
---+------------+---------+-----------------+---------------+--------------
1 | 1 | 1 | XX | 20:12:55 | NULL
2 | 2 | 1 | XA | 20:44:44 | NULL
3 | 1 | 2 | XZ | 20:55:55 | NULL
4 | 2 | 2 | XC | 22:22:22 | NULL
Later that day the same entries do still exist in the DB but they will now have a collected time if the clothes have been collected from the wardrobe on some of the numbers before people went home.
ID | wardrobeNo | storeID | customerBarcode | deliveredTime | collectedTime
---+------------+---------+-----------------+---------------+--------------
1 | 1 | 1 | XX | 20:12:55 | 23:23:23
2 | 2 | 1 | XA | 20:44:44 | NULL
3 | 1 | 2 | XZ | 20:55:55 | 22:23:23
4 | 2 | 2 | XC | 22:22:22 | NULL
I will then be able to see the occupied numbers with:
SELECT * FROM db WHERE storeID = x AND delivered NOT NULL AND collected = NULL
What i'm wondering about is how I would be able to lock these 'wardrobeNo' while the merchant is handling payment, so another merchant does not make order on the same 'wardrobeNo'... just like a restaurant that would link orders to tables.
Is this even a good way to tackle the problem or is there something a lot smarter? Or should I consider thinking about this problem in another way.
Hope it makes sense..
Updated: Instead of taking care of maintaining a sequence yourself, use MySQL's auto_increment in combination with a scheduled alter table command at midnight:
CREATE TABLE idTable (
idKey INT(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (idKey)
)
And at midnight:
TRUNCATE TABLE idTable;
ALTER TABLE idTable AUTO_INCREMENT = 1;
Then simply add a new record to idTable prior to adding a row to your wardrobe table and use the inserted ID (via mysql_insert_id()) to get a daily unique ID.

Joining from another table multiple times in a MySQL query

I am trying to do multiple joins on the same MySQL table, but am not getting the results that I expect to get. Hopefully someone can point out my mistake(s).
Table 1 - cpe Table
|id | name
|----------
| 1 | cat
| 2 | dog
| 3 | mouse
| 4 | snake
-----------
Table 2 - AutoSelect
|id | name | cpe1_id | cpe2_id | cpe3_id |
|-----------------------------------------------
| 1 | user1 | 1 | 3 | 4 |
| 2 | user2 | 3 | 1 | 2 |
| 3 | user3 | 3 | 3 | 2 |
| 4 | user4 | 4 | 2 | 1 |
------------------------------------------------
I would like to see an output of
user1 | cat | mouse | snake |
user2 | mouse | snake | dog |
..etc
Here is what I have tried
SELECT * FROM AutoSelect
LEFT JOIN cpe ON
( cpe.id = AutoSelect.cpe1_id ) AND
( cpe.id = AutoSelect.cpe2_id ) AND
( cpe.id = AutoSelect.cpe3_id )
I get blank results. I thought i knew how to do these joins, but apparently when I'm trying to match cpe?_id with the name of the cpe table.
Thanks in advance for any assistance.
You need left join 3 times as well. Currently your query only joins 1 time with 3 critieria as to the join. This should do:
SELECT a.name, cpe1.name, cpe2.name, cpe3.name FROM AutoSelect as a
LEFT JOIN cpe as cpe1 ON ( cpe1.id = a.cpe1_id )
LEFT JOIN cpe as cpe2 ON ( cpe2.id = a.cpe2_id )
LEFT JOIN cpe as cpe3 ON ( cpe3.id = a.cpe3_id )
And you probably mean to INNER JOIN rather than LEFT JOIN unless NULL values are allowed in your AutoSelect table.
I think your design is wrong.
With tables like that, you get it the way it's meant to be in relational databases :
table 1 : animal
id name
1 cat
2 dog
3 mouse
4 snake
table 2 : user
|id | name |
|--------------
| 1 | user1 |
| 2 | user2 |
| 3 | user3 |
| 4 | user4 |
table 3 : association
|id_user | id_animal|
|--------------------
| 1 | 1 |
| 1 | 3 |
| 1 | 4 |
| 2 | 3 |
| 2 | 1 |
| 2 | 2 |
| 3 | 3 |
| 3 | 2
| 4 | 4 |
| 4 | 2 |
| 4 | 1 |
---------------------
Then :
select u.name, a.name from user u, animal a, association ass where ass.user_id = u.id and ass.animal_id = a.id;
In this case, your solution won't produce a good dynamic database. There are other ways to make combinations of multiple tables. I can show you by my own database what you should use and when you should use this solution. The scheme is in dutch, but you'll probably understand the keywords.
Like you, I had to combine my windmills with a kWh-meter, which has to measure the energyproduction of my windmills. What you should do, is this case, is making another table(in my case molenkWhlink). Make sure your tables are INNODB-types(for making Foreign keys). What I've done is combining my meters and mills by putting a pointer(a foreign key) of their ID(in Dutch Volgnummer) in the new table. An advantage you may not need, but I certainly did, is the fact I was able to extend the extra table with connection and disconnection info like Timestamps and metervalues when linking or unlinking. This makes your database way more dynamic.
In my case, I Also had a table for meassurements(metingoverzicht). As you can see in the scheme, I've got 2 lines going from Metingoverzicht to molenkwhlink. The reason for this is quite simple. All meassurements I take, will be saved in table Metingoverzicht. Daily meassurements(which are scheduled) will have a special boolean put on, but unscheduled meassurements, will also me saved here, with the bollean turned off. When switching meters, I need the endvalue from the leaving meter and the startvalue from the new meter, to calculate the value of todays eneryproduction. This is where your solution comes in and an extra table won't work. Usually, when you need just one value from another table a JOIN will be used. The problem in this case is, I've got 2 meassurementIDs in 1 link(1 for connecting and 1 for disconnecting). They both point to the same tablecolumn, because they both need to hold the same type of information. That is when you can use a double JOIN from one table towards the other. Because, both values will only be used once, and just needed to be saved in a different place to avoid having 1 action stored on different locations, which should always be avoided.
http://s1101.photobucket.com/user/Manuel_Barcelona/media/schemedatabase.jpg.html