Calling SQL foreign key data with PHP - php

I need to get a better grasp on the process of manipulating and utilizing the SQL tables I need to make so I can continue figuring out exactly how I should make them and structure them to work.
If I have a table for shirts and another table for sizes and I use a foreign key on the shirts table to link to the sizes table to represent multiple options for that column. Do I only need to call on the shirts table in the PHP coding? If so how do I tell the PHP to gather whatever options are available for each row on the sizes table?
If in the table it has
vneck sizes,
scoop neck sizes
and I have it set where the vnecks only have s,m,l,1x and the scoop necks have xs,s,m,l,1x,2x,3x. How can I code the PHP to recognize the difference I have logically set in each row for that column?

It sounds like you actually need to have at least three tables, one for shirts, one for sizes , and one to relate shirts to sizes. There are any number of way you can use PHP to query the data, but most likely you would want to simply query using a JOIN to get data from all tables at the same time.
So perhaps something like this:
shirts table:
shirt_id (auto-incrementing primary key)
...other shirt-related fields
sizes table:
size_id (auto-incrementing primary key)
size_value (i.e. S, M, L)
...other size-related fields
shirt_sizes table:
shirt_id (foreign key to shirts table)
size_id (foreign key to sizes table)
(you have compound primary key across these two fields)
An you would query it like
SELECT * (or whatever fields you need)
FROM shirts
INNER JOIN shirt_sizes ON shirts.shirt_id = shirt_sizes.shirt_id
INNER JOIN size ON shirt_sizes.size_id = sizes.size_id

With the following table structure:
CREATE TABLE `shirt` (
`id` INTEGER NOT NULL,
`name` VARCHAR(32),
PRIMARY KEY( `id` )
);
CREATE TABLE `size` (
`id` INTEGER NOT NULL,
`name` VARCHAR(4),
PRIMARY KEY( `id` )
);
CREATE TABLE `shirt_size` (
`shirtId` INTEGER NOT NULL,
`sizeId` INTEGER NOT NULL,
PRIMARY KEY( `shirtId`, `sizeId` ),
FOREIGN KEY( `shirtId` ) REFERENCES `shirt`( `id` ),
FOREIGN KEY( `sizeId` ) REFERENCES `size`( `id` )
);
And this data:
INSERT INTO
`shirt` ( `id`, `name` )
VALUES
( 1, "vneck" ),
( 2, "scoop neck" );
INSERT INTO
`size` ( `id`, `name` )
VALUES
( 1, "xs" ), ( 2, "s" ), ( 3, "m" ),
( 4, "l" ), ( 5, "1x" ), ( 6, "2x" ), ( 7, "3x" );
INSERT INTO
`shirt_size` ( `shirtId`, `sizeId` )
VALUES
( 1, 2 ), ( 1, 3 ), ( 1, 4 ), ( 1, 5 ),
( 2, 1 ), ( 2, 2 ), ( 2, 3 ), ( 2, 4 ), ( 2, 5 ), ( 2, 6 ), ( 2, 7 );
In MySQL you could do:
SELECT
`shirt`.`id`,
`shirt`.`name`,
GROUP_CONCAT( `size`.`name` ) as `sizes`
FROM
`shirt`
JOIN
`shirt_size`
ON `shirt_size`.`shirtId` = `shirt`.`id`
JOIN
`size`
ON `size`.`id` = `shirt_size`.`sizeId`
GROUP BY `shirt`.`id`;
Which would result in something like:
+----+------------+-------------------+
| id | name | sizes |
+----+------------+-------------------+
| 1 | vneck | s,m,l,1x |
+----+------------+-------------------+
| 2 | snoop neck | xs,s,m,l,1x,2x,3x |
+----+------------+-------------------+
Not sure if other RDBMS's have aggregate functions similar to MySQL's GROUP_CONCAT(). If not, then use something like:
SELECT
`shirt`.`id`,
`shirt`.`name` as `shirtName`,
`size`.`name` as `sizeName`
FROM
`shirt`
JOIN
`shirt_size`
ON `shirt_size`.`shirtId` = `shirt`.`id`
JOIN
`size`
ON `size`.`id` = `shirt_size`.`sizeId`;
Which will give you multiple rows for every size, with each shirt.

Related

MySQL query to get sum of differences between column and AVG of same column

