database layout issue - mysql

i will try to explain the problem.
I have this structure:
offers
--------------
id_offer|offer|company
1 | web programmer| Microsoft
2 | web programmer| Microsoft
tags
--------------
id_tags | tags
1 | php
2 | js
3 | asp
offers_has_tags
---------------
offers_id_offer (fk) | tags_id_tags (fk)
1 | 1
1 | 2
1 | 3
2 | 1
2 | 2
If i use a system like Stackoverflow, where each title of question is unique, there is no problem with the code below. But if i can have various offers with same title, and with same owner, i can't do WHERE offers = ?,
So, i need a different approach to select a specific job. Can't be the title, and can't be the owner+title, because the same owner can have various offers with same title.
INSERT INTO `offers_has_tags` (
offers_id_offer,
tags_id_tags
)
VALUES (
(SELECT id_offer FROM offers WHERE offer = ?), //here
(SELECT id_tags FROM tags WHERE tags = ?))
How can i select an offer when exists more than one, with same title and same owner ?

Simple answer: there is no way to retrieve exactly one row from table if your where clause is not filtering rows by PK columns.
It is not 100% clear what You are trying to achieve. However, primary key is used to uniquely identify the row. This means that in this case You should use offer_id in where clause (in your insert statement in original question).
I guess that you have some UI in front of this - why don't your UI send offer_id to data access code instead of offer name?
But, if You want to insert all offers with same name and owner to offer_has_tag, try this (it is T-SQL syntax but as far as I can recall it should work on MySQL also):
INSERT INTO `offers_has_tags` (
offers_id_offer,
tags_id_tags
)
SELECT id_offer, (SELECT id_tags FROM tags WHERE tags = ?)
FROM offers WHERE offer = ? AND company = ?)
Please note that You should use id_tags instead of tag name in your queries. Only use descriptive attributes in filtering the list for end user. Otherwise, use primary key columns.

Could you be a bit more specific? In which situation are you executing the sql, for which purpose and in which way are the arguments given? Maybe post the code that's in front of these statements.
That could helping answer your question. The thing I'm wondering at the moment, why is it a problem to get more than one case as result?

Related

ER_NON_UNIQ_ERROR and how to design tables correctly

I have come across this problem and I've tried to solve it few days now.
Let's say I have following tables
properties
-----------------------------------------
| id | address | building_material |
-----------------------------------------
| 1 | Street 1 | 1 |
-----------------------------------------
| 2 | Street 2 | 2 |
-----------------------------------------
building_materials
-----------------------------
| id | building_material |
-----------------------------
| 1 | Wood |
-----------------------------
| 2 | Stone |
-----------------------------
Now. I would like to provide an API where you could send a request and ask for every property that has building material of wood. Like this:
myapi.com/properties?building_material=Wood
So I would like to query database like this (I want to return the string value of building_material not the numeric value):
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE building_material = "Wood"
But this will give me an error
Column 'building_material' in where clause is ambiguous
Also if I want to get property with id of 1.
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE id = 1
Column 'id' in where clause is ambiguous
I understand that the error means that I have same column name in two tables and I don't specify which id I want like p.id.
Problem is I don't know how many query parametes API user is going to send and I would like to avoid looping through them and changing id to p.id and building_material to bm.building_material. Also I don't want that user has to send request to the API like this
myapi.com/properties?bm.building_material=Wood
I've thought about changing the properties table building_material to fk_building_material and changing properties table id to property_id.
I just don't like the idea that on client side I would then have to refer property's building material as fk_building_material. Is this a valid method to solve this problem or what would be the correct way of designing these tables?
The query mentions two tables, so all the columns in both tables are "on the table" for use anywhere in the query.
In one table building_material is an "id" for linking to the other table; in the other table, it is a string. While this is possible, it is confusing to the reader. And to the parser. To resolve the confusion, you must qualify building_material with which one you want; that is done with a table alias (or table) in front (as you did in all other places).
There are two ids are all ambiguous. But this is the "convention" used by table designers. So, it is OK for an id in one table to be different than the id in the other table. (p.id refers to one thing in one table; bm.id refers to another in another table.)
SELECT p.id, p.address, bm.building_material
FROM properties as p
JOIN building_materials as bm ON (p.building_material = bm.id)
WHERE bm.building_material = "Wood" -- Note "bm."

Is there a way to display INNER JOIN entries in the order they're entered in the clause?

