Say if we had:
tbl `posts`
id | post_type | post_id | post_key | post_value | Locale
------------------------------------------------------------------------------
1 | news | 1 | title | The climate in Mongolia | pl_pl
2 | news | 1 | content | Mongolia has strange weather... | pl_pl
3 | news | 1 | date | 2015-9-24 | pl_pl
In order to get data for a post I would
SELECT post_key, post_value
FROM posts
WHERE post_id = 1 && post_type = 'news'
AND locale = 'pl_pl'
Then loop through the result set and mysqli_fetch_assoc() each row to give me an associative array with field names as the array keys;
$row['post_key'] == 'title'; $row['post_value'] == 'The climate in Mongolia';
$row['post_key'] == 'content'; $row['post_value'] == 'Mongolia has strange weather...';
What I would like is the actual values of post_key to be the array keys, keeping the value as post_value. So:
$row['title'] == 'The climate in Mongolia';
$row['content'] == 'Mongolia has strange weather...';
Yes I could format it like this with php and another loop, like:
$data = [];
foreach($rows as $r) {
$data[$r['post_key']] = $r['post_value'];
}
But I want to know if there is some mysql magic that could save the additional processing required in PHP?
Further info about this app: As Gal rightly pointed out, it seems strange that I wouldn't just use the field names as per the post_key's / conventional table design. But this is a translations table and I am exploring the possibility of having one table to cater for translations of many other content tables, and that I can use for new content types without too much faffing around with creating multiple additional tables and maintaining new fields across them. Reasons that I am not to worried about performance at this stage: 1. being the only developer on this startup project and where decisions are being made on the fly, I believe this will save time and tick all boxes for now. 2, number of entries will be low compared to a couple of years time, where we would have time then to look a solution to any performance issue that arises.
You can use a select like this to get all the post_key->post_value in a single row:
SELECT
title,
MAX(content) AS content,
MAX(date) AS date
FROM (
SELECT
post_type,
post_id,
CASE
WHEN post_key = 'title' THEN
post_value
END AS title,
CASE
WHEN post_key = 'content' THEN
post_value
END AS content,
CASE
WHEN post_key = 'date' THEN
post_value
END AS date
FROM
posts
WHERE
post_type = 'news' AND
post_id = 1
) AS a
GROUP BY
post_id
http://sqlfiddle.com/#!9/04684/1
UPDATE:
You can do something like i exposed but using the column value as a column name like this questions:
mysql select dynamic row values as column names, another column as value
MySQL select specific row values as column names
Then call a procedure like this:
How to call a MySQL stored procedure from within PHP code?
Hope it helps you!
You could. But as Gal stated, it's probably not the most efficient. If you value efficiency, I'd advice to just make meaningful columns.
Related
I have a games database and I need to create pairs of similar games in another table. So, let's say, game ID 5 and ID 12 need to be paired, which should be like this:
---------------------------
| PairID | First | Second |
---------------------------
| 1 | 5 | 12 |
---------------------------
So what I need basically is to join pairs and games tables and select and display data for all IDs of each pair depending on which game is being browsed. That is, if it's game ID 5 then ID 12 should be displayed and vice versa. Seems pretty trivial at first, except I discovered there is no neareast elegant solution to this, where the major problem is the order of IDs in the pairs table.
First of all, with this scheme I'm forced to use two separate joins of the 'games' table and select both games' data regardless like this:
SELECT simpairs.id, simpairs.first, simpairs.second,
games_one.id AS first_id, games_two.id AS sec_id,
games_one.title AS first_title, games_two.title AS sec_title,
games_one.year AS first_year, games_two.year AS sec_year
FROM simpairs
LEFT JOIN games AS games_one ON simpairs.first = games_one.id
LEFT JOIN games AS games_two ON simpairs.second = games_two.id
WHERE simpairs.first = <id> OR simpairs.second = <id>
Secondly, I need the script to pick out and display the correct data like this:
$id = $_GET['id'];
while($row = mysqli_fetch_assoc($res)) {
if($row['first_id'] != $id) {
$game_id = $row['first_id'];
$title = $row['first_title'];
$year = $row['first_year'];
} else {
$game_id = $row['sec_id'];
$title = $row['sec_title'];
$year = $row['sec_year'];
}
...
}
Ok, so this is already pretty messy. Still, the biggest issue is sorting the output alphabetically. Obviously, it can't be easily done on the SQL side unless the query is somehow rebuilt for which there is no apparent route as it must include an equivalent for PHP condition, or the pairs table should be modified to contain a mirrored pair for each new entry like this:
---------------------------
| PairID | First | Second |
---------------------------
| 1 | 5 | 12 |
---------------------------
| 2 | 12 | 5 |
---------------------------
This would work of course and I would only need 1 join instead of 2, which would also make sorting easier directly on the SQL side, yet this also doesn't seem very elegant as I would have to perform two inserts for each new pair instead of just 1 plus clutter the database with mirrored entries making it exactly 2 times larger. I mean, if this is the right way then I guess I'm fine with that.
Sorting data on the script side is no less daunting. Typically, arrays are used for the occasion, except this time around I don't see how it can be achieved effectively. I will need to store ID, game title and year of release somehow and then sort everything based on titles alone. Meaning, I should be probably using 2 dimensional arrays where titles act as parent keys for sub-arrays containing IDs and years, then I should somehow sort only the parent keys without affecting the sub-arrays and I really don't know how to do that nor if it's worth it at all. All in all, seems like unnecessary strain to both the database and the script.
So what would be the optimal solution here?
You have an id that relates to a game in the games table.
You have a table that links other game id's to the main game id
You want to show a list of those linked games and related info.
Select lg.*
FROM `games` g
LEFT JOIN `simpairs` s
ON s.first = g.id OR s.second = g.id
JOIN `games` lg // linked games
on lg.id = case when s.first = g.id then s.second else s.first end
where g.id = ?
Let me know if you have any questions, it's pretty self explanatory. Though it does assume simpairs never has reverse duplicate values in first and second otherwise you might need to filter those out.
I'm trying to make something similar to "related articles". This is what I have.
$query = "SELECT * FROM posts WHERE post_tags LIKE '%$post_tags%'";
I want it to 'select all from post where the post tags are similar to the current post's tags'.
Note: The tags look like this in the database: "tech, news, technology, iphone"
I looked into things like
$tags = explode(",", $post_tags );
But I'm not sure.
Use FullText search -- docs
SELECT * FROM `posts` WHERE MATCH(post_tags) AGAINST('$post_tags' IN BOOLEAN MODE)
Live demo
The query will require you to add a FULLTEXT index to your post_tags table column (unless you have the older MyISAM table). This query will be a lot faster than your current attempt.
query to add the index
ALTER TABLE `posts` ADD FULLTEXT INDEX `tag_search` (`post_tags`)
A better, faster approach
Change how you store the post-to-tag relationship in the DB. Your posts table should not be used to store tags because one post has many tags, but each post has only one record in the posts table. Instead, have a two other tables:
tags table
tag_id | name
1 | technology
2 | news
3 | hobbies
post_tags table
post_id | tag_id
1 | 1
1 | 3
2 | 1
Notice it's easy to tell that post_id #1 has the technology and hobbies tags. This will make your queries easier, and faster.
Even faster!
If you do want to store everything in the posts table but have even faster performance, you will need to store your tags as bit flags. For instance, if the following is true in your PHP application:
$techBit = 0b001; // number 1 in binary form
$newsBit = 0b010; // number 2 in binary form
$hobbiesBit = 0b100; // number 4 in binary form
Then it's easy to store tags in one field. A post that has technology and hobbies tag would have a value:
$tag = $techBit | $hobbiesBit; // 1 + 4 = 5
And if you wanted to search for all records with technology or hobbies, you would do:
// means: records where post_tags has either techBit or hobbiesBit turned ON
SELECT * FROM `posts` WHERE (`post_tags` & ($techBit | $hobbiesBit)) > 0
Well instead of "LIKE" you could use the "IN" clause.
$Results = join("','",$post_tags);
$SQLQuery = "SELECT * FROM galleries WHERE id IN ('$Results')";
Example: Passing an array to a query using a WHERE clause
I have a basic SQL problem that's been driving me mad. If I have a mySQL table e.g below.
How would I add another 80+ values to Column 2 starting from the first empty row (in this example row 3).
I've been trying a number of queries using INSERT or UPDATE but the closest I've got is to add the values to column 2 starting from the last defined ID value (e.g. row 80ish).
ID | Column 2 |
--------------------------------
1 | value |
2 | value |
3 | |
4 | |
5 | |
etc
The real table has around 10 columns, all with data in but I just need to add content (a list of around 80 different strings in CSV format to one of the columns)
I'd appreciate it if anyone could point me in the right direction.
I'd load the data into a separate table with the same structure and then update the target table using join or subquery to determine which columns are currently empty.
i.e. load interim table and then:
update target_table set column2 = (select column2 from interim_table where ...
where column2 is null
(slow but intuitive)
update target table, interim_table
set target table.column2 = interim_table.column2
where target table... = interim_table...
and target_table.column2 is null
(better performance)
Why don't you first run a query to find out the first empty row ID number? you can use SELECT COUNT(*)
FROM TABLE_NAME for that.
then you create a for loop and inside run a INSERT query, starting with the value returned by the previous query. just a scratch:
for(var id = last; id < totalOfQueries; id++)
{
var query = new MysqlCommand("INSERT INTO table VALUES ('" + id + "',....);
}
I'm doing a small thing like the like feature you see on facebook. So the way I'm doing it is like this. I have a table called products which contains products that people can like.
Like this (stripped down):
id | prodName | status (0=clear, 1=blocked)
----------------------------------------------------------
1 | Philips Food Processor | 0
2 | Le Sharp Knife | 0
3 | Ye Cool Fridge | 0
Then comes the `likes` table like this:
id | prodName | prodId | userId
--------------------------------------------
1 | Philips Food Processor | 1 | 1
2 | Le Sharp Knife | 2 | 1
3 | Ye Cool Fridge | 3 | 1
4 | Ye Cool Fridge | 3 | 2
I need to check, before adding to the likes table, if a product with that id actually actually exists in the products table and its status = 0. I currently do this with a lot of php code. What would be a good way to do this using sql? Is it possible? Using foreign keys or something like that?
I'm using innodb table type.
You can do a conditional insert. For product 6 and user 7:
insert into Likes
(prodName, prodId, userId)
select prodName
, id
, 7
from Products
where id = 6
and status = 0
If this inserts no rows, you know that the product did not exist with status 0.
If you just want to phrase the insert so it follows the rules, then you can use insert . . . select as follows:
insert into likes(prodId, userId)
select <prodid>, <userid>
from products p
where p.prodid = <prodid> and status = 0
I don't think MySQL supports "partial" foreign key constraints, where you can also include the requirement on the flag.
And, you shouldn't put the product name int he likes table. You should look it up in the products table.
The key element of trying to add something to the likes table that does not exist in the product table is the feedback to the user that lets them know they're doing it wrong. Any answer you determine on should not ignore the user feedback side of things - which is basically going to require your PHP code.
However, yes - there is a way to do it via foreign keys. You can index the prodid in the second table, and reference it as a foreign key to the first table.id. This means that if you try an insert and you get an error, there's a chance that the problem is that you're trying to add something without a match in the first table.
However, trying to determine precisely what the error is so you can determine the proper logic to respond to that error causes its own mass of php code, and is less easily transparent for future developers to maintain. I'd suggest a simple method in your Product object: isValid( id ) that returns true/false - so your 'check for this' code simply goes if( Product.isValid( prodId ) ){ Like.insert( userId, prodId ); }
But at the same time, I'd REALLY recommend a foreign key constraint along with the php code you're probably already using, just as insurance against your database becoming cluttered with unlinked rows. It's usually best to have multiple barriers against bad data.
Additionally ... is there a reason why you're storing the product names both in the product table AND in the likes table? I don't see why you'd need it in the likes table.
--Check to see if cleared product exist in products table
Select count(*) from products p where p.status = 0 and p.id = %IDVALUE
--Check if your user previous liked product
Select count(*) from products p, likes l where p.id = l.prodId and l.userId = %USERID
In your code you can execute the statements (replace %IDVALUE and %USERID with actual values) and check the return column to get the count and preform your custom logic.
Currently you require the prodId to populate the likes table, hence you need to lookup the data regardless of the contraint regarding blocked. Hence:
INSERT INTO likes (prodname, prodId, userId)
SELECT prodname, id, 123456
FROM products
WHERE prodname='Le Sharp Knife'
AND status=0;
(just substitute 123456 and 'Le Sharp Knife' for the parameters you need).
Yuo need to query database to check record,
for example you product id is 2 so your query would be something like
$query = select * from 'your-like-table' where 'prodId ' = 'ID';
then
if ( !mysql_query('your-db',$query)):
if you come under this condition then it's the time when you enter your like to database
endif;
hope it helps
i am using this code so i can count the number of comments for each article
SELECT *, COUNT(comment_id) as count
FROM article_comments
WHERE article_id =colname
GROUP BY article_id
this is what my comment table look like
http://i54.tinypic.com/2cdu3dk.png
i want to save these number in another table (the articles table.. each number next to it's article ) like this
http://i54.tinypic.com/2dgm82u.png
and when the user enter a comment..the number change automatically
someone help me with the code
or if there is another way to do this
i know it's a long question
but i have been trying to solve this for like..forever
thanx
You could set a TRIGGER that updates the comment count table every time a comment is added. Or you could simply add the UPDATE query right after the INSERT query in your comment page.
You probably do not need a lookup table. 1 article has many comments. Therefore, structure your comments table something like this (add an article field);
id | article | content
-------------------------
1 | 1 | Comment 1 for article 1.
2 | 1 | Comment 2 for article 1.
3 | 2 | Comment 3 for article 2.
When displaying your article, list comments using the following query;
SELECT a.id, a.content FROM articles a WHERE a.article = :myArticleId
When creating a new comment:
INSERT INTO comments (article, content) VALUES (:currentArticleId, :content)
UPDATE article SET commentCount = commentCount + 1 WHERE article = :currentArticleId
The articles table will look something like this;
id | commentCount | content
------------------------------
1 | 0 | Article with 0 comments.
2 | 3 | Article with 3 comments.
This requires some work on your part, but it has more benefits than drawbacks.
Your proposed solution has 2 large drawbacks;
COUNT() in SQL does not scale very well and can be slow, normally it can be avoided.
The lookup table adds unnecessary complexity to your application.
Triggers should also always be avoided. They create "magic" conditions - your database can be changed without you knowing about it. Triggers are often more difficult to change than code too.
$query = mysql_query("SELECT * FROM article_comments WHERE article_id =".$youarticleId);
//the number of comments is :
$number_Of_Comments = mysql_num_rows($query);
//save it to another table
$query2 = mysql_query("UPDATE yourTable set numberOfComments =".$number_Of_Comments);
on saving comments, try to:
update table_where_you_count_the_comments set number_of_comments = number_of_comments +1 where article_id = theID limit 1;
or look for mysql triggers.
you're asking the sql server to select everything and the count id at the same time, use one of them and give it a where close, and Bingo!