Dynamic Columns in SQL Select statement, keeping "undefined" values - mysql

This is a new Question based on my previos question, which was marked as beeing a "duplicate" of the question
mySQL - Create a New Table Using Data and Columns from Three Tables
This question looks very similiar, but has one essential part which is different
I tried to use the solutions from the linked, but the Ansers does not fit to my problem.
My personal SQL skills are limited. After looking around for several days, I did not find any working solution for my database query problem below. I attached the full example Database SQL-text at the end of this question.
My example database (made with MariaDB) contains two tables:
items, and
itemproperties.
For each item only an item.ID and an item.Name is defined. (In a real life example, the name would be defined to be unique.)
For each item an dynamic user defined set of properties is possible. These properties are defined as a name-value-pair.
For example, for an item named "Banana" a property "Color" with the value "yellow" may exists.
It is only valid to have one "Color" property for one item, so that not two different colors could be assigned to one item.
(In my real world problem the property names contains only two characters, so an additional property name table is not necessary, and subsequently for the ease of showing the problem not used in the example).
The example data for the items table:
ID, Name
1, Car
2, House
3, Homer
4, Earth
And a total of nine properties for the items above are defined. The entry "(NULL)" are indicating that this property is not defined for a given item
ItemID, ItemName, Color, Speed, Price
1, Car, blue, 200, 50000
2, House, red, (NULL), 250000
3, Homer, yellow, 5, (NULL)
4, Earth, blue, 108000, (NULL)
Unfortunally my select statement
SELECT items.ID as ItemID, items.Name as ItemName,
CASE WHEN (itemproperties.Name = 'Color')
THEN itemproperties.Value
#ELSE NULL
END as Color,
CASE WHEN (itemproperties.Name = 'Speed')
THEN itemproperties.Value
#ELSE NULL
END as Speed,
CASE WHEN (itemproperties.Name = 'Price')
THEN itemproperties.Value
#ELSE NULL
END as Price
FROM items left join itemproperties
ON (items.ID=itemproperties.ItemID)
returns the data like this
ItemID, ItemName, Color, Speed, Price
1, Car, blue, (NULL), (NULL)
1, Car, (NULL), 200, (NULL)
1, Car, (NULL), (NULL), 50000
2, House, red, (NULL), (NULL)
2, House, (NULL), (NULL), 250000
3, Homer, yellow, (NULL), (NULL)
3, Homer, (NULL), 5, (NULL)
4, Earth, blue, (NULL), (NULL)
4, Earth, (NULL), 108000, (NULL)
Question: How to write the select statement to get tha data in a collated form, one row for each item?
According too the linked question above, I tried also the following approach
SELECT i.ID as ItemID, i.Name as ItemName,
p1.Value AS Color, p2.Value AS Speed, p3.Value AS Price
FROM items as i
JOIN itemproperties AS p1 ON (i.ID=p1.ItemID)
JOIN itemproperties AS p2 ON (i.ID=p2.ItemID)
JOIN itemproperties AS p3 ON (i.ID=p3.ItemID)
WHERE (p1.Name = 'Color') and (p2.Name = 'Speed') and (p3.Name = 'Price')
But the result is only one line:
ItemID, ItemName, Color, Speed, Price
1, Car, blue, 200, 50000
The reason for this behavior is, that onle the item "Car" have all the three properties "Color", "Speed" and "Price" filled with values.
The item "House" is skipped because it has a "Color" and a "Price", but obviously no "Speed".
So how to get the table in the wanted manner?
Greetings Ekkehard
Database definition:
-- --------------------------------------------------------
-- Host: 127.0.0.1
-- Server Version: 10.1.13-MariaDB - mariadb.org binary distribution
-- Server Betriebssystem: Win32
-- HeidiSQL Version: 9.4.0.5125
-- --------------------------------------------------------
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET #OLD_FOREIGN_KEY_CHECKS=##FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET #OLD_SQL_MODE=##SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
-- Exportiere Datenbank Struktur für DynamicColTest
CREATE DATABASE IF NOT EXISTS `dynamiccoltest` /*!40100 DEFAULT CHARACTER SET latin1 */;
USE `DynamicColTest`;
-- Exportiere Struktur von Tabelle DynamicColTest.itemproperties
CREATE TABLE IF NOT EXISTS `itemproperties` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique ID of the property',
`ItemID` int(10) unsigned DEFAULT '0' COMMENT 'ID of the Item this property belongs to',
`Name` varchar(20) DEFAULT '0' COMMENT 'Name of the property',
`Value` varchar(20) DEFAULT '0' COMMENT 'Value of the property',
UNIQUE KEY `Schlüssel 3` (`Name`,`ItemID`),
KEY `Schlüssel 1` (`ID`),
KEY `FK_itemproperties_items` (`ItemID`),
CONSTRAINT `FK_itemproperties_items` FOREIGN KEY (`ItemID`) REFERENCES `items` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=latin1 COMMENT='The properties of the items';
-- Exportiere Daten aus Tabelle DynamicColTest.itemproperties: ~9 rows (ungefähr)
DELETE FROM `itemproperties`;
/*!40000 ALTER TABLE `itemproperties` DISABLE KEYS */;
INSERT INTO `itemproperties` (`ID`, `ItemID`, `Name`, `Value`) VALUES
(1, 1, 'Color', 'blue'),
(1, 4, 'Color', 'blue'),
(1, 2, 'Color', 'red'),
(2, 3, 'Color', 'yellow'),
(3, 1, 'Speed', '200'),
(3, 4, 'Speed', '108000'),
(4, 3, 'Speed', '5'),
(5, 1, 'Price', '50000'),
(5, 2, 'Price', '250000');
/*!40000 ALTER TABLE `itemproperties` ENABLE KEYS */;
-- Exportiere Struktur von Tabelle DynamicColTest.items
CREATE TABLE IF NOT EXISTS `items` (
`ID` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique Item ID',
`Name` varchar(25) DEFAULT '0' COMMENT 'Name of the Item',
KEY `Schlüssel 1` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1 COMMENT='Contains all Items, with a minimum of definitions';
-- Exportiere Daten aus Tabelle DynamicColTest.items: ~4 rows (ungefähr)
DELETE FROM `items`;
/*!40000 ALTER TABLE `items` DISABLE KEYS */;
INSERT INTO `items` (`ID`, `Name`) VALUES
(1, 'Car'),
(2, 'House'),
(3, 'Homer'),
(4, 'Earth');
/*!40000 ALTER TABLE `items` ENABLE KEYS */;
/*!40101 SET SQL_MODE=IFNULL(#OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(#OLD_FOREIGN_KEY_CHECKS IS NULL, 1, #OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT */;

You are very close. You need to join your key/value table itemproperties once for each distinct key (property) you need to retrieve. The thing is, you need to use LEFT JOIN. Plain inner JOIN suppresses the output row when the join criterion is unmet.
Try this.
SELECT i.ID as ItemID, i.Name as ItemName,
p1.Value AS Color, p2.Value AS Speed, p3.Value AS Price
FROM items as i
LEFT JOIN itemproperties AS p1 ON (i.ID=p1.ItemID) AND (p1.Name = 'Color')
LEFT JOIN itemproperties AS p2 ON (i.ID=p2.ItemID) AND (p2.Name = 'Speed')
LEFT JOIN itemproperties AS p3 ON (i.ID=p3.ItemID) AND (p3.Name = 'Price')
The expressions selecting the Name values (z.B. p3.Name = 'Price') go in your ON clause rather than your WHERE clause.

The below answer is for SQL SERVER but I hope it could be of some help to you. I am using PIVOT function to achieve the result.You particularly can a similar function in MariaDB.
WITH cte as(
SELECT i.ID as ItemID, i.Name as ItemName,p1.name,p1.value
FROM items as i
JOIN itemproperties AS p1 ON (i.ID=p1.ItemID)
)
select * from cte
PIVOT
(
max(value) for Name in ([Color],[Speed],[Price])
)A
REXTESTER DEMO

Related

Getting common values in a table based on a subquery

I couldn't find what I am looking for even though it seems like a 'simple' thing to do in SQL.
So here it is,
I have a table containing dates and countries, let's call it A.
I have a subquery that selects a list of countries from a table C, let's call it B.
The goal is to return only and distinctively the dates from A that have every single country in B
For example,
A B (SELECT FROM C WHERE ...)
date country country
2020-07-21 1 1
2020-07-21 2 2
2020-07-12 1
2020-07-12 2
2020-07-06 1
2020-07-06 2
2020-07-06 3
Should return
date
2020-07-21
2020-07-12
2020-07-06
But if B was
B
country
1
2
3
then it should return
2020-07-06
The whole point is to get all the dates that have all the country listed in the B list
I tried
SELECT DISTINCT T.date FROM (
SELECT date
FROM A
WHERE country = ALL (SELECT country FROM C WHERE ...)
) AS T
But it doesn't return anything because I think it only returns the list if it is true for everything.
Here is an MCRE:
/*!40101 SET #saved_cs_client = ##character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `countries` (
`country_id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`country_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = #saved_cs_client */;
/*!40101 SET #saved_cs_client = ##character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `projects` (
`project_id` int(11) NOT NULL AUTO_INCREMENT,
`project_date` date NOT NULL,
`country_id` int(11) NOT NULL,
PRIMARY KEY (`project_id`),
KEY `country_id` (`country_id`),
CONSTRAINT `projects_ibfk_1` FOREIGN KEY (`country_id`) REFERENCES `countries` (`country_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;
/*!40101 SET character_set_client = #saved_cs_client */;
INSERT INTO countries VALUES (1);
INSERT INTO countries VALUES (2);
INSERT INTO countries VALUES (3);
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-21', 1);
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-21', 2);
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-12', 1)
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-12', 2)
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-06', 1);
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-06', 2);
INSERT INTO projects(project_date, country_id) VALUES ('2020-07-06', 3);
Then this is what I try to do
SELECT project_id, project_date
from projects
where country_id = all (select country_id from countries where country_id in (1,2))
group by project_date
I know the subquery is useless here but in reality, the condition is different from IN (1,2) but this simplifies it without losing the meaning of what I'm trying to do.
In this database, with (1,2) in the subquery, it should return every date as they all have the country_id 1 and 2. However, if it was set to (1,2,3) it should only return '2020-07-06' as a group because it is the only date with the country_id 1,2 and 3
Assuming that your query that returns the countries from C is something like:
select country_id from countries where country_id in (....)
then use it like this:
select project_date
from projects
where country_id in (select country_id from countries where country_id in (...))
group by project_date
having count(*) = (select count(*) from countries where country_id in (...))
If there are duplicate countries in projects then change the HAVING clause to:
having count(distinct country_id) = (select count(*) from countries where country_id in (...))
See the demo.
You can try the below way -
select date
from tablename
group by date
having count(distinct country)=(select count(distinct country) from countryTable)

One column in a table referring to another column where there are duplicate data in SQL

I want to build simple database system about bike (or bicycle) rental store.
Here are the tables, and columns in parantheses:
Model (MNr, Modelname, dayprice)
Bike (MNr, CopyNr, Frame, Color)
Customer (CNr, Name, Surename, MobileNr)
Rental (CNr, MNr, CopyNr, DateOut, DateIn)
(MNr stands for model number, CNr stands for customer number).
Models are bike models, Bike table shows data on each particular bike.
There can be several bikes of each model, to distinguish between them
"CopyNr" column is used, where each bike in a given model is numbered 1, 2, 3, etc.
Rental: A rental relationship always applies to only one bike and one customer.
So I put the following primary and foreign keys:
Model (MNr (PK), Modelname, dayprice)
Bike (MNr (FK), CopyNr (PK), Frame, Color)
Customer (CNr (PK), Firstame, Surname, MobileNr)
Rent (CNr (FK), MNr (FK), CopyNr (FK), DateOut, DateIn)
The "CopyNr" at "Rental" is referring to the "CopyNr" of "Bikes". So I used "CopyNr" as
primary key column.
But that column can contain duplicate values, like there can be copy nr 2 of model A and copy nr 2 of
model B.
And primary key columns don't allow duplicate values.
I wrote the following in the SQL file, and when but it on PHPMyAdmin.
Here's the code from SQL code:
[It had Norwegian names, I changed it to English, thus color names are Norwegian]
-- phpMyAdmin SQL Dump
-- version 4.8.4
-- https://www.phpmyadmin.net/
--
-- Host: 127.0.0.1:3306
-- Generation Time: Feb 04, 2020 at 07:15 PM
-- Server version: 10.2.26-MariaDB-log
-- PHP Version: 7.1.30
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET AUTOCOMMIT = 0;
START TRANSACTION;
SET time_zone = "+01:00";
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET #OLD_CHARACTER_SET_RESULTS=##CHARACTER_SET_RESULTS */;
/*!40101 SET #OLD_COLLATION_CONNECTION=##COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
--
--
-- --------------------------------------------------------
--
-- Table structure for table `Customer`
--
CREATE TABLE `Customer` (
`CNr` int(2) NOT NULL,
`Firstname` varchar(20),
`Surname` varchar(20),
`MobileNr` varchar(8),
PRIMARY KEY (CNr)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Customer` (`CNr`, `Firstname`, `Surname`, `MobileNr`) VALUES
(1, 'Olav', 'Petterses', '88888888'),
(2, 'Petter', 'Olavsson', '44444444');
-- --------------------------------------------------------
--
-- Table structure for table `Model`
--
CREATE TABLE `Model` (
`MNr` int(5) NOT NULL,
`Modelname` varchar(20),
`Dayprice` double DEFAULT NULL,
PRIMARY KEY (MNr)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Model` (`MNr`, `Modelname`, `Dayprice`) VALUES
(2, 'FirstPrice', 10),
(15, 'DBS', 50),
(16, 'DBS', 60);
-- --------------------------------------------------------
--
-- Table structure for table `Bike`
--
CREATE TABLE `Bike` (
`MNr` int(5),
`CopyNr` int(2) NOT NULL,
`Frame` int(3),
`Color` varchar(10),
PRIMARY KEY (`CopyNr`, `MNr`),
CONSTRAINT `Bike_ibfk_1` FOREIGN KEY (`MNr`) REFERENCES `Model` (`MNr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Bike` (`MNr`, `CopyNr`, `Frame`, `Color`) VALUES
(2, 1, 55, 'rød'),
(15, 1, 65, 'rød'),
(16, 1, 55, 'grønn'),
(2, 2, 55, 'rød'),
(15, 2, 65, 'grønn');
-- --------------------------------------------------------
--
-- Table structure for table `Rent`
--
CREATE TABLE `Rent` (
`CNr` int(2),
`MNr` int(5),
`CopyNr` int(2),
`DatoUt` date,
`DateInn` date,
CHECK (`DatoUt` < `DateInn`),
CONSTRAINT `Rent_ibfk_1` FOREIGN KEY (`MNr`) REFERENCES `Model` (`MNr`),
CONSTRAINT `Rent_ibfk_2` FOREIGN KEY (`CopyNr`) REFERENCES `Bike` (`CopyNr`),
CONSTRAINT `Rent_ibfk_3` FOREIGN KEY (`CNr`) REFERENCES `Customer` (`CNr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `Rent` (`CNr`, `MNr`, `CopyNr`, `DatoUt`, `DateInn`) VALUES
(1, 15, 1, '2020-01-01', '2020-01-30'),
(2, 15, 2, '2020-02-15', '2020-02-29');
/*!40101 SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=#OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=#OLD_COLLATION_CONNECTION */;
When I put it on SQL on PHPAdmin and click "GO", I got error because of CHECK ( DatoIn > DateUt) code.
And I remove that part, then I get "Rent" table empty of values.
What's wrong am I doing?
Why is the "Rent" table empty of values when I put this on SQL on PHPMyAdmin?
Should I restructure the table relations?
And why "CHECK" doesn't work?
The error message I get where I use CHECK:
I believe you're seeing the syntax warning due to a bug in the parser used by phpMyAdmin which improperly marks this syntax as incorrect. I think that bug specifically is already reported as https://github.com/phpmyadmin/sql-parser/issues/167.
As nick indicates, you can still use the query despite the syntax warning.
I think that the date out should've be less than the date in
CHECK (DatoUt < DateInn),

SQL Query is grabbing double the results

I have an SQL Query that is grabbing the db for movies. This SQL query is only for showing the users history:
$movie = $db2->query("SELECT m.id AS mid,m.photo AS pho,
m.destination AS des,m.length AS len,
m.length_content AS lenc,m.description AS desa,m.rating AS rat,
m.files AS fil
FROM movies m
INNER JOIN history h ON h.movie_id = m.id
WHERE h.user_id = $id3
ORDER BY h.id
LIMIT $start, $per_page
");
while($movie3 = mysqli_fetch_array($movie)) {
$number++;
$id2 = $movie3["mid"];
$photo = $movie3["pho"];
$destination = $movie3["des"];
$length = $movie3["len"];
$length_content = $movie3["lenc"];
$description = $movie3["desa"];
$rating = $movie3["rat"];
$files = $movie3["fil"];
$friends_one = $db2->query("SELECT * FROM likes WHERE number_likes='$id2' LIMIT 2");
while($movie4 = mysqli_fetch_array($friends_one)) {
...
}
}
I have results that are doubling, it is not a problem with the server or db information. This is a photo of the problem:
This next image is going to show what the results are suppose to look like. This SQL Query follows the same db outline and while loops as history but uses a slightly different SQL query due to the structure of the db.
Code Associated with second image:
$movie = $db2->query("SELECT *
FROM movies m
INNER JOIN likes h ON h.number_likes = m.id
WHERE h.user = $id3
ORDER BY h.id
LIMIT $start, $per_page");
while($movie3 = mysqli_fetch_array($movie)) {
$number++;
$id2 = $movie3["id"];
$photo = $movie3["photo"];
$rating = $movie3["rating"];
$destination = $movie3["destination"];
$length = $movie3["length"];
$length_content = $movie3["length_content"];
$files = $movie3["files"];
$description = $movie3["description"];
$friends_one = $db2->query("SELECT * FROM likes WHERE number_likes='$id2' LIMIT 2");
while($movie4 = mysqli_fetch_array($friends_one)) {
...
}
}
I realize this is difficult to figure out. I'm new at programming and appreciate any help given...
EDIT: DDL For PhpmyAdmin
I'm not not quite sure if I did this right. Here are my results:
-- phpMyAdmin SQL Dump
-- version 4.4.14
-- http://www.phpmyadmin.net
--
-- Host: 127.0.0.1
-- Generation Time: Mar 15, 2016 at 12:30 PM
-- Server version: 5.6.26
-- PHP Version: 5.6.12
SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
SET time_zone = "+00:00";
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET #OLD_CHARACTER_SET_RESULTS=##CHARACTER_SET_RESULTS */;
/*!40101 SET #OLD_COLLATION_CONNECTION=##COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
--
-- Database: `crave crap`
--
-- --------------------------------------------------------
--
-- Table structure for table `episodes`
--
CREATE TABLE IF NOT EXISTS `episodes` (
`id` int(11) NOT NULL,
`show_id` int(11) NOT NULL,
`title` text NOT NULL,
`destination` text NOT NULL,
`description` text NOT NULL,
`length` text NOT NULL,
`likes` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `friends`
--
CREATE TABLE IF NOT EXISTS `friends` (
`id` int(11) NOT NULL,
`user` int(11) NOT NULL,
`grab` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=17 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `friends`
--
INSERT INTO `friends` (`id`, `user`, `grab`) VALUES
(13, 52, 51),
(14, 53, 51),
(15, 54, 51),
(16, 55, 51);
-- --------------------------------------------------------
--
-- Table structure for table `history`
--
CREATE TABLE IF NOT EXISTS `history` (
`id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`movie_id` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=90 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `history`
--
INSERT INTO `history` (`id`, `user_id`, `movie_id`) VALUES
(80, 51, 7),
(81, 51, 8),
(82, 51, 9),
(83, 51, 11),
(84, 51, 11),
(85, 51, 11),
(86, 51, 11),
(87, 51, 11),
(88, 51, 11),
(89, 51, 11);
-- --------------------------------------------------------
--
-- Table structure for table `likes`
--
CREATE TABLE IF NOT EXISTS `likes` (
`id` int(11) NOT NULL,
`user` int(11) NOT NULL,
`number_likes` int(11) NOT NULL,
`review` text NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=150 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `likes`
--
INSERT INTO `likes` (`id`, `user`, `number_likes`, `review`) VALUES
(130, 51, 9, 'gtgtgt'),
(131, 51, 8, 'gbgtrgrt'),
(132, 52, 8, 'tgrrgrgt'),
(135, 53, 8, 'tgrgtrgtr'),
(136, 53, 9, 'gtbrgtrgtrgtr'),
(137, 52, 11, 'tgrgtrgtr'),
(138, 53, 11, 'tgrgtrgtr'),
(139, 54, 11, 'tgrtgrbgtr'),
(140, 52, 12, 'tgrtgrgtr'),
(141, 53, 12, 'gtrgtrgtr'),
(143, 52, 9, 'ttrgth'),
(146, 51, 7, 'gtgtrtg'),
(147, 52, 7, 'tgtrgtrtr'),
(149, 51, 11, 'No Review Found');
-- --------------------------------------------------------
--
-- Table structure for table `movies`
--
CREATE TABLE IF NOT EXISTS `movies` (
`id` int(11) NOT NULL,
`title` text NOT NULL,
`photo` text NOT NULL,
`destination` text NOT NULL,
`description` text NOT NULL,
`rating` text NOT NULL,
`length` text NOT NULL,
`length_content` text NOT NULL,
`likes` int(11) NOT NULL,
`files` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=19 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `movies`
--
INSERT INTO `movies` (`id`, `title`, `photo`, `destination`, `description`, `rating`, `length`, `length_content`, `likes`, `files`) VALUES
(7, 'Star Inside Out', 'covers/pirate.jpg', 'movies/56c7ede7d3ed3658.44679765.mp4', 'Professional, Clean, Ready to Go', 'G', '1h 20m', '1', 6, 3),
(8, 'Star Wars', 'covers/star wars.jpg', 'movies/56c7ede7d3ed3108.44679765.mp4', 'Thirty years after the defeat of the Galactic Empire, the galaxy faces a new threat from the evil Kylo Ren (Adam Driver) and the First Order. When a defector named Finn crash-lands on a desert planet, he meets Rey (Daisy Ridley), a tough scavenger whose droid contains a top-secret map. Together, the young duo joins forces with Han Solo (Harrison Ford) to make sure the Resistance receives the intelligence concerning the whereabouts of Luke Skywalker (Mark Hamill), the last of the Jedi Knights.', 'PG', '2h 16m', '1', 100, 100),
(9, 'Hello', 'covers/56e068530dc9f9.52895782.jpg', 'movies/56e06853166618.33290858.mp4', 'Hello', 'PG-13', '58m', '0', 27, 12),
(11, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 29, 58),
(12, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 30, 60),
(14, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 33, 60),
(15, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 30, 60),
(16, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 34, 60),
(17, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 32, 60),
(18, 'The lost boy', 'covers/56e4341b6bb3f5.36887672.jpg', 'movies/56e4341b6bd319.33767547.mp4', 'A boy gets lost in the forest, separating him from his family, he must learn how to survive long enough to get out alive.', 'PG-13', '1h 47m', '1', 32, 60);
-- --------------------------------------------------------
--
-- Table structure for table `shows`
--
CREATE TABLE IF NOT EXISTS `shows` (
`id` int(11) NOT NULL,
`title` text NOT NULL,
`photo` text NOT NULL,
`description` text NOT NULL,
`likes` int(11) NOT NULL,
`seasons` int(11) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `shows`
--
INSERT INTO `shows` (`id`, `title`, `photo`, `description`, `likes`, `seasons`) VALUES
(1, 'Age of Ultron', 'covers/56c6961452b097.49801377.jpg', 'Hello', 5, 2);
-- --------------------------------------------------------
--
-- Table structure for table `show_likes`
--
CREATE TABLE IF NOT EXISTS `show_likes` (
`id` int(11) NOT NULL,
`user` int(11) NOT NULL,
`number_likes` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- --------------------------------------------------------
--
-- Table structure for table `users`
--
CREATE TABLE IF NOT EXISTS `users` (
`id` int(11) NOT NULL,
`username` text NOT NULL,
`password` text NOT NULL,
`dropbox_token` text NOT NULL,
`active` int(11) NOT NULL,
`premium` int(11) NOT NULL,
`avatar` text NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=63 DEFAULT CHARSET=latin1;
--
-- Dumping data for table `users`
--
INSERT INTO `users` (`id`, `username`, `password`, `dropbox_token`, `active`, `premium`, `avatar`) VALUES
(51, 'MatthewMalan', '76a7289eb67f6468356cba907809f2fd', 'kLQQAh1zJSAAAAAAAAABjaQvSFyq0RxKylBPtSL3-PM7uKRDaATZunClQ1Zsv24F', 0, 1, 'avatars/matthew.jpg'),
(52, 'Sam Coles', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB48QKdqo-X0nN99I_3bxbFMKRtSdwbsrBz00yO_0P0XAB', 0, 1, 'avatars/sam.jpg'),
(53, 'Traek Malan', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB48QKdqo-X0nN99I_3bxbFMKRtSdwbsrBz00yO_0P0XAB', 0, 1, 'avatars/traek.jpg'),
(54, 'Jesse Gaines', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB48QKdqo-X0nN99I_3bxbFMKRtSdwbsrBz00yO_0P0XAB', 0, 1, 'avatars/jessie.jpg'),
(55, 'Rich Radford', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB48QKdqo-X0nN99I_3bxbFMKRtSdwbsrBz00yO_0P0XAB', 0, 1, 'avatars/rich.jpg'),
(58, 'Donald Trump', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB48QKdqo-X0nN99I_3bxbFMKRtSdwbsrBz00yO_0P0XAB', 0, 1, 'avatars/donald.jpg'),
(59, 'Marco Rubio', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB48QKdqo-X0nN99I_3bxbFMKRtSdwbsrBz00yO_0P0XAB', 0, 1, 'avatars/marco.jpg'),
(62, 'Joseph Malan', '91d9dfc1fef07bbcf46fe06f659e8cb3', 'kLQQAh1zJSAAAAAAAAAB5mCiOhnPi2nNKr7bhFLnOC8LVRMiLvdI30CNgSJNOygm', 0, 1, 'default');
--
-- Indexes for dumped tables
--
--
-- Indexes for table `episodes`
--
ALTER TABLE `episodes`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `friends`
--
ALTER TABLE `friends`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `history`
--
ALTER TABLE `history`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `likes`
--
ALTER TABLE `likes`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `movies`
--
ALTER TABLE `movies`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `shows`
--
ALTER TABLE `shows`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `show_likes`
--
ALTER TABLE `show_likes`
ADD PRIMARY KEY (`id`);
--
-- Indexes for table `users`
--
ALTER TABLE `users`
ADD PRIMARY KEY (`id`);
--
-- AUTO_INCREMENT for dumped tables
--
--
-- AUTO_INCREMENT for table `episodes`
--
ALTER TABLE `episodes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `friends`
--
ALTER TABLE `friends`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=17;
--
-- AUTO_INCREMENT for table `history`
--
ALTER TABLE `history`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=90;
--
-- AUTO_INCREMENT for table `likes`
--
ALTER TABLE `likes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=150;
--
-- AUTO_INCREMENT for table `movies`
--
ALTER TABLE `movies`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=19;
--
-- AUTO_INCREMENT for table `shows`
--
ALTER TABLE `shows`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=2;
--
-- AUTO_INCREMENT for table `show_likes`
--
ALTER TABLE `show_likes`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
--
-- AUTO_INCREMENT for table `users`
--
ALTER TABLE `users`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=63;
/*!40101 SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=#OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=#OLD_COLLATION_CONNECTION */;
EDIT #trincot
This is the code I used:
$movie = $db2->query("SELECT m.id AS mid, m.photo AS pho,
m.destination AS des, m.length AS len,
m.length_content AS lenc, m.description AS desa, m.rating AS rat,
m.files AS fil
FROM movies m
INNER JOIN
( SELECT movie_id, MAX(id) AS id,
FROM history
WHERE h.user_id = $id3
GROUP BY movie_id
) h ON h.movie_id = m.id
ORDER BY h.id
LIMIT $start, $per_page");
$movie connects to a while loop. $id3 is the users id, which is found by a session. $start and $per_page are used for pagination purposes.
The duplicates
The DDL section shows you have duplicates in the history table, which in itself is not a problem, but it does explain the duplicate results you get in your query.
The solution is to filter away the duplicate history records, and keep only the most recent from them for the rest of the query. This filtering you can do in a sub query, like this:
SELECT m.id AS mid,
m.photo AS pho,
m.destination AS des,
m.length AS len,
m.length_content AS lenc,
m.description AS desa,
m.rating AS rat,
m.files AS fil
FROM movies m
INNER JOIN (
SELECT movie_id,
MAX(id) AS id
FROM history
WHERE user_id = ?
GROUP BY movie_id
) h ON h.movie_id = m.id
ORDER BY h.id
LIMIT ?, ?
(If SQL fiddle is willing to work, here is a fiddle).
The essence is in the GROUP BY movie_id which ensures you will not get duplicate movies. At the same time you take the greatest id from that group, which corresponds to the most recent one of that group, and this is important for the outer ORDER BY.
The question marks are place holders to be replaced when you prepare the statement (see below).
In the second version of the query, you don't select from history, but from likes. I assume you really want to select from history. If however, you want to only show movies that appear for this user in the likes table, you must either make sure the combination of movie and user is unique in that table, or apply the same principle as above, i.e. perform a sub query.
Prepared statements
You should use prepared statements to avoid SQL injection. Here is how you would do it:
$sql = ' ... '; // insert above query here
$stmt_movies = $db2->prepare($sql);
$stmt_movies->bind_param("iii", $user_id, $start, $per_page);
$stmt_movies->execute() or die($stmt_movies->error);
$stmt_movies->store_result();
$stmt_movies->bind_result($movie_id, $photo, $destination, $length,
$length_content, $description; $rating, $files);
while ($stmt_movies->fetch()) {
//...
}
$stmt_movies->free_result();
The second query
It is really confusing to use the name number_likes for a column that apparently holds a movie id. I would recommend to change that name to movie_id. The fact that it concerns a like is already apparent from the table name.
Secondly, it is just as confusing to select from the table likes and then call the result $friends_one. As far as I can tell a record in the table likes indicates that a user likes a certain movie, and potentially records a review written by that user. It does not have any information about friends... A better name would be $movie_likers.
You limit the number of records to two, but have no control over which two will be taken. It would be better if you would add an ORDER BY clause.
Finally, you should either combine this query with the main query, or at least prepare it, so it is compiled once, and not in every iteration. Here is how that would look (with some change to variable names, see below):
$sql = "SELECT review FROM likes WHERE number_likes = ? ORDER BY id DESC LIMIT 2";
$stmt_likes = $db2->prepare($sql);
$stmt_likes->bind_param("i", $move_id);
Then combined with the other code, you get this code:
$sql = ' ... '; // insert above query here
$stmt_movies = $db2->prepare($sql);
$stmt_movies->bind_param("iii", $movie_id, $start, $per_page);
$stmt_movies->execute() or die($stmt_movies->error);
$stmt_movies->store_result();
$stmt_movies->bind_result($movie_id, $photo, $destination, $length,
$length_content, $description; $rating, $files);
$sql = "SELECT review FROM likes WHERE number_likes = ? ORDER BY id DESC LIMIT 2";
$stmt_likes = $db2->prepare($sql);
while ($stmt_movies->fetch()) {
$stmt_likes->bind_param("i", $movie_id);
$stmt_likes->execute() or die($stmt_likes->error);
$stmt_likes->store_result();
$stmt_likes->bind_result($review);
while ($stmt_likes->fetch()) {
//...
}
$stmt_likes->free_result();
//...
}
$stmt_movies->free_result();
Choose telling variable names
As already hinted above, the names of the variables should better not be $id3, $id2, $movie4, ... etc. Try to use more telling names, like user_id, movie_id, etc. It helps to understand the code -- especially when you come back to it after a few months.
The first query joins the history table, but does not select from it. So you receive as many records per movie as there are history entries for the movie.

What would be the most efficient way of creating my MySQL database

I'm thinking of doing something like...
Table Pants (20 entrees)
| ID | Item | Description | Price
Table Shirts (20 entrees)
| ID | Item | Description | Price
Table Socks (5 entrees)
| ID | Item | Description | Price
Then I run a php foreach ID in $table, to populate the list on a page.
So would this be an efficient way of doing this since some of my tables might not have many entrees? Or should I have all the entrees in one table and add an extra field for the different categories and find another way of populating my page via the new field? Or maybe a greater idea?
Thanks for your time.
your model is not flexible and not efficient. Your second solution is the good one create a table named items and add an extra field named categories which should be a foreign key.
you can check this http://sqlfiddle.com/#!2/74c91/1, here is all you need
here is the code needed for the database creation :
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!40014 SET FOREIGN_KEY_CHECKS=0 */;
-- Dumping structure for table database.categories
CREATE TABLE IF NOT EXISTS `categories` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Dumping data for table database.categories: ~0 rows (approximately)
/*!40000 ALTER TABLE `categories` DISABLE KEYS */;
INSERT INTO `categories` (`id`, `name`, `created`) VALUES
(1, 'Pants ', '2012-12-15 11:37:27'),
(2, 'Shirts ', '2012-12-15 11:37:36'),
(3, 'Socks ', '2012-12-15 10:38:47');
/*!40000 ALTER TABLE `categories` ENABLE KEYS */;
-- Dumping structure for table database.clothes
CREATE TABLE IF NOT EXISTS `clothes` (
`id` int(10) NOT NULL AUTO_INCREMENT,
`item` varchar(255) NOT NULL,
`description` text,
`category_id` int(10) NOT NULL,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `category_id` (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Dumping data for table database.clothes: ~0 rows (approximately)
/*!40000 ALTER TABLE `clothes` DISABLE KEYS */;
INSERT INTO `clothes` (`id`, `item`, `description`, `category_id`, `created`) VALUES
(1, 'red socks', 'Red Socks Rocks', 3, '2012-12-15 10:39:06'),
(2, 'blue socks', 'nice socks too', 3, '2012-12-15 10:39:18'),
(3, 'grey pants', 'pretty cool', 1, '2012-12-15 10:40:34'),
(4, 'Blue Shirt', 'Nice blue shirt', 2, '2012-12-15 10:40:10');
/*!40000 ALTER TABLE `clothes` ENABLE KEYS */;
/*!40014 SET FOREIGN_KEY_CHECKS=1 */;
/*!40101 SET CHARACTER_SET_CLIENT=#OLD_CHARACTER_SET_CLIENT */;
And here is the code to retrieve your data:
SELECT
clothes.id AS clotheId,
clothes.item AS clotheItem,
clothes.description AS clotheDescription,
categories.id AS categoryId,
categories.name AS categoryName
FROM clothes
LEFT JOIN categories
ON clothes.category_id=categories.id;
I'd go with the latter.
ID | Item | Description | Price | Type
Then use a query like:
SELECT * FROM items WHERE type='$type'
I would not store this data in separate tables. Create one table, then include an indicator as to the type of item it is.
Similar to this:
create table ClothingItems
(
id,
typeid,
item,
description,
price
);
create table clothingType
(
typeid,
name
);
Then to query you can use:
select *
from clothingitems i
left join clothingType t
on t.typeid = t.typeid
where t.name = 'socks'
Well, it might not have so many entrees for the moment. But maybe in the future? Don't put yourself in a corner :).
Ask yourself the question; -Can an article belong to more than one category?
If NO, you should create at least 2 tables (one-to-many relation):
categories
id|title|description|...
articles
id|category_id|title|price|...
If YES, you should create at least 3 tables (many-to-many relation):
categories
id|title|description|...
articles
id|category_id|title|price|...
articles_categories
article_id|category_id
Best of luck :)

sql to list services by the number of ratings the service owners have

I have 3 tables - user, service and ratings. The user's primary key is user_id and is a foreign key in the service table. Its also a foreign key in ratings table linked to rated_id (the user id of the person being rated) and rater_id (the user id of the person providing the rating)
Each user has one service and can have mutltiple ratings. If the pushed field in the ratings table is 1, then its a valid rating and can be used. If its 0, its not valid yet and is waiting to be pushed.
I need a query to show me a list of all services, sorted in decending ordered by the users with most ratings. Services belonging to Users with ratings but are not pushed are at the bottom along with services belonging to users with no ratings.
Here is the sql to create the table and data:
SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";
/*!40101 SET #OLD_CHARACTER_SET_CLIENT=##CHARACTER_SET_CLIENT */;
/*!40101 SET #OLD_CHARACTER_SET_RESULTS=##CHARACTER_SET_RESULTS */;
/*!40101 SET #OLD_COLLATION_CONNECTION=##COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
--
-- Database: `ratings`
--
-- --------------------------------------------------------
--
-- Table structure for table `ratings`
--
CREATE TABLE IF NOT EXISTS `ratings` (
`unique_id` int(11) NOT NULL AUTO_INCREMENT,
`rater_id` int(11) NOT NULL,
`rated_id` int(11) NOT NULL,
`pushed` int(11) NOT NULL,
PRIMARY KEY (`unique_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
--
-- Dumping data for table `ratings`
--
INSERT INTO `ratings` (`unique_id`, `rater_id`, `rated_id`, `pushed`) VALUES
(1, 4, 1, 1),
(2, 4, 1, 1),
(3, 4, 2, 1),
(4, 4, 3, 0);
-- --------------------------------------------------------
--
-- Table structure for table `service`
--
CREATE TABLE IF NOT EXISTS `service` (
`unique_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`description` varchar(100) NOT NULL,
PRIMARY KEY (`unique_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
--
-- Dumping data for table `service`
--
INSERT INTO `service` (`unique_id`, `user_id`, `description`) VALUES
(1, 1, 'marks service'),
(2, 2, 'shanes service'),
(3, 3, 'peters service');
-- --------------------------------------------------------
--
-- Table structure for table `user`
--
CREATE TABLE IF NOT EXISTS `user` (
`user_id` int(11) NOT NULL,
`name` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `user`
--
INSERT INTO `user` (`user_id`, `name`) VALUES
(1, 'mark'),
(2, 'shane'),
(3, 'peter'),
(4, 'jobposter');
and here is a query i came up with
SELECT s.*, count(r.rated_id), r.pushed FROM service s
join user u on (u.user_id = s.user_id)
join ratings r on (r.rated_id = u.user_id)
group by r.rated_id
order by r.rated_id
the problem is its incomplete. users with no ratings wont show and if a user has multiple ratings but none are pushed, they will appear high up...
Try this query -
SELECT
s.*, r.rated_id, r.pushed_count
FROM
service s
JOIN user u ON
u.user_id = s.user_id
LEFT JOIN (
SELECT
rated_id, COUNT(IF(pushed = 1, 1, NULL)) pushed_count
FROM
ratings
GROUP BY
rated_id
) r
ON r.rated_id = u.user_id
ORDER BY
r.pushed_count DESC
You might try this:
SELECT u.name,
s.DESCRIPTION,
COUNT(r.rated_id) rated,
r.pushed
FROM USER u
LEFT JOIN SERVICE s
ON ( u.user_id = s.user_id )
LEFT JOIN ratings r
ON ( r.rated_id = u.user_id )
GROUP BY u.name,
r.rated_id,
r.pushed
ORDER BY r.pushed DESC,
3 DESC,
u.name
I am not hundred percent sure about left joining user with services. Remove left if you require users to have services in order to participate in ratings.