ok I have looked around here for a few days now, and I did find this :
MySQL GROUP BY two columns
This has been helpful but hasn't worked for what I am currently working on.
So here is the scope of things I have 3 different tables
table 1 (quantities) consist of:
quantity_id int(11) auto_increment not null (primary key)
product_id int(11) not null
color_id int(11) not null
size_id int (11) not null
quantity int (11) not null
table 2 (colors) consist of:
color_id int(11) auto_increment not null (primary key)
color_name varchar(255)
table 3 (sizes) consist of:
size_id int (11) auto_increment not null (primary key)
size varchar(255)
SELECT s.size, c.color_name, q.quantity, q.size_id, q.product_id, q.color_id
FROM quantities q, colors c, sizes s
WHERE q.color_id = c.color_id
AND q.size_id = s.size_id
AND q.product_id = $pid
GROUP BY q.color_id
ORDER BY q.size_id
$pid is the product id passed by the form.
When I load the results into 2 different select statements: one for color, one for size
I get multiple of the same color or size:
red s
red L
yellow L
I understand that it is literally pulling the rows out of the database and displaying them, but how
do I not repeat myself?
I have tried this query
SELECT s.size, c.color_name, q.quantity, q.size_id, q.product_id, q.color_id
FROM quantities q LEFT JOIN colors c ON q.color_id = c.color_id
LEFT JOIN sizes s ON q.size_id = s.size_id
WHERE q.product_id = $pid
Is there a better way?
Your question is a bit unclear, but the query below will give you color, size and quantity information given a product id by using joins.
Updated with Quantity Constraint
SELECT size, color_name, quantity, sizes.size_id, colors.color_id
FROM quantities
INNER JOIN colors ON quantities.color_id = colors.color_id
INNER JOIN sizes ON quantities.size_id = sizes.size_id
WHERE product_id = ? and quantity > 1
You are getting repeated rows because you are getting all the combinations of color and size. I imagine there are several colors for the same size, which is normal. If you want to force the integrity, change you left join for an inner join so that you only get records that have a match.
SELECT s.size, c.color_name, q.quantity, q.size_id, q.product_id, q.color_id
FROM quantities q INNER JOIN colors c ON q.color_id = c.color_id
INNER JOIN sizes s ON q.size_id = s.size_id
WHERE q.product_id = $pid
UPDATE: If you only want to show quantities >=1 then do this:
SELECT s.size, c.color_name, q.quantity, q.size_id, q.product_id, q.color_id
FROM quantities q INNER JOIN colors c ON q.color_id = c.color_id
INNER JOIN sizes s ON q.size_id = s.size_id
WHERE q.product_id = $pid and q.quantity>=1
If you also only want to show one color and one size and the sum of all qty then do this:
SELECT min(s.size) as size, min(c.color_name) as color, sum(q.quantity) as TotalQty
FROM quantities q INNER JOIN colors c ON q.color_id = c.color_id
INNER JOIN sizes s ON q.size_id = s.size_id
WHERE q.product_id = $pid and q.quantity>=1
If you only store id and name for color it could very well be a php function, so you don't need a color table:
<?PHP
function color($color)
{
$colors['red']=1;
$colors['blue']=2;
$colors['green']=3;
return $colors[$color];
}
//example of usage
$sql ="... where `color`=".color($_POST['color'])." ";
?>
Also since size is a small value (is not a big text for example) then you don't need a size table at all. All you need is a product and a quantity (better rename it to product_info) table:
mysql_query("
CREATE TABLE `Product` (
`product_id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`product_name` varchar(30) NOT NULL ,
unique(`product_name`),
PRIMARY KEY (`product_id`)
) ENGINE=InnoDB ROW_FORMAT=COMPACT;")
or die(mysql_error());
mysql_query("
CREATE TABLE `Product_Info` (
`product_id` INT UNSIGNED NOT NULL ,
`color_code` char(3) NOT NULL ,
`prod_col_size` varchar(15) NOT NULL ,
`quantity` mediumint UNSIGNED NOT NULL ,
PRIMARY KEY (`product_id`,color_code,prod_col_size),
CONSTRAINT `prod_id_FK` FOREIGN KEY (`product_id`)
REFERENCES `Product` (`product_id`)
ON DELETE CASCADE
) ENGINE=InnoDB ROW_FORMAT=COMPACT;")
or die(mysql_error());
This is not perfect normalization but you already choosed this way. The queries will be very easy this way. To bother with long joins in a well normalized schema makes sense. To bother for no reason at all!? You only need to make a join in two tables in case you need some filed from the product table.
/*Find how many red items has a specific product*/
select sum(`Product_Info`.`quantity`)
from `Product_Info` where `color_code`='124' and `product_id`='3';
Related
I would like to have some statistics and calculate the percentages of which tools have been chosen the most overall in all the registrations of my database
These are my two tables:
$table_registration = $wpdb->prefix . 'registration';
$table_tools = $wpdb->prefix . 'tools';
wp_registration table:
CREATE TABLE $table_registration
(
reg_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
dato date,
billedeURL VARCHAR(80) NOT NULL,
fiske_vaegt DECIMAL( 2,1 ) NOT NULL,
fiske_laengde INT NOT NULL,
reg_user_id BIGINT UNSIGNED NOT NULL,
reg_tools_id INT UNSIGNED NOT NULL
PRIMARY KEY (reg_id),
FOREIGN KEY (reg_user_id) REFERENCES wp_users(id),
FOREIGN KEY (reg_tools_id) REFERENCES $table_tools(tools_id)
)
wp_tools table:
CREATE TABLE $table_tools
(
tools_id INT UNSIGNED AUTO_INCREMENT NOT NULL,
tools_navn CHAR (20),
PRIMARY KEY (tools_id)
)
I have been trying to create the correct mysql but with no luck so this is what I've been doing up till now.
select l.*, concat(round(100 * count(t.reg_tools_id) / t2.cnt,0),'%')
from wp_registration l
left join wp_tools t on l.toolss_id = t.reg_id
cross join
(select count(*) cnt
from wp_registration
where reg_tools_id = 1) t2
group by l.reg_id;
But it tells me that every tool has been used 50% of the times. which obviously is wrong I have three tools users can choose from and right now have 1 - two votes and 2 - nine votes and 3 - two votes there are 13 registrations in total
Hopefully, I understand what do you need !
SELECT
tools.tools_id,
((COUNT(*) / (SELECT COUNT(*) FROM registration)) * 100) AS percent
FROM
registration
JOIN
tools ON registration.reg_tools_id = tools.tools_id
GROUP BY
tools.tools_id
ORDER BY
percent DESC
LIMIT 1
Some remarks :
Try to write in a pure sql
You do not need a php tag for this question
Minimize your code from unnecessary part
Use the concat and round functions in the programming language that you are using not in SQL (I think you are using php here, so do the query then get the result and apply the round and the concat in php instructions)
i have a 2 category system, basically what i want to do is i have 2 tables, top_category and bottom_category, i have created my sidebar which will list all the products using sql query. is there a way i can pull the top_category and bottom_category data in one sql query and have the bottom_category sorted by the foreign key id of top_category so when i loop them in a list they end up in the right nest?
Here are my tables,
CREATE TABLE top_category (
id INT PRIMARY KEY,
NAME VARCHAR(100)
);
CREATE TABLE bottom_category (
id INT PRIMARY KEY,
NAME VARCHAR(100) ,
top_category_id INT REFERENCES top_category
);
And here is my products table, so when i click on a bottom_category link i want it to list the products linked to the bottom_category_id's:
create table product (
id int primary key,
name varchar(100) ,
bottom_category_id int references bottom_category
);
You could write something like
SELECT product.*, bottom_category.name, top_category.name
FROM product
LEFT JOIN bottom_category ON bottom_category.id = product.bottom_category_id
LEFT JOIN top_category ON top_category.id = bottom_category.top_category_id
ORDER BY top_category.id,bottom_category.id
But if you have really big tables then just forget about 3nd normal form and add names for categories into product table. But only if you have really big tables with categories.
UPD
Add ORDER BY
select p.*,
bc.name bc_name,
tc.name tc_name
from product p
left join bottom_category bc on p.bottom_category_id=bc.id
left join top_category tc on bc.top_category_id=tc.id
order by tc.id,bc.id
Failing to find a solution to this question I was wondering if there was a better way of storing data for this problem.
That db structure allows items to be stored in multiple categories, but doesn't allow easy access to the parent category hierarchy.
What I would like is to have a category relationship such as:
Books
> Novels
> Paperbacks
> Hardbacks
And have an item stored against Paperbacks for instance that would also show up in Novels and Books. So the 'categories' actually work more like filters than actual categories.
First of all you need to design your category table with usage of Nested Set architecture. With usage of Nested Sets you will easily select whole branch of categories, and then you will be able to select products for these categories.
So the first table will be:
CREATE TABLE categories (
id int unsigned NOT NULL auto_increment,
name varchar(255) NOT NULL,
left int unsigned NOT NULL,
right int unsigned NOT NULL,
PRIMARY KEY (id)
);
The second table will be:
CREATE TABLE products (
id int unsigned NOT NULL auto_increment,
name varchar(255) NOT NULL,
PRIMARY KEY (id)
);
And the third table will be:
CREATE TABLE product_categories (
category_id int unsigned NOT NULL,
product_id int unsigned NOT NULL,
PRIMARY KEY (category_id, product_id)
);
Now to select all products for whole branch of categories, you need to use query like this:
SELECT p.*
FROM categories AS c1
LEFT JOIN categories AS c2 ON c1.left <= c2.left AND c2.right <= c1.right
LEFT JOIN product_categories AS pc ON pc.category_id = c2.id
LEFT JOIN products AS p ON pc.product_id = p.id
WHERE c1.id = #id
Nested set operations
Add new node
1st step: update already existed categories
UPDATE categories
SET right = right + 2, left = IF(left > #right, left + 2, left)
WHERE right >= #right
2nd step: insert new category
INSERT INTO categories SET left = #right, right = #right + 1, name = #name
Delete existing node
1st step: delete node
DELETE FROM categories WHERE left >= #left AND right <= #right
2nd step: update else nodes
UPDATE categories
SET left = IF(left > #left, left – (#right - #left + 1), left),
right = right – (#right - #left + 1)
WHERE right > #right
I am currently developing a an application to allow users to search through a database of documents using various paramaters and returning a set of paged results. I am building it in PHP/MySQL, which is not my usual development platform, but its been grand so far.
The problem I am having is that in order to return a full set of results I have to use LEFT JOIN on every table, which completely destroys my performance. The person who developed the database has said that the query I am using will return the correct results, so thats what I have to use. The query is below, I am by no means an SQL Guru and could use some help on this.
I have been thinking that it might be better to split the query into sub-queries? Below is my current query:
SELECT d.title, d.deposition_id, d.folio_start, d.folio_end, pl.place_id, p.surname, p.forename, p.person_type_id, pt.person_type_desc, p.age, d.manuscript_number, dt.day, dt.month, dt.year, plc.county_id, c.county_desc
FROM deposition d
LEFT JOIN person AS p ON p.deposition_id = d.deposition_id
LEFT JOIN person_type AS pt ON p.person_type_id = pt.person_type_id
LEFT JOIN place_link AS pl ON pl.deposition_id = d.deposition_id
LEFT JOIN date AS dt ON dt.deposition_id = d.deposition_id
LEFT JOIN place AS plc ON pl.place_id = plc.place_id
LEFT JOIN county AS c ON plc.county_id = c.county_id
WHERE 1 AND d.manuscript_number = '840'
GROUP BY d.deposition_id ORDER BY d.folio_start ASC
LIMIT 0, 20
Any help or guidance would be greatly appreciated!
Deposition Table:
CREATE TABLE IF NOT EXISTS `deposition` (
`deposition_id` varchar(11) NOT NULL default '',
`manuscript_number` int(10) NOT NULL default '0',
`folio_start` varchar(4) NOT NULL default '0',
`folio_end` varchar(4) default '0',
`page` int(4) default NULL,
`deposition_type_id` int(10) NOT NULL default '0',
`comments` varchar(255) default '',
`title` varchar(255) default NULL,
PRIMARY KEY (`deposition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Date Table
CREATE TABLE IF NOT EXISTS `date` (
`deposition_id` varchar(11) NOT NULL default '',
`day` int(2) default NULL,
`month` int(2) default NULL,
`year` int(4) default NULL,
PRIMARY KEY (`deposition_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Person_Type
CREATE TABLE IF NOT EXISTS `person_type` (
`person_type_id` int(10) NOT NULL auto_increment,
`person_type_desc` varchar(255) NOT NULL default '',
PRIMARY KEY (`person_type_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=59 ;
Seems that you want to select one person, place etc. per deposition.
The query you wrote will return you this, but it's not guaranteed which one will it return, and the query is inefficient.
Try this:
SELECT d.title, d.deposition_id, d.folio_start, d.folio_end, pl.place_id, p.surname, p.forename, p.person_type_id, pt.person_type_desc, p.age, d.manuscript_number, dt.day, dt.month, dt.year, plc.county_id, c.county_desc
FROM deposition d
LEFT JOIN
person p
ON p.id =
(
SELECT id
FROM person pi
WHERE pi.deposition_id = d.deposition_id
ORDER BY
pi.deposition_id, pi.id
LIMIT 1
)
LEFT JOIN
place_link AS pl
ON pl.id =
(
SELECT id
FROM place_link AS pli
WHERE pli.deposition_id = d.deposition_id
ORDER BY
pli.deposition_id, pi.id
LIMIT 1
)
LEFT JOIN
date AS dt
ON dt.id =
(
SELECT id
FROM date AS dti
WHERE dti.deposition_id = d.deposition_id
ORDER BY
dti.deposition_id, pi.id
LIMIT 1
)
LEFT JOIN
place AS plc
ON plc.place_id = pl.place_id
LEFT JOIN
county AS c
ON c.county_id = plc.county_id
WHERE d.manuscript_number = '840'
ORDER BY
d.manuscript_number, d.folio_start
LIMIT 20
Create an index on deposition (manuscript_number, folio_start) for this to work fast
Also create a composite index on (deposition_id, id) on person, place_link and date.
The poor performance is almost certainly from lack of indexes. Your deposition table doesn't have any indexes, and that probably means the other tables you're referencing don't have any either. You can start by adding an index to your deposition table. From the MySQL shell, or phpMyAdmin, issue the following query.
ALTER TABLE deposition ADD INDEX(deposition_id, manuscript_number);
You know you're on the right track if the query executes faster after adding the index. From there you might want to put indexes on the other tables on the referenced columns. For instance for this part of your query "LEFT JOIN person AS p ON p.deposition_id = d.deposition_id", you could try adding an index to the person table using.
ALTER TABLE person ADD INDEX(deposition_id);
You only need a LEFT JOIN if the joined table might not have a matching value. Is it possible in your database schema for a person to not have a matching person_type? Or deposition to not have a matching row in date? A place not have a matching county?
For any of those relationships that must exist for the result to make sense you can change the LEFT JOIN to an INNER JOIN.
These columns should have indexes (unique if possible):
person.deposition_id
date.deposition_id
place_link.deposition_id
place_link.place_id
The date table looks like a bad design; I can't think of a reason to have a table of dates instead of just putting a column of type date (or datetime) in the deposition table. And date is a terrible name for a table because it's a SQL reserved word.
Say I have three tables in my database:
CREATE TABLE `users` (
`user_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`username` VARCHAR(16) NOT NULL
);
CREATE TABLE `users_meta` (
`meta_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`key` VARCHAR(200) NOT NULL ,
`value` TEXT NOT NULL
);
CREATE TABLE `posts` (
`post_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`user_id` INT NOT NULL ,
`content` TEXT NOT NULL
);
The table users_meta is just a key-value store of information about users, such that we can add any piece of information we want.
Say I added a key => value pair to the users_meta table for each user where the key was "age", and the value was a number representing their age.
Given this set of circumstances, what's the best way to select the first 10 posts ordered by user age?
I like putting the condition of the join in the join itself to be clear that I want a limited join:
SELECT p.post_id, p.content
FROM users u
INNER JOIN users_meta um
ON (u.user_id = um.user_id) AND um.key = 'age'
INNER JOIN posts p
ON (p.user_id = u.user_id)
ORDER BY um.value
limit 10
If you order by user age only, you will select 10 posts of the same user (the youngest one).
I would suggest to denormalize and store age in users table directly.
Agree with #KOHb, but if that's exactly what you want, here is the query:
SELECT TOP 10 p.id, p.content
FROM users u JOIN users_meta um ON (u.user_id = um.user_id)
JOIN posts p ON (p.user_id = u.user_id)
WHERE um.key = 'age'
ORDER BY um.value