I'm a beginner when it comes to MySQL and I've taken it upon myself to create a type of translating service like google translate. The problem is the querys are not being displayed the way I enter them, instead they seem to be ordered by the ID column.
I've tried (with my limited knowledge) looking into different ways of creating relations etc. to display the equivelent words in the different languages. For now I've landed on trying to use the INNER JOIN clause to display and "structure" the sentences.
SELECT swedish.word,
german.word,
german.swear,
swedish.swear,
swedish.id
FROM swedish
INNER JOIN german
ON swedish.id = german.id
WHERE swedish.word = "Hej"
OR swedish.word = "Mitt"
OR swedish.word = "Namn"
OR swedish.word = "Är";
This will display the swedish words alongside the german words, aka create sentences but it will now diplay in the order i typed them in, instead it will sort in by the ID column, which mixes the words around. Is there any solution to this?
Here's and image of the results, ordered by the ID:
I've thought about using ORDER BY and some sort of temporary value and then order it by that but then I'm not sure about how to implement and auto increment that value for only the selected entries/rows.
I'm using OR statements to enable more than one entry in the same result, as parentheses (seen in other tutorials) gave me syntax errors.
Also, if there is a better way of going about this please let me know!
EDIT: I would want to clarify that I am aware that this is not a sustainable solution for creating a transaltion service, I simply thought this would be an interesting way to understand a bit more about how you can connect and work with different tables etc.
You can use FIND_IN_SET
ORDER BY FIND_IN_SET(swedish.word, 'Hej,Mitt,Namn,Är');
I would suggest a subquery with prioritization:
SELECT s.word, g.word, g.swear, s.swear, s.id
FROM swedish s JOIN
(SELECT 'Hej' as word, 1 as ord UNION ALL
SELECT 'Mitt' as word, 2 as ord UNION ALL
SELECT 'Namn' as word, 3 as ord UNION ALL
SELECT 'Är' as word, 4 as ord
) w
ON s.word = w.word JOIN
german g
ON s.id = g.id
ORDER BY w.ord;
The advantage of this approach over other approaches is that the list of words is only included once. This makes it easier to update and prevents errors when writing thew query.
Also, if there is a better way of going about this please let me know!
It isn’t the databases job to do this, it’s the front end’s job
If you have the sentence;
Hej Mitt Namn Ar Caius
Then the front end should do something like this (pseudocode):
string newsentence = “”
foreach(word in sentence.split(‘ ‘))
newsentence = newsentence + “ “ + dblookup(word)
(You can assume dblookup is a helper method that takes a single [swedish] word and returns the equivalent single [german] word)
The order is preserved because you perform database lookups in order as you traverse the sentence. You don’t try to send all the words to the db, and force order the results so you can just concat them back into a sentence, you look up one word at a time. If you have the same word twice in a sentence, all the approaches here (in other answers - at the time of writing this answer) will break; a sentence of “hej mitt hej” will come back ordered as “hallo hallo meine” because you can’t ask the db to order hej as both first and third, all the “hej” will order to be first
There isn’t much to be gained by submitting multiple words for translation, some minor performance benefit maybe but it would be trivial. If you were engineering this solution for performance you could have your dblookup method cache a few hundred thousand most recently requested words, but don’t bang your head on the wall of trying to submit an entire sentence to the db in “or or or” style and preserving the order; it’s so complicated to do so and for no practical benefit
As a brief aside, this isn’t how languages work either, though I appreciate that this is the very early stages and you may just be indertaking this for a learning exercise - you cannot make a translator software by literally translating each word by word individually
Not sure if that's what you mean, but try this way:
SELECT swedish.word,
german.word,
german.swear,
swedish.swear,
swedish.id
FROM swedish
INNER JOIN german
ON swedish.id = german.id
WHERE swedish.word = "Hej"
OR swedish.word = "Mitt"
OR swedish.word = "Namn"
OR swedish.word = "Är"
ORDER BY field(swedish.word,"Hej","Mitt","Namn","Är");
You can provide your way to sort the rows with CASE in ORDER BY:
SELECT
swedish.word, german.word, german.swear, swedish.swear, swedish.id
FROM swedish INNER JOIN german
ON swedish.id = german.id
WHERE swedish.word IN ('Hej', 'Mitt', 'Namn', 'Är')
ORDER BY CASE swedish.word
WHEN 'Hej' THEN 1
WHEN 'Mitt' THEN 2
WHEN 'Namn' THEN 3
WHEN 'Är' THEN 4
END
There are some fundamental problems with your question:
I've taken it upon myself to create a type of translating service like google translate.
Automated translation is hard, and can't be solved just by word-to-word database lookups (especially if you're "a beginner when it comes to MySQL"). Languages have lots of complicated grammar rules, and you can't translate a sentence just by translating it word-for-word. Take a look at this article: You'd really have to get into something like machine learning, rather than (just) database development.
If you want experience with automated translation, you might want to take a look at the Google Translate API.
Another problem is that you seem to have a separate table/entity for each language. This is problematic: As the number of languages grows, the number of tables will increase. To translate from Language A to Language B, you'll have to know which tables to use, which will likely involve dynamic SQL. It would be far better to properly normalize your data. Something like this:
CREATE TABLE Words
(
word_id INT PRIMARY KEY,
universal_name VARCHAR(255) -- the "universal" way to refer to a word (e.g. you could store the Esperanto version).
);
INSERT INTO Words
(word_id, universal_name)
VALUES
(1, 'hello');
CREATE TABLE word_translations
(
word_id INT NOT NULL FOREIGN KEY REFERENCES Words(word_id),
language VARCHAR(255) NOT NULL,
word_name VARCHAR(255) NOT NULL
);
INSERT INTO word_translations
(word_id, language, word_name)
VALUES
(1, 'en', 'hello'),
(1, 'es', 'hola');
But again, this won't really solve the problem of translation, since word-for-word translations aren't sufficient.
Same answer as Gordon Linoff, just automating the numbering of the order of filter list:
CREATE TABLE tbl
(fruit varchar(10), sweetness int)
;
INSERT INTO tbl
(fruit, sweetness)
VALUES
('apple', 7),
('banana', 6),
('papaya', 4),
('grape', 2),
('watermelon', 3)
;
Query, just including Postgres for its expressiveness :)
Live test: http://sqlfiddle.com/#!17/3fa48/3
with a as
(
select *
from unnest(array['banana','grape','apple','banana'])
with ordinality as x(f,i)
)
select tbl.*,'',a.i
from tbl
join a on tbl.fruit = a.f
order by a.i;
Output:
| fruit | sweetness | ?column? | i |
|--------|-----------|----------|---|
| banana | 6 | | 1 |
| grape | 2 | | 2 |
| apple | 7 | | 3 |
| banana | 6 | | 4 |
Query for MySQL:
Live test: http://sqlfiddle.com/#!9/86b0d9/4
select tbl.*, '', a.i
from tbl
join (
select #i := #i + 1 as i, x.f
from
(
select 'banana' as f
union all
select 'grape'
union all
select 'apple'
union all
select 'banana'
) as x
cross join (select #i := 0) y
) a on tbl.fruit = a.f
order by a.i;
Output:
| fruit | sweetness | | i |
|--------|-----------|--|---|
| banana | 6 | | 1 |
| grape | 2 | | 2 |
| apple | 7 | | 3 |
| banana | 6 | | 4 |

In MySQL, How do you dynamically SELECT the values of one table, as columns in another?

I'm no MySQL guru, but I get around the basic stuff pretty well. Thanks for your feedback.
I have two tables user and favorite. Each user can have multiple unique favorites.
table user u
[ user_id + name ]
100 | Sally
table favorite fav
[ fav_id + user_id + fav_key + fav_val ]
1 | 100 | icecream | mint
2 | 100 | candybar | snickers
3 | 100 | color | red
I want to create a SELECT statement that will turn the user's favorites into columns using the fav_key value as the column header. *The problem is I will never know what the fav_val value will be as these are user entered, so the column names have to be generated dynamically.
SELECT ...
[ user_id + name + fav_icecream + fav_candybar + fav_color ]
100 | Sally | mint | snickers | red
With some distant thought of performance in mind -- one of the issues is that I don't want to run two SELECT statements to get the user data and the user favorites (plus I like the idea of having the columns dynamically named in this way).
UPDATE
So this is called, pivoting, excellent.
What if I don't ever know what the values are? I need to dynamically generate the columns from a query on favorites.
Like this:
Select fav.user_id, u.name
MAX(case WHEN fav.fav_key = 'icecream' then fav.fav_val end) as 'fav_icecream',
MAX(case WHEN fav.fav_key = 'candybar' then fav.fav_val end) as 'fav_candybar',
MAX(case WHEN fav.fav_key = 'color' then fav.fav_val end) as 'fav_color'
From favorite fav
inner join users u on fav.user_id = u.user_id
group by fav.user_id
Working Example: DEMO
Note that: the demo is for MS SQL Server but it is working the same way for mysql, I just tried to give you a live demo.
Essentially you want PIVOT functionality. Unfortunately, that's not natively supported by MySQL (unlike Oracle or SQL Server). There are many questions on StackOverflow showing how to work around that lacking functionality of MySQL:
https://stackoverflow.com/search?q=mysql+pivot+sql
Some examples:
pivot in mysql queries
MySQL Query and Pivot Tables

Mysql: how to structure this data and search it

I'm new to mysql. Right now, I have this kind of structure in mysql database:
| keyID | Param | Value
| 123 | Location | Canada
| 123 | Cost | 34
| 123 | TransportMethod | Boat
...
...
I have probably like 20 params with unique values for each Key ID. I want to be able to search in mysql given the 20 params with each of the values and figure out which keyID.
Firstly, how should I even restructure mysql database? Should I have 20 param columns + keyID?
Secondly, (relates to first question), how would I do the query to find the keyID?
If your params are identical across different keys (or all params are a subset of some set of params that the objects may have), you should structure the database so that each column is a param, and the row corresponds to one KeyID and the values of its params.
|keyID|Location|Cost|TransportMethod|...|...
|123 |Canada |34 |Boat ...
|124 | ...
...
Then to query for the keyID you would use a SELECT, FROM, and WHERE statement, such as,
SELECT keyID
FROM key_table
WHERE Location='Canada'
AND Cost=34
AND TransportMethod='Boat'
...
for more info see http://www.w3schools.com/php/php_mysql_where.asp
edit: if your params change across different objects (keyIDs) this will require a different approach I think
The design you show is called Entity-Attribute-Value. It breaks many rules of relational database design, and it's very hard to use with SQL.
In a relational database, you should have a separate column for each attribute type.
CREATE TABLE MyTable (
keyID SERIAL PRIMARY KEY,
Location VARCHAR(20),
Cost NUMERIC(9,2),
TransportMethod VARCHAR(10)
);
I agree that Nick's answer is probably best, but if you really want to keep your key/value format, you could accomplish what you want with a view (this is in PostgreSQL syntax, because that's what I'm familiar with, but the concept is the same for MySQL):
CREATE OR REPLACE VIEW myview AS
SELECT keyID,
MAX(CASE WHEN Param = 'Location' THEN Value END) AS Location,
MAX(CASE WHEN Param = 'Cost' THEN Value END) AS Cost,
....
FROM mytable;
Performance here is likely to be dismal, but if your queries are not frequent, it could get the job done.

Counting instances in table

I've got this tag system for tagging blog entries and such. The tags are in one table, containing only a tag name and a primary key. Then I have another table with objects that are using the tags.
It could look something like this:
_________________________________
| tags |
--------------------------------|
| id | name |
|-------------------------------|
| 1 | Scuba diving |
| 2 | Dancing |
---------------------------------
_________________________________
| tag_objects |
--------------------------------|
| id | tag | object |
|-------------------------------|
| 1 | 2 | 13 |
| 2 | 2 | 18 |
| 3 | 1 | 24 |
---------------------------------
Now, what I need to accomplish is to to add a column to the tags table, called "occurrences" or something. For each tag in tags, occurrences should be set to the number of times that tag is used in tag_objects.
So basically something like (obviously pseudo-code):
foreach(tags):
UPDATE tags
SET occurrences = (SELECT COUNT(id)
FROM tag_objects
WHERE tag = tags.id);
When people create new posts and stuff in the future, I'll just have a trigger to update the count, but I have a couple of thousand rows already that I need to count first. I don't know how to do this, so any assistance would be appreciated.
The easiest way to do this, without any extra tables, would be:
First add the extra field:
mysql> alter table tags add occurs int
default 0;
Then just update this new field with the number of occurences.
mysql> update tags left join (select tag,
count(id) as cnt from tag_objects
group by tag) as subq on
tags.id=subq.tag set
occurs=coalesce(subq.cnt,0);
Note the use of the left join to ensure all tags are counted, even the unused ones. The coalesce-function will convert NULL to 0.
You have done a good work, your query must work.
But, this will result in awful performance. I advise you to recreate a table :
CREATE TABLE newTags AS
SELECT t.id, t.name, COUNT(*) AS occurrences
FROM tags t
INNER JOIN tag_objects to
ON to.tag = tags.id
GROUP BY t.id, t.name
This will be very fast.
Unless you really need to denormalize your data, you should stay away from that. Counting on indexed columns is usually very fast. I am a big fan of clean and normalized data ;-)
I would generally not want to store computed values in columns on the database - it's messy, can easily get out of sync, and offends the deities of normalization.
However, if you really must have a database entity with the count, rather than calculating on the fly, I'd create a view (http://dev.mysql.com/doc/refman/5.0/en/create-view.html) which stores the pre-computed value, using the SQL provided by Scorpio
CREATE view tag_occurences AS
SELECT t.id, t.name,
COUNT(*) AS occurrences
FROM tags t
INNER JOIN tag_objects to
ON to.tag = tags.id
GROUP BY t.id, t.name
I think you will gain better performance if you will be incrementing and decrementing the value of occurrences on table tag_objects insert/delete trigger.
Your psuedeo code will work exactly as written (without the foreach loop). At least it would in oracle, I assume MySQL lets you use a correlated subquery as the value too.
For the inserting of new rows you could use a query like:
INSERT INTO tags VALUES(x,y,z,1) ON DUPLICATE KEY UPDATE occurrences = occurrences+1;
I didn't check the syntax, but something like that.