MYSQL select statement with non unique primary key - mysql

Cars
|Vin |Make |Year|
| 1 |Honda|2009|
| 2 |Honda|2010|
| 3 |Honda|2009|
| 4 |Toyota|2009|
Colors
|Color|Make |Vin|
| Red |Honda|1|
|Blue |Toyota|4|
|Pink |Honda|2|
Financer
|Bank|Make |
| BOA |Honda|
| Cha |Toyota|
| PNC |Jeep|
A query
SELECT
car.Vin,
colors.Color,
financer.Bank
from Car
JOIN Colors ON car.vin = colors.vin
AND car.make = colors.make
JOIN financer ON car.make = financer.make
WHERE car.make = 'Honda'
Trying to join these tables but getting more rows than expected. My financer table above doesn't have a column that's unique.
Is there a way that I can do an ALTER TABLE to make a primary key unique by combining 2 columns?

Let's take a step back from the RDBMS details, and consider what it means that your financer table has no unique column. What does that mean in the world of your application?
It might mean that a particular Make has more than one Bank that gives loans, in which case there's some kind of choice, or alternative, embedded in that table.
It might mean there are completely duplicated rows.
Here are examples of both circumstances.
|Bank |Make |
| BOA |Honda |
| TD |Honda | 1. more than one bank finances Honda
| Cha |Toyota |
| PNC |Jeep |
| PNC |Jeep | 2. entirely duplicate row
This version of the financer table generates this result set (http://sqlfiddle.com/#!9/27705/2/0). It has, as you mention plenty of duplicates
| Vin | Color | Bank |
|-----|-------|------|
| 1 | Red | BOA |
| 2 | Pink | BOA |
| 1 | Red | TD |
| 2 | Pink | TD |
| 4 | Blue | Cha |
You can prevent the completely duplicated rows by creating a composite primary key, consisting of both columns. That probably makes sense in your application's world.
If you want a 1::1 relationship Make::Bank, you can put a primary key on Make. But I suspect that won't model your application's world correctly. (http://sqlfiddle.com/#!9/27705/5/0)
Maybe you want this query:
SELECT cars.Vin, cars.Make,
GROUP_CONCAT(DISTINCT colors.Color) Colors,
GROUP_CONCAT(DISTINCT financer.Bank) Banks
from Cars
JOIN Colors ON cars.vin = colors.vin
AND cars.make = colors.make
JOIN financer ON cars.make = financer.make
GROUP BY cars.Make, cars.vin
This combination of GROUP_CONCAT() and GROUP BY yields a list of matching colors, and a list of matching banks, for each car.
| Vin | Make | Colors | Banks |
|-----|--------|--------|--------|
| 1 | Honda | Red | TD,BOA |
| 2 | Honda | Pink | TD,BOA |
| 4 | Toyota | Blue | Cha |
As you can see, each of the Honda cars has a list of two banks.

Related

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`

One to one relationship with foreign key to mutiple tables

Please let me start by saying, I know this has been asked many times before and I've studied other questions (and answers) but after 2 days of reading questions and amending my database I can't get this to work as I want.
At the moment I have various tables, for example customer, supplier, product, banner, etc.
I have a table called custom_field which allows custom fields to be created and used against various other tables.
At the moment some of my tables look like this:
General Tables
==============
Customer
+-------------+---------------+
| customer_id | customer_name |
+-------------+---------------+
| 1 | Peter |
| 2 | Sally |
+-------------+---------------+
Banner
+-----------+-------------+--------------+
| banner_id | banner_name | banner_width |
+-----------+-------------+--------------+
| 1 | Easter | 100px |
| 2 | Xmas | 250px |
+-----------+-------------+--------------+
Tables for managing custom fields
=================================
Custom_Field
+----------+------------+----------------+-----------+
| field_id | field_name | field_label | item_type |
+----------+------------+----------------+-----------+
| 100 | fav_color | Favorite Color | customer |
| 101 | border | Border | banner |
+----------+------------+----------------+-----------+
Custom_Field_Value
+----------+----------+---------+-------------+
| value_id | field_id | item_id | field_value |
+----------+----------+---------+-------------+
| 1567 | 100 | 1 | Red |
| 1568 | 100 | 2 | Blue |
| 1569 | 101 | 1 | Solid |
| 1570 | 101 | 2 | Dotted |
+----------+----------+---------+-------------+
To clarify, item_id refers to a customer_id, a banner_id, or a supplier_id, etc. In the example above this means Peter has a "favorite color" custom field set to Red, and Sally has a "favorite color" custom field set to Blue.
The Easter Banner has a "border" custom field set to solid, and the Xmas Banner has a "border" custom field set to Dotted.
This all works fine, except there can be no foreign key or referential integrity set between Custom_field_value.item_id and Customer.customer_id (or Banner.banner_id) because item_id's context is described by the item_type field in the Custom_Field table.
I don't want to create multiple nullable foreign keys (not sure that would even work anyway) as it will become unmanageable.
I did try creating sub tables, for example customer_custom_field, and relate this between Customer and Custom_Field, but again it becomes unmanageable when you consider every table could potentially have custom fields.
A single field value would only ever apply to a single entity from another table.
As an aside I also want to create an Attachments table for managing uploaded attachments to a particular entity, and again that could apply to customers, suppliers, products and various other tables, so it's a similar issue.

Rows as columns with lookup table

I have two tables that look like this:
Table cars
+--------+-----------+-------+---------+
| car_id | attribute | value | brand |
+--------+-----------+-------+---------+
| 1 | colore | rosso | Ferrari |
| 1 | prezzo | 100 | Ferrari |
| 2 | couleur | bleu | Renault |
| 2 | prix | 50 | Renault |
| 3 | colore | blu | Ferrari |
| 3 | prezzo | 100 | Ferrari |
+--------+-----------+-------+---------+
Table translations
+--------------------+----------------+---------+----------------------+------------------+
| original_attribute | original_value | brand | translated_attribute | translated_value |
+--------------------+----------------+---------+----------------------+------------------+
| colore | rosso | Ferrari | color | red |
| prezzo | 100 | Ferrari | price | 100 |
| colore | blu | Ferrari | color | blue |
| couleur | bleu | Renault | color | blue |
| prix | 50 | Renault | price | 50 |
+--------------------+----------------+---------+----------------------+------------------+
I am trying to get to a table that looks like this:
+-------------------+-------+-------------+--------------------+
| translated_car_id | color | price | translated_brand |
+-------------------+-------+-------------+--------------------+
| 1 | red | 100 | Ferrari |
| 2 | blue | 50 | Renault |
| 3 | blue | 100 | Ferrari |
+-------------------+-------+-------------+--------------------+
At the moment, I am using below code. It works, but is excruciatingly slow.
SELECT
car_id translated_car_id,
MAX(CASE
WHEN translations.translated_attribute = 'color' THEN translations.translated_value
END) color,
MAX(CASE
WHEN translations.translated_attribute = 'price' THEN translations.translated_value
END) price,
brand translated_brand
FROM
cars c
INNER JOIN
translations ON (c.attribute = translations.original_attribute
AND c.brand = translations.brand
AND c.value = relations.original_value)
GROUP BY c.car_id
Anybody have an idea on how to make the query or the structure more efficient? Would really appreciate it.
Thanks in advance
From what I can find MySql doesn't have hash matches, which would be useful here. So I'm assuming that everything is being done as nested loop joins.
My concern is that because there isn't an index on translations, for every row in cars it has to scan the translations table to find the matching rows.
I would recommend a clustered index on translations(original_value, brand, original_attribute). (ms-sql does best with most specific first everything else being equal, not sure about MySql) That way it can go right to the row that it needs to match to. That should allow queries with one car to be done quickly.
If you can reduce the translations from manufacturer to language that would certainly help with the size of the translation table, but you would have to be sure that it works with your dataset because it does take away a level of flexibility.
I think that MySql will be able to use the index on cars to process the GROUP BY efficiently, but I will propose a more normalized schema where you don't need the group by.
car
-------------
car_id
brand
car_attribute
------------
car_id
attribute
value
clustered_index(car_id, attribute)
brand_attribute
------------
brand_attribute_id
brand
attribute
translated_attribute
clustered_index(brand, translated_attribute)
brand_attribute_value
------------
brand_attribute_id
value
translated_value
clustered_index(brand_attribute_id, value)
And here's the query to do it that way.
SELECT
car.car_id,
color_brand_value.translated_value AS color,
price_brand_value.translated_value AS price,
car.brand
FROM
car
INNER JOIN brand_attribute AS color_brand
ON color_brand.brand = car.brand
AND color_brand.translated_attribute = 'color'
INNER JOIN car_attribute AS color_attribute
ON color_attribute.car_id = car.car_id
AND color_attribute.attribute = brand.attribute
INNER JOIN brand_attribute_value AS color_brand_value
ON color_brand_value.brand_attribute_id = color_brand.brand_attribute_id
AND color_brand_value.value = color_attribute.value
INNER JOIN brand_attribute AS price_brand
ON price_brand.brand = car.brand
AND price_brand.translated_attribute = 'price'
INNER JOIN car_attribute AS price_attribute
ON price_attribute.car_id = car.car_id
AND price_attribute.attribute = brand.attribute
INNER JOIN brand_attribute_value AS price_brand_value
ON price_brand_value.brand_attribute_id = price_brand.brand_attribute_id
AND price_brand_value.value = price_attribute.value
It's definitely more complicated to do it this way. With your situation I'm not sure it's better, but something to consider if the first option isn't good enough.
My background is in mssql, so there may be differences that I'm not aware of. Please leave comments if I've missed or got something wrong.

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

Use MySQL to SELECT attributes similar to Amazon's SimpleDB

Sorry I couldn't think of a better way to title this. In Amazon's SimpleDB, an item can have multiple values in the same column, so it's possible to select only those items that have all of the attributes being sought.
In MySQL, let's say the following table ("Photo_Attributes") contains an unlimited number of attributes for photographs that are contained in another table ("Photos"), and that the two tables are joined by Item_Number.
And, let's say I wanted to find a hat whose color was red and size was medium, which in this case would be ITEM_ID "ABC" and not "OPQ".
+-----+----------+--------+-----------+-------+
| ID | Item_ID | Object | Attribute | Value |
+-----+----------+--------+-----------+-------+
| 1 | ABC | Hat | Color | Red |
+-----+----------+--------+-----------+-------+
| 2 | FGH | Pants | Color | Blue |
+-----+----------+--------+-----------+-------+
| 3 | FGH | Pants | Size | Large |
+-----+----------+--------+-----------+-------+
| 4 | LMN | Shirt | Color | Red |
+-----+----------+--------+-----------+-------+
| 5 | ABC | Hat | Size | Med |
+-----+----------+--------+-----------+-------+
| 6 | LMN | Shirt | Size | Med |
+-----+----------+--------+-----------+-------+
| 7 | OPQ | Hat | Color | White |
+-----+----------+--------+-----------+-------+
| 8 | OPQ | Hat | Size | Med |
+-----+----------+--------+-----------+-------+
The following query would yield no results because each row contains only one Attribute and one Value.
SELECT FROM Photo_Attributes WHERE OBJECT='hat' AND (Attribute='Color" AND
Value='Red") AND (Attribute='Size' AND Value='Med');
And, this query would produce more rows than it should (i.e., all red and all medium-sized items).
SELECT FROM Photo_Attributes WHERE OBJECT='hat' AND (Attribute='Color" AND
Value='Red") OR (Attribute='Size' AND Value='Med');
What's the best way to write this -and- is there a way to do it without using JOIN in the SELECT statement? I'm wondering the latter because the query would be programmatically generated (in nodejs) and the number of Attribute-Value pairs could range from one to several. I figure I could also use nested queries, culling from recordset, but that seems equally inefficient.
SELECT pa1.Item_ID
FROM Photo_Attributes pa1
INNER JOIN Photo_Attributes pa2
ON pa1.Item_ID = pa2.Item_ID
AND pa2.Attribute = 'Size'
AND pa2.Value = 'Med'
WHERE pa1.Object = 'Hat'
AND pa1.Attribute = 'Color'
AND pa1.value = 'Red'
Assuming no overlap in your name/value pairs (e.g., You'd never have Size/Red or Color/Med), you could probably also do something like this.
SELECT pa.Item_ID
FROM Photo_Attributes pa
WHERE pa.Object = 'Hat'
AND pa.Attribute IN ('Size', 'Color')
AND pa.Value IN ('Med', 'Red')
GROUP BY pa.Item_ID
HAVING COUNT(DISTINCT Value) = 2