I have a table which contains the scores by user, for a game:
UserID (Integer)
MatchId (Integer)
Score (Double)
I'd like to getter sum each user's "points above average" (PAA) - the
amount by which a user's score was above or below the average.
So you'd need to calculate the average of 'Score' for each 'MatchId',
then for each row in the table calculate the amount by which the
'Score' differs from the match average. And then sum that PAA value by
user.
Is it possible to do this via a MySQL query? Or do I need PHP? If it can be done by query, what would that query look like?
plan
compute avg scores by match
join user scores to avg scores and compute sum of derived difference field by userid
setup
create table scores
(
UserID integer not null,
MatchId integer not null,
Score decimal(5, 2) not null,
primary key ( UserID, MatchId )
);
insert into scores
( UserID, MatchId, Score )
values
( 1, 1, 22.1 ),
( 2, 1, 36.0 ),
( 3, 1, 35.3 ),
( 1, 2, 50.0 ),
( 2, 2, 39.8 ),
( 3, 2, 42.0 )
;
query
select s.UserID, sum(s.Score - avgs.avg_score) as paa
from scores s
inner join
(
select MatchId, avg(Score) as avg_score
from scores
group by MatchId
) avgs
on s.MatchId = avgs.MatchId
group by s.UserID
;
output
+--------+-----------+
| UserID | paa |
+--------+-----------+
| 1 | -2.966666 |
| 2 | 0.733334 |
| 3 | 2.233334 |
+--------+-----------+
sqlfiddle

How to join with multiple tables in MySQL?

