How to turn rows into columns? - php

I have a database where I store keywords grouped into projects and data related to each keyword, then I display datagrids foreach project with one row per keyword and several columns all retrieved from the same table "data". I have 4 tables, keywords, projects, group_keywords and data. "keywords" only stores the keyword, "projects" the project name, "group_keywords" the keywords ids for the keywords assigned to that project, and "data" is where all the data foreach keyword goes, identified by a foreign key for the keywords.id, and a name column to identify the data name.
Now to retrieve the keywords + all the data for a project I use this query:
SELECT * FROM `group_keywords`
INNER JOIN keywords on keywords.id = keyword_id
INNER JOIN data ON data.id = keywords.id
WHERE `group_id` = (SELECT `id` FROM `projects` WHERE `name` = 'ProjectName'
This gives me something like
id group_id keyword_id id keyword id name value
12 5 52 52 absorption food 52 data_name_x1 6
12 5 52 52 absorption food 52 data_name_x2 8
12 5 52 52 absorption food 52 data_name_x3 26
12 5 52 52 absorption food 52 data_name_x4 2
...
But what I want is to get:
id group_id keyword_id id keyword id data_name_x1 data_name_x2 data_name_x3 data_name_x4
12 5 52 52 absorption food 52 6 8 26 2
...
So I can sort and use pagination for the datagrids easly, otherwise I have no idea how to do it, because when using big data sets I can't just dump everything into an array, too much data.
This is the schema:
-- --------------------------------------------------------
-- Table structure for table `keywords`
CREATE TABLE IF NOT EXISTS `keywords` (
`id` int(10) unsigned NOT NULL auto_increment,
`keyword` varchar(255) NOT NULL,
UNIQUE KEY `id` (`id`),
UNIQUE KEY `keyword` (`keyword`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=115386 ;
-- --------------------------------------------------------
-- Table structure for table `data`
CREATE TABLE IF NOT EXISTS `data` (
`id` int(10) unsigned NOT NULL,
`name` varchar(100) NOT NULL,
`value` varchar(15) NOT NULL,
UNIQUE KEY `id` (`id`,`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
-- Table structure for table `projects`
--
CREATE TABLE IF NOT EXISTS `projects` (
`id` int(10) NOT NULL auto_increment,
`name` varchar(100) NOT NULL,
`parent` varchar(100) default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=21 ;
-- --------------------------------------------------------
-- Table structure for table `group_keywords`
CREATE TABLE IF NOT EXISTS `group_keywords` (
`id` int(10) NOT NULL auto_increment,
`group_id` int(10) NOT NULL,
`keyword_id` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `group_id` (`group_id`,`keyword_id`),
KEY `keyword_id` (`keyword_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=119503 ;
-- --------------------------------------------------------
-- Constraints for table `data`
--
ALTER TABLE `data`
ADD CONSTRAINT `data_ibfk_1` FOREIGN KEY (`id`) REFERENCES `keywords` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;
-- --------------------------------------------------------
-- Constraints for table `group_keywords`
--
ALTER TABLE `group_keywords`
ADD CONSTRAINT `group_keywords_ibfk_1` FOREIGN KEY (`keyword_id`) REFERENCES `keywords` (`id`) ON DELETE CASCADE ON UPDATE CASCADE;

This operation is traditionally called "unpivot" and a few RDBMSs support it, but MySQL doesn't appear to be one of them. You have two options, do it in SQL or do it in PHP. In MySQL it looks something like this with self-joins (I don't know which field qualifies as an ID field for you, so forgive me creating my own example). From a performance perspective, make sure you index both the ID and Column Name, otherwise these joins will crawl.
shapes
ID Name Value
1 Color Red
1 Shape Circle
... for more "columns"
2 Color Green
2 Shape Square
... for more "columns"
SELECT
A.ID,
B.Value as Color,
C.Value as Shape
... for more "columns"
FROM shapes A
LEFT JOIN shapes B ON B.ID = A.ID AND B.Name = 'Color'
LEFT JOIN shapes C ON C.ID = A.ID AND C.Name = 'Shape'
... for more "columns"
Which should net us (unless my head-SQL-parser is misrunning tonight):
ID Color Shape
1 Red Circle
2 Green Square
For the PHP version, you don't necessarily have to load up an array, you can stream it. Sort by the PK and walk down it setting the properties. In pseudocode:
Set X to undefined
Get a Record
Check the ID property, if it's different than X, create a new object, set X to the new ID, and yield the previous object
Set the property of the object based on the "Name" column of our result
Hope this helps!

Related

SQL SELECT id WHERE emails are the same

I do not know if it's possible at all, but I have two tables, userBasic and carPlateConfidence, in carPlateConfidence I would like to insert id of userBasic where emails are matched.
$query .= "INSERT IGNORE INTO userBasic (id_uM, userNameG, userEmailG) values ((SELECT id_uM FROM userMore WHERE userEmailG='$userEmailG'),'$userNameG', '$userEmailG');";
$query .= "INSERT IGNORE INTO carPlateConfidence (emailConfid, id_uB,plateNumber, confidencePlate, plateNumberUn) values ('$userEmailG', (SELECT id_uB FROM userBasic WHERE userEmailG='(SELECT max(emailConfid) FROM carPlateConfidence)'), '$plateNumber','$confidencePlate', '$plateNumberUn');";
So if I have:
userBasic:
id_uM = 555;
userNameG = BlaBla;
userEmailG = blabla#blabla.com
And in this table I would like
carPlateConfidence:
emailConfid = blabla#blabla.com;
id_uB = 555
plateNumber = 1111
confidencePlate = 70
plateNumberUn = 2222
AND if email do not matched:
emailConfid = blabla2#blabla.com;
id_uB = NULL
plateNumber = 1111
confidencePlate = 70
plateNumberUn = 222
P>S> Currently I have tried this, to select id from userBasic:
(SELECT id_uB FROM userBasic WHERE userEmailG='(SELECT max(emailConfid) FROM carPlateConfidence)')
id_uB in carPlateConfidence is set as foreign key;
Tables:
--
-- Table structure for table `carPlateConfidence`
--
DROP TABLE IF EXISTS `carPlateConfidence`;
CREATE TABLE IF NOT EXISTS `carPlateConfidence` (
`id_cof` int(11) NOT NULL AUTO_INCREMENT,
`id_uB` int(11) NOT NULL,
`emailConfid` varchar(50) NOT NULL,
`plateNumber` varchar(10) NOT NULL,
`confidencePlate` varchar(10) DEFAULT NULL,
`plateNumberUn` varchar(10) DEFAULT NULL,
PRIMARY KEY (`id_cof`),
KEY `id_uB` (`id_uB`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
-- --------------------------------------------------------
--
-- Table structure for table `userBasic`
--
DROP TABLE IF EXISTS `userBasic`;
CREATE TABLE IF NOT EXISTS `userBasic` (
`id_uB` int(11) NOT NULL AUTO_INCREMENT,
`id_uM` int(11) NOT NULL,
`userNameG` varchar(50) NOT NULL,
`userEmailG` varchar(50) NOT NULL,
PRIMARY KEY (`id_uB`),
UNIQUE KEY `userEmailG` (`userEmailG`),
KEY `id_uM` (`id_uM`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=119 ;
--
-- Constraints for dumped tables
--
--
-- Constraints for table `carPlateConfidence`
--
ALTER TABLE `carPlateConfidence`
ADD CONSTRAINT `carPlateConfidence_ibfk_1` FOREIGN KEY (`id_uB`) REFERENCES `userBasic` (`id_uB`);
--
-- Constraints for table `userBasic`
--
ALTER TABLE `userBasic`
ADD CONSTRAINT `userBasic_ibfk_1` FOREIGN KEY (`id_uM`) REFERENCES `userMore` (`id_uM`);
So you want an update, not an insert :
UPDATE carPlateConfidence t
SET t.id_uB = (SELECT distinct s.id_uM FROM userBasic s
WHERE s.userEmailG = t.emailConfid)
This will work only if there can be only 1 match, if there can be more then one match you should specify which one you want, if it doesn't matter, either use MAX() or limit :
UPDATE carPlateConfidence t
SET t.id_uB = (SELECT max(s.id_uM) FROM userBasic s
WHERE s.userEmailG = t.emailConfid)

PHP MySQL query: match two tables with percentage match

I have two tables with 'skills', the first one contains the skills of an employee, the second table contains the 'skills' of a company.
Table employee skills
CREATE TABLE `employee_skills` (
`e_id` int(8) NOT NULL,
`skill` int(8) NOT NULL,
`points` enum('0','25','50','75','100') COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`u_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Table company skills
CREATE TABLE `company_skills` (
`c_id` int(8) NOT NULL,
`skill` int(8) NOT NULL,
`points` enum('0','25','50','75','100') COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`u_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
So if a company has 100 points for the skill Windows and a employee 25 points, the match percentage is 25%. I'm using the following query to get the results:
$result = $connection->prepare("select u_id, skill, points, COUNT(points) from employee_skills");
The expected result should match the certain skills and return a percentage similar to:
skill | match
windows | 25%
linux | ...%
mac | ...%
Try below:
SELECT e_id, e.skill, c.skill, (e.points/c.points)*100 from employee_skills as e, company_skills as c
WHERE e.skill = c.skill
GROUP BY e.e_id,e.skill
See below:
SELECT c.skill, total_emp_points, c.points as total,
(total_emp_points/c.points)*100 as percnt FROM company_skills AS c,
(SELECT e.skill, SUM(e.points) as total_emp_points
FROM employee_skills as e
GROUP BY e.skill) as emp
WHERE c.skill = emp.skill;
see SqlFiddle

sql query is slow

I have a phpmyadmin database with 1 000 000 record i need to search in. Every week there are 500 000 records added.
so, this is what I need:
location_id value date time name lat lng
3 234 2011-11-18 19:50:00 Amerongen beneden 5.40453 51.97486
4 594 2011-11-18 19:50:00 Amerongen boven 5.41194 51.97507
I do this with this query:
SELECT location_id, value, date, time, locations.name, locations.lat, locations.lng FROM
(
SELECT location_id, value, date, time from `measurements`
LEFT JOIN units ON (units.id = measurements.unit_id)
WHERE units.name='Waterhoogte'
ORDER BY measurements.date DESC, measurements.time DESC
) as last_record
LEFT JOIN locations on (locations.id = location_id)
GROUP BY location_id
which takes 30 seconds. How can I improve this? This is my structure:
CREATE TABLE IF NOT EXISTS `locations` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
`code` varchar(255) NOT NULL,
`lat` varchar(10) NOT NULL,
`lng` varchar(10) NOT NULL,
`owner_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=244 ;
-- --------------------------------------------------------
--
-- Table structure for table `measurements`
--
CREATE TABLE IF NOT EXISTS `measurements` (
`id` int(11) NOT NULL auto_increment,
`date` date NOT NULL,
`time` time NOT NULL,
`value` varchar(255) NOT NULL,
`location_id` int(11) NOT NULL,
`unit_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=676801 ;
-- --------------------------------------------------------
--
-- Table structure for table `owner`
--
CREATE TABLE IF NOT EXISTS `owner` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
-- --------------------------------------------------------
--
-- Table structure for table `units`
--
CREATE TABLE IF NOT EXISTS `units` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
`description` text NOT NULL,
`unit_short` varchar(255) NOT NULL,
`owner_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=44 ;
What is the limit what phpmyadmin can handle?
Create an index on units.name specifically is a good start.
You should also really rethink the amount of data you are pulling back.
Is someone really going to sift through that many records. Change your query to limit the number of records and think of a UI interface that involves a paging mechanism.
you need to put an index or unique index on units.name.
Add the following indexes:
A composite index (a covering index) on unit.name and unit.id.
A composite index of measurements.date and measurements.time.
An index on location.id
You should try creating an index on units.name as a first step. But understand that there is a tradeoff with an index - read operations will be faster, but it can slow down write operations. If you're concerned about that, or if you're affected by slow writes, then you may want to try creating the index on a smaller number of characters in units.name.
For instance, to declare an index on the first 12 characters of units.name, you'd declare the following:
CREATE INDEX first_twelve ON units (name(12));
Again, this may not be necessary if you don't notice any ill effects from just throwing an index on, but it's something to keep in mind.
SELECT measurements.location_id, measurements.value, measurements.date, measurements.time, locations.name, locations.lat, locations.lng
FROM measurements
LEFT JOIN units ON units.id = measurements.unit_id
LEFT JOIN locations ON locations.id = measurements.location_id
WHERE units.id = 4
GROUP BY measurements.location_id
ORDER BY measurements.date DESC, measurements.time DESC

MySQL / PHP - Conversation System

I'm having a really hard time figuring this one out. I am trying to make a conversation system and it would work like this:
A logged in user would have a link to "conversations". On that page a list of all conversation.titles would show up. When a conversation-name is clicked the conversation messages (mostly between 2 users, but could be more) will show up like this:
Conversation tiltle...
|------------------------------------------------------------|
|Send-button| Textfield for new message here |
|----------------|-------------------------------------------|
Sender: Hi....... (08/24/11 - 12PM)
receiver: Hi!..... (08/24/11 - 11PM
Sender: bla blah... (08/24/11 - 10PM)
etc..
I have no problem listing messages in the conversation itself, but rather getting the list of conversations a user participates in.
I get multiple results of the same instance.
I would like a query that produces a list of all conversations a logged in user has participated in either by sending a message, recieving a message or both.
I guess i would need a nested query, but i dont know how to do that. So i'm a bit stuck here, trying to inner-join all tables doesnt cut the cheese.
This is the database mysql design / ER-modell:
-- -----------------------------------------------------
-- Table `firm`.`conversation`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`conversation` (
`conversation_id` INT NOT NULL AUTO_INCREMENT ,
`title` VARCHAR(45) NULL ,
`date` TIMESTAMP NULL ,
PRIMARY KEY (`subject_id`) )
ENGINE = InnoDB;
-- -----------------------------------------------------
-- Table `firm`.`message`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`message` (
`message_id` INT NOT NULL AUTO_INCREMENT ,
`message` LONGTEXT NULL ,
`date` TIMESTAMP NOT NULL ,
`read_sender` BINARY NOT NULL ,
`read_receiver` BINARY NOT NULL ,
`deleted_receiver` BINARY NOT NULL ,
`deleted_sender` BINARY NOT NULL ,
`conversation_id` INT NOT NULL ,
PRIMARY KEY (`message_id`) ,
INDEX `fk_message_subject1` (`subject_id` ASC) ,
CONSTRAINT `fk_message_con1`
FOREIGN KEY (`conversation_id` )
REFERENCES `firm`.`conversation` (`conversation_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
DEFAULT CHARACTER SET = latin1
COLLATE = latin1_swedish_ci;
-- -----------------------------------------------------
-- Table `firm`.`outbox`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`outbox` (
`outbox_id` INT NOT NULL AUTO_INCREMENT ,
`user_id` INT(10) NOT NULL ,
`conversation_id` INT(10) NOT NULL ,
`date` TIMESTAMP NOT NULL ,
PRIMARY KEY (`outbox_id`) ,
INDEX `fk_outbox_users1` (`user_id` ASC) ,
INDEX `fk_utbox_con1` (`conversation_id` ASC) ,
CONSTRAINT `fk_outbox_users1`
FOREIGN KEY (`user_id` )
REFERENCES `firm`.`users` (`user_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_outbox_con1`
FOREIGN KEY (`conversation_id` )
REFERENCES `firm`.`conversation` (`conversation_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
-- -----------------------------------------------------
-- Table `firm`.`inbox`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `firm`.`inbox` (
`inbox_id` INT NOT NULL ,
`user_id` INT(10) NOT NULL ,
`message_id` INT NOT NULL ,
`read` BINARY NOT NULL ,
`date` TIMESTAMP NOT NULL ,
PRIMARY KEY (`inbox_id`) ,
INDEX `fk_inbox_users1` (`user_id` ASC) ,
INDEX `fk_inbox_message` (`message_id` ASC) ,
CONSTRAINT `fk_inbox_users1`
FOREIGN KEY (`user_id` )
REFERENCES `firm`.`users` (`user_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION,
CONSTRAINT `fk_inbox_message1`
FOREIGN KEY (`message_id` )
REFERENCES `frim`.`message` (`message_id` )
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;
SELECT DISTINCT conversation.conversation_id
FROM conversation
INNER JOIN message ON message.conversation_id = conversation.conversation_id
INNER JOIN inbox ON inbox.message_id = message.message_id
INNER JOIN outbox ON inbox.message_id = message.message_id
WHERE outbox.user_id = #USER#
OR inbox.user_id = #USER#
This join should work. DISTINCT because a user is likely to have participated as a sender and a recipient.

Finds related data from mysql table

These are the two table structure of my db
-- Table structure for table `gf_actor`
CREATE TABLE IF NOT EXISTS `gf_actor` (
`actor_id` bigint(20) NOT NULL auto_increment,
`actor_name` varchar(100) default NULL,
PRIMARY KEY (`actor_id`),
UNIQUE KEY `actor_name` (`actor_name`)
) ENGINE=MyISAM;
-- Table structure for table `gf_film_actor`
CREATE TABLE IF NOT EXISTS `gf_film_actor` (
`film_id` int(20) NOT NULL,
`actor_id` int(20) NOT NULL,
KEY `film_id` (`film_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
So i need a query which fetches five related actors name and id from gf_actor table who is having acted in movies which was performed by the actor_id lets say actor_id=1 and movies must be different that means five related actors must be acted in different movies with the actor_id=1
Try this
select ga.actor_id, actor_name from gf_actor ga inner join gf_film_actor gf on ga.actor_id = gf.actor_id where gf.film_id in (select gf1.film_id from gf_film_actor gf1 where gf1.actor_id=1) AND gf.actor_id != 1 LIMIT 5
You have wrong table structure. It should be like:
CREATE TABLE IF NOT EXISTS `gf_actor` (
`actor_id` bigint(20) NOT NULL auto_increment,
`actor_name` varchar(100) default NULL,
PRIMARY KEY (`actor_id`),
UNIQUE KEY `actor_name` (`actor_name`)
) ENGINE=MyISAM;
CREATE TABLE IF NOT EXISTS `gf_film` (
`film_id` int(20) NOT NULL,
`performed_by_actor_id` bigint(20) NOT NULL,
KEY `film_id` (`film_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
CREATE TABLE IF NOT EXISTS `gf_film_actor` (
`film_id` int(20) NOT NULL,
`actor_id` bigint(20) NOT NULL,
KEY `film_actor_id` (`film_id`, `actor_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
And now you can create your query like this:
SELECT a.*
FROM `gf_film` AS f
LEFT JOIN `gf_film_actor` AS fa ON f.`film_id` = fa.`film_id`
LEFT JOIN `gf_actor` AS a ON fa.`actor_id` = a.`actor_id`
WHERE f.`performed_by_actor_id` = 1
LIMIT 0, 5;

Categories