I have two database tables that I am using to create a Twitter-style following system.
sh_subscriptions
=> id
=> user_id
=> feed_id
sh_feeds
=> id
=> item
=> shop_name
=> feed_id
The problem with storing feed_id rather than shop_name in sh_subscriptions is that it requires a lot of table joining:
$id = $_POST['id'];
$user_id = $id['id'];
$shop_name = mysqli_escape_string($con, $_POST['shop_name']);
$query = "SELECT * FROM sh_subscriptions s INNER JOIN sh_feeds f ON s.feed_id = f.feed_id WHERE s.user_id = $user_id AND f.shop_name = '$shop_name'";
$result = mysqli_query($con, $query) or die(mysqli_error($con));
if (mysqli_num_rows($result) > 0)
{
$query2 = "DELETE FROM sh_subscriptions s INNER JOIN sh_feeds f ON s.feed_id = f.feed_id WHERE s.user_id = $user_id AND f.shop_name = '$shop_name'";
$result2 = mysqli_query($con, $query2) or die(mysqli_error($con));
}
else
{
// insert the row instead
}
(I know there's an error somewhere in the if statement, but I'll worry about that later.)
If I were to replace feed_id with shop_name, I would be able to replace line 5 with this:
$query = "SELECT * FROM sh_subscriptions WHERE user_id = $user_id AND shop_name = '$shop_name'";
My question is: is it always preferable to store MySQL values as integers where possible, or in a situation like this, would it be faster to have sh_subscriptions contain shop_name rather than feed_id?
Your sh_subscriptions table is actually a many-to-many join table that relates users to feeds. This is considered a fine way to design database schemas.
Your basic concept is this: you have a collection of users and a collection of feeds. Each user can subscribe to zero or more feeds, and each feed can have zero or more subscribers.
To enter a subscription you create a row in the sh_subscriptions table. To cancel it you delete the row.
You say there's "a lot of table joining." With respect, this is not a lot of table joining. MySQL is made for this kind of joining, and it will work well.
I have some suggestions about your sh_subscriptions table.
get rid of the id column. Instead make the user_id and feed_id columns into a composite primary key. That way you will automatically prevent duplicate subscriptions.
add an active column ... a short integer ... to the table. When it is set to a value of 1 your suscription is active. That way you can cancel a subscription by setting active to 0.
you might also add a subscribed_date column if you care about that.
create two compound non unique indexes (active,user_id,feed_id) and (active,feed_id,userId) on the table. These will greatly accelerate queries that join tables like this.
Query fragment:
FROM sh_feed f
JOIN sh_subscription s ON (f.feed_id = s.feed_id AND s.active = 1)
JOIN sh_users u ON (s.user_id = u.user_id)
WHERE f.shop_name = 'Joe the Plumber'
If you get to the point where you have hundreds of millions of users or feeds, you may need to consider denormalizing this table.. that is, for example, relocating the shop name text so it's in the sh_subscriptions table. But not now.
Edit I am proposing multiple compound covering indexes. If you're joining feeds to users, for example, MySQL starts satisfying your query by determining the row in sh_feeds that matches your selection.
It then determines the feed_id, and random-accesses your compound index on feed_id. Then, it needs to look up all the user_id values for that feed_id. It can do that by scanning the index from the point where it random-accessed it, without referring back to the table. This is very fast indeed. It's called a covering index.
The other covering index deals with queries that start with a known user and proceed to look up the feeds. The order of columns in indexes matters: random access can only start with the first (leftmost) column of the index.
The trick to understand is that these indexes are both randomly accessible and sequentially scannable.
one other note If you only have two columns in the join table, one of your covering indexes is also your primary key, and the other contains the columns in the reverse order from the primary key. You don't need any duplicate indexes.
Related
I have this problem that's been killing me for a couple days now.
So we have a table of all processed orders.
We have a table for all orders that come in.
We need to effectively cross-reference the orders in the new table that is continually updating against the orders already completely in the primary table so that we don't complete the same order multiple times.
After we get a batch of new orders, this is the query that I currently run in an attempt to cross reference it with the table of completed orders:
$sql = "DELETE
FROM
`orders_new`
WHERE
`order` IN (
SELECT DISTINCT
`order`
FROM
`orders_all`
)
AND `name` IN (
SELECT DISTINCT
`name`
FROM
`orders_all`
)
AND `jurisdiction` IN (
SELECT DISTINCT
`jurisdiction`
FROM
`orders_all`
)";
As you can probably tell, I want to delete rows from the "orders_new" table where a row with the same order, name, and jurisdiction already exists in the "orders_all" table.
Is this the right way to handle this sort of query?
Well, the right way depends on many things.
But first, I do not like your division into two tables. In that case I would introduce a column identfying state, that woul reference a table with possible states. Those would be "new", "in process", "completed". That way you have one order stored as only one record as it should be.
But your query migt be ok, but you should check the performance.
Take a look at: https://sqlperformance.com/2012/12/t-sql-queries/left-anti-semi-join
Not exactly your case but very similar.
Another thing: Why do you use DISTINCT. That would imply that "order" is not a unique identifier.
Based on your edit you identify the order with composite key "order", "name", "jurisdiction". Is this really the key, the whole key and nothing but the key so help you Codd. If not, you could delete a bunch of records. But even so your query would delete an all orders for which the order, name and jurisdiction can be found in table order IN DIFFERENT RECORDS. So your query is false.
Saying that, a variant of your query might be
DELETE order_new
FROM
order_new
INNER JOIN
order_all ON order_all.order = order_new.order
AND order_all.name = order_new.name
AND order_all.jurisdiction = order_new.jurisdiction
But, the real problem is your ER model.
No, your query will delete any record where there are any records with the same order, name, and jurisdiction, even if those records are different from one another. In other words, a row in orders_new will be deleted if one row in order_all has the same order, a different one has the same name, and a third one has the same jurisdiction. You are very very likely to delete way more than you want to. Instead, this would be more appropriate:
DELETE FROM `orders_new`
WHERE (`order`, `name`, jurisdiction`) IN (
SELECT `order`, `name`, `jurisdiction`
FROM `orders_all`
)
or maybe
DELETE FROM `orders_new`
WHERE EXISTS (
SELECT 1
FROM `orders_all` AS oa
WHERE oa.`order` = `orders_new`.`order`
AND oa.`name` = `orders_new`.`name`
AND oa.`jurisdiction` = `orders_new`.`jurisdiction`
)
You should convert that to a DELETE - JOIN construct like
DELETE `orders_new`
FROM `orders_new`
INNER JOIN `orders_all` ON `orders_new`.`order` = `orders_all`.`order`
AND `orders_new`.`name` = `orders_all`.`name`
AND `orders_new`.`jurisdiction` = `orders_all`.`jurisdiction`;
I want to show recommended users to a user in descending order where skills match the most.
Issue is that I am storing skill in single field in this form
user_skill
musician,pop,singer
Note: this will be input musician,pop,singer..
so what I want to achieve I want to show users have all three skills at top,then those having two at last those having only one.
so out put will be like
**user_name skills**
sam musician,pop,singer
smith musician,pop,singer
ali musician,singer
nasira musicain,pop
siri musician
taylor pop
andrew singer
Can this be achieved by single mysql query?
If this not possible can this be done by php code.I don't want to change table structure as this will require lots of redo.
Thanks for your help.
You might be able to do with with SQL but the query would be so complex i probably wouldnt.
On the php side you could iterate over the result set and tally the skills for each user_name within a given set of skills you are looking for, and then just sort them:
$desired = explode(',', $input);
$users = array();
// i presume you have the query worked out to find users with any one of input
// skills attributed to them so lets say that $stmt is the PDO statement where you
// have executed that query
while (false === ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
$row['skills'] = explode(',', $row['skills']);
// assign an array containing only skills that were in your $input
// to $row['desired_skills']
$row['desired_skills'] = array_intersect($desired, $row['skills']);
$row['nb_desired_skills'] = count($row['desired_skills']);
$user[] = $row;
}
usort($users, function ($a, $b) {
return $b['nb_desired_skills'] - $a['nb_desired_skills'];
});
// now you can loop over $users and display the fields you need
However, the definition and attribution of these skills is a key part of you application, you should just normalize the tables now... its only going to become a larger refactor by putting it off.
I don't want to change table structure as this will require lots of redo.
I strongly (!) recommend you make a separate skills table, and link that to your users table using foreign keys. You will be infinitely thankful to yourself once your project gets even slightly more complex.
If you don't know how, I'm sure people on this site would help you with a script that does the conversion if you were to post another question.
MySQL
CREATE TABLE skills
(
name VARCHAR(64) NOT NULL,
user_id INT NOT NULL,
PRIMARY KEY (name, user_id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
This way you can easily select users by most skills using a simple subquery (assuming you have a users table with an id column):
MySQL
SELECT *,
(SELECT COUNT(*)
FROM skills
WHERE user_id = u.id) AS num_skills
FROM users u
ORDER BY num_skills DESC;
So why would you take this approach instead of relying on PHP?
Creates minimal extra overhead in database,
significantly reduces chatter between database and webserver by not requiring to query the entire users table if you just want, for example, only the top 10 users,
enables more complex and varying queries in the future easily.
The following MySQL snippet queries only those users who have a given skill (for example, "singer"), then sorts them in descdending order based on number of skills. The query does not return users with zero skills:
MySQL
SELECT *,
(SELECT COUNT(*)
FROM skills
WHERE user_id = u.id
AND name = 'singer') AS num_skills
FROM users u
WHERE num_skills > 0
ORDER BY num_skills DESC;
Of course, you can also search for skills by id just replace the second part of the WHERE clause inside the subquery with AND id = 3, which will query those users, who have a skill with ID of 3.
The next step towards a more optimal database would be creating a real skills table, which stores only the skills that you have registered in your database, and a user_skills table, that links it and the users table together.
This would enable you to significantly reduce database size on the long run, and be able to run complex but 'clean' queries that do not depend on the webserver.
I have 2 tables, a users table and a trade table.
Which look like:
The structure of my code right now is:
<?php
$history = mysqli_query($con, "SELECT * FROM .......");
while($row = mysqli_fetch_array($history)) {
echo("The sentence");
} ?>
Problem I'm facing is that I'm trying to echo the user_name which in one case has to be the receiver and other the person giving it.
Pro tip: Never use SELECT * in software unless you know exactly why you are doing so. In your case it is harmful.
I'm assuming your query is really against the user and trade tables you mentioned in your question.
First, recast your query using 21st century SQL, as follows:
SELECT *
FROM trade AS t
JOIN user AS s ON s.user_id = t.user_id_sender
WHERE s.facebook_id = $fbid
Second, use this to retrieve your user's names and the item id traded.
SELECT s.user_name AS sender,
r.user_name AS receiver,
t.trade_id AS item_id
FROM trade AS t
JOIN user AS s ON s.user_id = t.user_id_sender
JOIN user AS r ON r.user_id = t.user_id_receiver
WHERE s.facebook_id = $fbid
See how we JOIN the user table twice, with two different aliases s (for sender) and r (for receiver)? That's the trick to fetching both names from IDs.
See how we employ the aliases sender and receiver to disambiguate the two user_name columns in the result set?
Now, when you use the php fetch_array function, you'll end up with these elements in the array.
$history['sender']
$history['receiver']
$history['item_id']
The array index strings correspond to the alias names you specified in your SELECT clause in your query.
So, one reason to avoid SELECT * is that you can get more than one column with the same name, and that means fetch_array will eliminate those duplicates and so it will lose useful information from your result set.
Would it be possible to store a "link" to another record inside a record? For example:
table USERS
id name link
-------------------------------------------------------
1 user1 [link to record with id=4 in table info]
So, in PHP, I could do something like this:
// connect to the database etc....
$query = "select * from users where id=1";
$result = mysql_query($query);
$another_result = mysql_result($result, 0, 'link');
So that $another_result stores the result of another query, in the same raw format as if it was called using mysql_query().
Is this possible?
$query = "select info.* from info
inner join users on users.link = info.id
where users.id=1";
$result = mysql_query($query);
Using JOIN is a fundamental part of SQL, like using for loops in PHP.
Read A Visual Explanation of SQL Joins by fearless leader.
Maybe you mean a key that point to a another key in another table, so for example you can have something like this :
table USERS
id name info_id
-------------------------------------------------------
1 user1 4
table INFO
id info
--------------
4 someinfo
With a JOIN you can get for example a resultset with the "linked" fields :
SELECT u.name AS name, i.info AS info
FROM USERS u
JOIN INFO i ON u.info_id = i.id
MySql is a so called relational database and having relations (links) between tables is one of the key concepts. In your specific case the "link" you want is called a Foreign Key. You might want to have a read here (there are many more articles around if you have a look on google).
You can retrieve linked records via a JOIN operation as the other answerers have already told you.
I have two tables, one holds the information of contributors to my site and one holds information on photographs contributed.
For the admin side of the site, I want to create a table using php and mysql that displays all contributors but also counts the number of photographs each contributor has available for the site.
I get the list of names using this code
SELECT *
FROM site_con
ORDER BY surn ASC
I have then set up a loop to list all the names but have added a query within that loop to count the number of photographs using this code
$contributor = $row_rsContrib['con_Code'];
mysql_select_db($database_connGrowl, $connGrowl);
$query_rsCounter = "SELECT COUNT(*) AS Count
FROM site_phts
WHERE photter = $contributor";
$rsCounter = mysql_query($query_rsCounter, $connGrowl) or die(mysql_error());
$row_rsCounter = mysql_fetch_assoc($rsCounter);
$totalRows_rsCounter = mysql_num_rows($rsCounter);
The only problem is when '$contributor' is not in the photographs table, it returns an error.
Any ideas?
You can get the list of contributors & the number of photos in a single query:
SELECT sc.*,
COALESCE(x.numPhotos, 0) AS numPht
FROM SITE_CON sc
LEFT JOIN (SELECT sp.photter,
COUNT(*) AS numPhotos
FROM SITE_PHTS sp
GROUP BY sp.photter) x ON x.photter = sc.con_code
ORDER BY ssc.surn
Your query fails because a photographer doesn't necessarily have contributions -- the query above returns the list of photographers, and those without photos associated will have a numPht value of zero. Here's a primer on JOINs, to help explain the OUTER JOIN that's being used.
Actually the best way to do this is by using MSQL to count rather than PHP:
SELECT site_con.*, COUNT( photo_id )
FROM site_con
LEFT JOIN site_phts ON site_con.con_Code = site_phts.photter
GROUP BY site_con.con_Code
ORDER BY site_con.surn
The LEFT JOIN has the special property of creating NULL entries when there is no row in the right table (photos) that matches a contributor row. COUNT will not count these NULL entries. (You need some unique column in the photos table, I used photo_id for that.)
this is the relation between Contributors and photographs:
1 photograph can have a most 1 Contributor
1 Contributor can have a most infinit photograph
Contributor <-(0,n)------(0,1)-> Photograph
so you might wanna add a connexion betweet those two tables, I mean you add the con_id to the photographs table (as a column).
this way you'll be able to retrieve all the informations in one SQL query.
(like OMG Ponies just said)
Do something like this, I believe this should work :
$result = mysql_query("SELECT COUNT(*) AS Count FROM site_phts WHERE photter = '$contributor'"); // put the single quote if $contributor is a string value
//use mysql_fetch_array
if ($row = mysql_fetch_array($result, MYSQL_NUM)) {
printf("ID: %d", $row[0]);
}
Hopefully this works, Good luck mate !