The image of the table relation can be found at image.
-- Table structure for table `area`
CREATE TABLE `area` (
`area_id` int(10) NOT NULL auto_increment,
`area_name` varchar(255) NOT NULL,
PRIMARY KEY (`area_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `area` (`area_id`, `area_name`) VALUES
(1, 'Area 1'),
(2, 'Area 2'),
(3, 'Area 3'),
(4, 'Area 4');
-- Table structure for table `fruits`
CREATE TABLE `fruits` (
`fruit_id` int(10) NOT NULL auto_increment,
`fruit_name` varchar(255) NOT NULL,
`area_id` int(10) NOT NULL,
PRIMARY KEY (`fruit_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `fruits` (`fruit_id`, `fruit_name`, `area_id`) VALUES
(1, 'Apple', 1),
(2, 'Orange', 1),
(3, 'Mango', 2),
(4, 'Apricot', 3);
-- Table structure for table `vegetables`
CREATE TABLE `vegetables` (
`veg_id` int(10) NOT NULL auto_increment,
`veg_name` varchar(255) NOT NULL,
`area_id` int(10) NOT NULL,
PRIMARY KEY (`veg_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `vegetables` (`veg_id`, `veg_name`, `area_id`) VALUES
(1, 'Chickpea', 1),
(2, 'Drumstick', 4);
If I use the following query I get the output as below
SELECT
`area`.`area_name` AS AreaName
,COUNT(*) AS num
FROM
`area`
INNER JOIN `fruits`
ON (`fruits`.`area_id` = `area`.`area_id`)
GROUP BY `fruits`.area_id
UNION ALL
SELECT
`area`.`area_name` AS AreaName
,COUNT(*) AS num
FROM
`area`
INNER JOIN `vegetables`
ON (`vegetables` .`area_id` = `area`.`area_id`)
GROUP BY `vegetables`.area_id
AreaName num
Area 1 2
Area 2 1
Area 3 1
Area 1 1
Area 4 1
But I want the output to be like:
it should fetch all the areas which are present in vegetables and fruits and if the area is repeating in either fruits or vegetables it should return the total count of area_id by totalling the count of fruits and vegetables.. so the output will be like below
AreaName num
Area 1 3
Area 2 1
Area 3 1
Area 4 1
You can use a subselect over your query and use SUM() to add the counts for same area
SELECT t.AreaName ,SUM(t.num) num
FROM ( ....) t
GROUP BY t.AreaName
Fiddle Demo

Modify SQL to include extra table

How would I get the banner name? If you look at the DB below you will see that this bring back everything apart from the actual banner.name?
Also I presume that it should check that the banner status to check it is enabled.
BEFORE:
SELECT *
FROM banner_image bi
LEFT JOIN banner_image_description bid ON (bi.banner_image_id = bid.banner_image_id)
WHERE
bi.banner_id = '".$banner_id."'
AND bid.language_id = '".$this->config->get('config_language_id')."'
Array (
[0] => Array (
[banner_image_id] => 1
[banner_id] => 1
[link] =>
[image] => data/banners/test.jpg
[language_id] => 1
[title] => Test banner
)
)
AFTER:
SELECT
bi.*,
b.name
FROM
banner b,
banner_image bi
LEFT JOIN banner_image_description bid ON (bi.banner_image_id = bid.banner_image_id)
WHERE
b.banner_id = '".$banner_id."'
AND bi.banner_id = '".$banner_id."'
AND bid.language_id = '".$this->config->get('config_language_id')."'
Array (
[0] => Array (
[banner_image_id] => 1
[banner_id] => 1
[link] =>
[image] => data/banners/test.jpg
[name] => Banner heading
)
)
DB Structure:
CREATE TABLE IF NOT EXISTS `banner` (
`banner_id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(64) COLLATE utf8_bin NOT NULL,
`status` tinyint(1) NOT NULL,
PRIMARY KEY (`banner_id`)
);
CREATE TABLE IF NOT EXISTS `banner_image` (
`banner_image_id` int(11) NOT NULL AUTO_INCREMENT,
`banner_id` int(11) NOT NULL,
`link` varchar(255) COLLATE utf8_bin NOT NULL,
`image` varchar(255) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`banner_image_id`)
);
CREATE TABLE IF NOT EXISTS `banner_image_description` (
`banner_image_id` int(11) NOT NULL,
`language_id` int(11) NOT NULL,
`banner_id` int(11) NOT NULL,
`title` varchar(64) COLLATE utf8_bin NOT NULL,
PRIMARY KEY (`banner_image_id`,`language_id`)
);
I think this will do what you want:
SELECT *
FROM
banner b
INNER JOIN banner_image bi ON b.banner_id = bi.banner_id
INNER JOIN banner_image_description bid ON bi.banner_image_id = bid.banner_image_id
WHERE
b.banner_id = '". $banner_id ."'
AND b.status = TRUE
AND bid.language_id = '". $this->config->get('config_language_id') ."'
I would avoid using SELECT * and instead, explicitly list out each column you actually want to fetch.
One reason I think you were having trouble is that you used a comma (implicit join) to join in the banner table, but you didn't specify a join condition. You would have needed a condition in your WHERE clause like b.banner_id = bi.banner_id. But it would be better to use explicit INNER JOIN syntax.
I don't see a reason for using a LEFT JOIN instead of an INNER JOIN in this query. In the WHERE clause, you specify a condition that must be met in the banner_image_description table in order for a row to be returned. If there is no corresponding row in that table (which is the purpose of a LEFT JOIN), then there will be no row returned. So I switched them to INNER JOIN.

mysql left join takes too long

I have the following SQL Query:
SELECT
upd.*,
usr.username AS `username`,
usr.profile_picture AS `profile_picture`
FROM
updates AS upd
LEFT JOIN
subscribers AS sub ON upd.uid=sub.suid
LEFT JOIN
users AS usr ON upd.uid=usr.uid
WHERE
upd.deleted='0' && (upd.uid='118697835834' || sub.uid='118697835834')
GROUP BY upd.id
ORDER BY upd.date DESC
LIMIT 0, 15
where i get all user(118697835834) updates, his profile picture from another table using left join and also all his subscription users updates so can i show them in his newsfeed.
However as the updates get more and more so the query takes more time to load... right now using Codeigniter's Profiler i can see that the query takes 1.3793...
Right now i have created around 18k dummy accounts and subscribed from to me and vice versa so i can test the execution time... the times that i get are tragic considering that i am in localhost...
I also have some indexes where i suppose need more in the users table(username and uid as unique), updates table(update_id as unique and uid as index)
I suppose i am doing something wrong to get so bad results...
EDIT:
Running EXPLAIN EXTENDED result:
Array
(
[0] => stdClass Object
(
[id] => 1
[select_type] => SIMPLE
[table] => upd
[type] => ALL
[possible_keys] => i2
[key] =>
[key_len] =>
[ref] =>
[rows] => 22
[filtered] => 100.00
[Extra] => Using where; Using temporary; Using filesort
)
[1] => stdClass Object
(
[id] => 1
[select_type] => SIMPLE
[table] => sub
[type] => ALL
[possible_keys] =>
[key] =>
[key_len] =>
[ref] =>
[rows] => 18244
[filtered] => 100.00
[Extra] => Using where
)
[2] => stdClass Object
(
[id] => 1
[select_type] => SIMPLE
[table] => usr
[type] => eq_ref
[possible_keys] => uid
[key] => uid
[key_len] => 8
[ref] => site.upd.uid
[rows] => 1
[filtered] => 100.00
[Extra] =>
)
)
EDIT2: SHOW CREATE of Tables
Users table:
CREATE TABLE `users` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uid` bigint(20) NOT NULL,
`username` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`email` text CHARACTER SET latin1 NOT NULL,
`password` text CHARACTER SET latin1 NOT NULL,
`profile_picture_full` text COLLATE utf8_unicode_ci NOT NULL,
`profile_picture` text COLLATE utf8_unicode_ci NOT NULL,
`date_registered` datetime NOT NULL,
`activated` tinyint(1) NOT NULL,
`closed` tinyint(1) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uid` (`uid`),
UNIQUE KEY `username` (`username`)
) ENGINE=MyISAM AUTO_INCREMENT=23521 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Subscribers table:
CREATE TABLE `subscribers` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`sid` bigint(20) NOT NULL,
`uid` bigint(20) NOT NULL,
`suid` bigint(20) NOT NULL,
`date` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=18255 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Updates table:
CREATE TABLE `updates` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`update_id` bigint(19) NOT NULL,
`uid` bigint(20) NOT NULL,
`type` text COLLATE utf8_unicode_ci NOT NULL,
`update` text COLLATE utf8_unicode_ci NOT NULL,
`date` datetime NOT NULL,
`total_likes` int(11) NOT NULL,
`total_comments` int(11) NOT NULL,
`total_favorites` int(11) NOT NULL,
`category` bigint(20) NOT NULL,
`deleted` tinyint(1) NOT NULL,
`deleted_date` datetime NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `i1` (`update_id`),
KEY `i2` (`uid`),
KEY `deleted_index` (`deleted`)
) ENGINE=MyISAM AUTO_INCREMENT=23 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Try this one (without the GROUP BY):
SELECT
upd.*,
usr.username AS `username`,
usr.profile_picture AS `profile_picture`
FROM
updates AS upd
LEFT JOIN
users AS usr
ON upd.uid = usr.uid
WHERE
upd.deleted='0'
AND
( upd.uid='118697835834'
OR EXISTS
( SELECT *
FROM subscribers AS sub
WHERE upd.uid = sub.suid
AND sub.uid = '118697835834'
)
)
ORDER BY upd.date DESC
LIMIT 0, 15
At least the columns that are used in Joins should be indexed: updates.uid, users.uid and subscribers.suid.
I would also add an index on subscribers.uid.
Try:
SELECT
upd.*,
usr.username AS `username`,
usr.profile_picture AS `profile_picture`
FROM
updates AS upd
LEFT JOIN
subscribers AS sub ON upd.uid=sub.suid
LEFT JOIN
users AS usr ON upd.uid=usr.uid
WHERE
upd.deleted=0 and upd.uid in (118697835834,118697835834)
GROUP BY upd.id
ORDER BY upd.date DESC
LIMIT 0, 15
Note that ' has been removed from numeric values and bitwise operators changed to conventional operators.
don't use joins, try this one:
select *,
(select username from users where uid = upd.uid) as username,
(select profile_picture from users where uid = upd.uid) as profile_picture,
from updates as upd
WHERE
upd.deleted='0' && upd.uid='118697835834'
(not tested!)
maybe you have to check if there exists a subscriber in the where-clause with another sub-select.
Another way would be to make a join on sub-selects and not on the whole table. This may increase your performance also.
Shouldn't take too long to run; do you have an index on 'deleted'? What is the 'GROUP BY id' doing? Should it be UID? Can it come out, if ID is in fact just an auto increment, unique ID? (which would be expensive as well as pointless)
I think you'll be best separating this query into a select on the user table and then union those results with the select on the subscribers table.

Parse sql query

I have following query:
CREATE TABLE `test` (
`col1` INT( 10 ) NOT NULL ,
`col2` VARCHAR( 50 ) NOT NULL ,
`col3` DATE NOT NULL
) ENGINE = MYISAM ;
I want to write a general php script that get table name(test) from above query.
If you can access the string which gets queried before you run the query, you can do this:
preg_match("/^create table `(?P<tablename>)`/i", $query, $matches);
print_r($matches);
/*
Output:
Array
(
[0] => CREATE TABLE `test` (`col1` INT( 10 ) NOT NULL ,`col2` VARCHAR( 50 ) NOT NULL , `col3` DATE NOT NULL ) ENGINE = MYISAM ;
[tablename] => test
[1] => test
)
*/
If you can't access the string for some reason, but you know that nothing is created in the database between the query and your code then you can use this query to retrieve the last created table:
SELECT
*
FROM
information_schema.TABLES
ORDER BY
CREATE_TIME DESC
LIMIT
1;

Categories