I am new in using advanced SQL queries and I am struggling with one query.
I have booking system created in php and it is using 4 tables:
site_days
site_timeslots
site_bookings
site_teams
each site_team is related to site_booking
each site_booking is related to site_timeslot
each site_timeslot is related to site_days
there can be more site_timeslots related to one site_day
there can be more site_bookings related to one site_timeslot
there can be more site_teams related to one site_bookings
you can create test tables with this sql:
-- Adminer 3.6.3 MySQL dump
SET NAMES utf8;
SET foreign_key_checks = 0;
SET time_zone = 'SYSTEM';
SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO';
DROP TABLE IF EXISTS `site_bookings`;
CREATE TABLE `site_bookings` (
`id` int(11) NOT NULL auto_increment,
`timeslot_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `site_bookings` (`id`, `timeslot_id`) VALUES
(1, 6443);
DROP TABLE IF EXISTS `site_days`;
CREATE TABLE `site_days` (
`id` int(11) NOT NULL auto_increment,
`date` date NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=93 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `site_days` (`id`, `date`) VALUES
(85, '2013-04-01'),
(92, '2013-04-02');
DROP TABLE IF EXISTS `site_teams`;
CREATE TABLE `site_teams` (
`id` int(11) NOT NULL auto_increment,
`booking_id` int(11) NOT NULL,
`name` varchar(100) collate utf8_bin NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `site_teams` (`id`, `booking_id`, `name`) VALUES
(1, 1, 'Avengers'),
(2, 1, 'Big Five');
DROP TABLE IF EXISTS `site_timeslots`;
CREATE TABLE `site_timeslots` (
`id` int(11) NOT NULL auto_increment,
`day_id` int(11) NOT NULL,
`date` date NOT NULL,
`starts` time NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7152 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
INSERT INTO `site_timeslots` (`id`, `day_id`, `date`, `starts`) VALUES
(6443, 85, '2013-04-01', '08:00:00'),
(6444, 85, '2013-04-01', '08:10:00'),
(7098, 92, '2013-04-02', '08:00:00'),
(7099, 92, '2013-04-02', '08:10:00');
As a result I want to get ALL timeslots of table site_timeslots with few additional info:
- for each site_timeslot I want to know count of site_teams in all related bookings to that timeslot in total (for example if there are 2 site_bookings for that site_timeslot and each has 2 site_teams, then total count should be 4) and also count of related bookings.
I have tried this sql:
SELECT `site_teams`.`id` AS site_teams_id, `site_teams`.`name` AS site_teams_name, `site_teams`.`booking_id` AS site_teams_booking_id, `site_days`.`id` AS site_days_id, `site_days`.`date` AS site_days_date, `site_timeslots`.`id` AS site_timeslots_id, `site_timeslots`.`starts` AS site_timeslots_starts, `site_bookings`.`id` AS site_bookings_id, `site_bookings`.`timeslot_id` AS site_bookings_timeslot_id
FROM (`site_days`)
LEFT JOIN `site_timeslots` ON `site_timeslots`.`day_id` = `site_days`.`id`
LEFT JOIN `site_bookings` ON `site_bookings`.`timeslot_id` = `site_timeslots`.`id`
LEFT JOIN `site_teams` ON `site_teams`.`booking_id` = `site_bookings`.`id`
GROUP BY `site_teams`.`booking_id`
-> but i won't get timeslots which haven't got any site_bookings, please how I should alter this sql query to have in result:
site_timeslot per row
count of site_bookings related to that site_timeslot in new column 'count_of_site_bookings'
count of site_teams related to all site_bookings that are related to that site_timeslot in new column 'count_of_site_teams'
You can do this by LEFT JOINing starting on site_timeslots and then using COUNT on the 2 relevant fields to get the totals you are after
SELECT
sti.*,
COUNT(DISTINCT sb.id) AS count_of_site_bookings,
COUNT(DISTINCT ste.id) AS count_of_site_teams
FROM site_timeslots sti
INNER JOIN site_days sd
ON sd.id = sti.day_id
LEFT JOIN site_bookings sb
ON sb.timeslot_id = sti.id
LEFT JOIN site_teams ste
ON ste.booking_id = sb.id
GROUP BY sti.id
You can find this on SQL Fiddle http://sqlfiddle.com/#!2/1a253/2
I also did a previous version which used a subquery due to an incorrect assumption on my part, if you'd like to take a look at that for reference it's available also at http://sqlfiddle.com/#!2/9ccf2/10
Related
I have a two date one is entry_user and exit_user i want display count of number of user spend time in showroom from last 5 minit , last 30 minit and last 1 hours..I want to make this result from mysql query
Please Suggest me to how i do this for display below result
thanks advance
i want result like this
date visitor_count
5 (minit) 4
10(minit) 10
1(hours) 20
my table example:
Create auxiliary table intervals, and add to this your time intervals in seconds. For example
CREATE TABLE `intervals` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`value` int(11) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `analytics`.`intervals` (`value`) VALUES(0),(300),(600),(1800),(3600);
To display count of user number accordinging to duration of an user activity use following:
SELECT
`value` AS `Duration`, COUNT(*) AS `visitor_count`
FROM
(
SELECT
UNIX_TIMESTAMP(exit_user)-UNIX_TIMESTAMP(entry_user) AS `Interval`
FROM
YourTable
) AS `Calculations`
JOIN intervals ON `Calculations`.`Interval` >= intervals.value
GROUP BY intervals.value
If you also want to group users by date of visit, then use
SELECT
`Date`, `value` AS `Duration`, COUNT(*) AS `visitor_count`
FROM
(
SELECT
DATE(`entry_user`) AS `Date`,
UNIX_TIMESTAMP(`exit_user`)-UNIX_TIMESTAMP(`entry_user`) AS `Interval`
FROM
YourTable
) AS `Calculations`
JOIN intervals ON `Calculations`.`Interval` >= intervals.value
GROUP BY `Calculations`.`Date`, intervals.value
Let there exists the table sessions with columns entry_user and exit_user. For example:
DROP TABLE IF EXISTS `sessions`;
CREATE TABLE `sessions` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`entry_user` datetime NOT NULL,
`exit_user` datetime NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
INSERT INTO `sessions` (entry_user, exit_user)
VALUES
('2016-01-01 00:00:00','2016-01-01 00:00:10'),
('2016-01-01 00:00:00','2016-01-01 00:03:00'),
('2016-01-01 00:00:00','2016-01-01 01:00:00'),
('2016-01-01 00:00:00','2016-01-01 00:07:00'),
('2016-01-01 00:00:00','2016-01-01 00:15:03'),
('2016-01-01 00:00:00','2016-01-01 00:06:00'),
('2016-01-01 00:00:00','2016-01-01 00:11:00'),
('2017-01-01 00:00:00','2017-01-01 00:11:00'),
('2017-01-01 00:00:00','2017-01-01 00:01:30');
Create auxiliary table ticks:
DROP TABLE IF EXISTS `ticks`;
CREATE TABLE `ticks` (
`ID` int(11) NOT NULL AUTO_INCREMENT,
`value` int(11) NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
and add time ticks values in seconds:
INSERT INTO `ticks` (`value`) VALUES (0),(300),(600),(900),(1800),(3600);
Create view intervals
DROP TABLE IF EXISTS `intervals`;
CREATE VIEW intervals AS
SELECT
s.`value` AS started, f.`value` AS finished
FROM
ticks s JOIN ticks f ON s.ID = (f.ID - 1);
Now you can count sessions that durations more then specified ticks values, using
SELECT
`Date`, `value` AS `Duration more or equals then`, COUNT(*) AS `Visitors Number`
FROM
(
SELECT
DATE(entry_user) AS `Date`,
UNIX_TIMESTAMP(exit_user)-UNIX_TIMESTAMP(entry_user) AS Duration
FROM
sessions
) AS Durations
JOIN ticks ON Durations.Duration >= ticks.`value`
GROUP BY Durations.`Date`, ticks.`value`;
If you want to get density function of the session duration, use following:
SELECT
`Date`, started AS `Duration more or equals then`, finished AS `and less or equals then`, COUNT(*) AS `Visitors Number`
FROM
(
SELECT
DATE(entry_user) AS `Date`,
UNIX_TIMESTAMP(exit_user)-UNIX_TIMESTAMP(entry_user) AS Duration
FROM
sessions
) AS Durations
JOIN intervals ON Durations.Duration BETWEEN intervals.started AND intervals.finished
GROUP BY Durations.`Date`, intervals.`started`;
I am trying to write a script that will allow the user to select a list of fields to be displayed from different column/table in a database. This script will need be able to generate the full query and execute it.
I am able to select the field and add the proper where clause. However, I am being challenged on how to generate the ON clause which is part of the JOIN statement.
Here is what I have done so far.
First, I defined 3 tables like so
-- list of all tables available in the database
CREATE TABLE `entity_objects` (
`object_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`object_name` varchar(60) CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
`object_description` varchar(255) DEFAULT NULL,
PRIMARY KEY (`object_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
-- list of all tables available in the database
CREATE TABLE `entity_definitions` (
`entity_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`display_name` varchar(255) NOT NULL,
`entity_key` varchar(60) NOT NULL,
`entity_type` enum('lookup','Integer','text','datetime','date') CHARACTER SET latin1 COLLATE latin1_general_ci NOT NULL,
`object_id` int(11) unsigned NOT NULL,
PRIMARY KEY (`entity_id`),
KEY `object_id` (`object_id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
-- a list of the fields that are related to each other. For example entity 12 is a foreign key to entity 11.
CREATE TABLE `entity_relations` (
`relation_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`entity_a` int(11) unsigned NOT NULL,
`entity_b` int(11) unsigned NOT NULL,
`relation_type` enum('1:1','1:N') NOT NULL DEFAULT '1:1',
PRIMARY KEY (`relation_id`),
UNIQUE KEY `entity_a` (`entity_a`,`entity_b`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
To get a list of the relations that are available, I run this query
SELECT
CONCAT(oa.object_name, '.', ta.entity_key) AS entityA
, CONCAT(ob.object_name, '.', tb.entity_key) AS entityB
FROM entity_relations as r
INNER JOIN entity_definitions AS ta ON ta.entity_id = r.entity_a
INNER JOIN entity_definitions AS tb ON tb.entity_id = r.entity_b
INNER JOIN entity_objects AS oa ON oa.object_id = ta.object_id
INNER JOIN entity_objects AS ob ON ob.object_id = tb.object_id
I am having hard time trying to figure out how to generated the JOIN statement of the query. I am able to generate the SELECT ..... and the WHERE... but need help trying to generate the ON.... part of the query.
My final query should look something like this
SELECT
accounts.account_name
, accounts.industry_id
, accounts.primary_number_id
, accounts.person_id
, industries.industry_id
, industries.name
, contact_personal.first_name
, contact_personal.person_id
, account_phone_number.number_id
FROM accounts
LEFT JOIN industries ON industries.industry_id = accounts.industry_id
LEFT JOIN contact_personal ON contact_personal.person_id = accounts.person_id
LEFT JOIN account_phone_number ON account_phone_number.number_id = accounts.primary_number_id
WHERE industries.name = 'Marketing'
I created a SQL Fiddle with my MySQL code.
How can I define the ON clause of the join statement correctly?
It is completely unnecessary to create these tables, mysql can handle all of this for you so long as you are using the InnoDB storage engine by using foreign keys.
list all tables on current database
SHOW TABLES;
get list of columns on a given table
SELECT
*
FROM
information_schema.COLUMNS
WHERE
TABLE_SCHEMA = :schema
AND TABLE_NAME = :table;
get list of relationships between tables
SELECT
*
FROM
information_schema.TABLE_CONSTRAINTS tc
INNER JOIN
information_schema.INNODB_SYS_FOREIGN isf ON
isf.ID = concat(tc.CONSTRAINT_SCHEMA, '/', tc.CONSTRAINT_NAME)
INNER JOIN
information_schema.INNODB_SYS_FOREIGN_COLS isfc ON
isfc.ID = isf.ID
WHERE
tc.CONSTRAINT_SCHEMA = :schema
AND tc.TABLE_NAME = :table;
I have a scenario where i have one main table. Main table has 2 extra columns one is for table name (child table name) and other is for table id (child table id). when we enter the value in main table we also tell enter value in child table and then we enter the name of the table in main table name field and child id in the child field of the main table.
now when i query i need to join query with child table in a way that i picks up the table name from the column and join query with that table with concat function and then join on child id.
below is the structure of the table and also there values
CREATE TABLE IF NOT EXISTS `tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`tbl_type` enum('multi','gift','pledge') DEFAULT NULL,
`tbl_type_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `tbl` (`id`, `timestamp`, `tbl_type`, `tbl_type_id`) VALUES
(1, '2015-03-09 09:39:42', '', 1),
(2, '2015-03-09 22:43:23', 'multi', 2),
(3, '2015-03-09 23:26:38', 'gift', 1),
(4, '2015-03-10 09:46:15', 'pledge', 2);
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `tbl_gift` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`amount` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `tbl_gift` (`id`, `amount`) VALUES
(1, '1231200'),
(2, '1231200');
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `tbl_multi` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`amount` float(255,0) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
-- --------------------------------------------------------
CREATE TABLE IF NOT EXISTS `tbl_pledge` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`amount` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `tbl_pledge` (`id`, `amount`) VALUES
(1, '10000'),
(2, '10200');
so this is simple hard code query
select * from tbl t left join tbl_gift g on g.id = t.tbl_type_id
but i want to make it dynamic i tried this
select * from tbl t left join (concat('tbl', '_', t.tbl_type)) g on g.id = t.tbl_type_id
should get the table which i need
(concat('tbl', '_', t.tbl_type))
but it get error
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '('tbl', '_', t.tbl_type)) g on g.id = t.tbl_type_id LIMIT 0, 30' at line 1
The comments by Ankit and Usedby answered your question.
SQL does not allow you to provide dynamically constructed table names as you attempted. They provided you with two options: 1) Construct your query dynamically on the PHP side, then SQL see only the static table names or
2) Use the SQL PREPARE command to construct the dynamic table name and the EXECUTE SQL command to execute it.
Is it possible to select certain rows based on a category which matches it when there are multiple categories in the entry? It's hard to explain so I'll show you. The row I have in the database looks like this:
**article_title** | **article_content** | **category**
Article-1 | some content here | one,two,three,four
So my query looks like this:
$sql = mysqli_query($mysqli_connect, "SELECT * FROM table WHERE category='
preg_match(for example the word three)'");
Reason why I'm doing that is some articles will be available on multiple pages like page one and page three...so is there a way to match what I'm looking for through the entry in the database row?
You should use a more flexible database design. Create a separate table that holds the one-to-many relationships between (one) article and (many) categories:
CREATE TABLE IF NOT EXISTS `articles` (
`article_id` int(11) NOT NULL AUTO_INCREMENT,
`article_name` varchar(255) NOT NULL,
PRIMARY KEY (`article_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=2 ;
INSERT INTO `articles` (`article_id`, `article_name`) VALUES
(1, 'Research Normalized Database Design');
CREATE TABLE IF NOT EXISTS `article_category` (
`article_id` int(11) NOT NULL,
`category_id` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `article_category` (`article_id`, `category_id`) VALUES
(1, 1),
(1, 2);
CREATE TABLE IF NOT EXISTS `categories` (
`category_id` int(11) NOT NULL AUTO_INCREMENT,
`category_name` varchar(255) NOT NULL,
PRIMARY KEY (`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
INSERT INTO `categories` (`category_id`, `category_name`) VALUES
(1, 'Databases'),
(2, 'Normalization');
Querying then becomes as simple as:
SELECT
*
FROM
articles AS a
JOIN
article_category AS pivot ON a.article_id = pivot.article_id
WHERE
pivot.category_id = 2
Or do something like:
SELECT
*
FROM
articles AS a
JOIN
article_category AS pivot ON a.article_id = pivot.article_id
JOIN
categories AS c ON pivot.category_id = c.category_id
WHERE
c.category_name = 'Normalization'
I am trying to duplicate a page in the database and all related rows.
The problem I am having is because the page_group_id is an identifier for both tables. Is there any way of doing this without looping each of the new "page_groups" records?
pages (page_id, page_name, etc)
page_groups (page_group_id, page_id, etc)
page_group_items (page_group_id, item_id, etc)
UPDATE:
--
-- Table structure for table `pages`
--
CREATE TABLE IF NOT EXISTS `pages` (
`page_id` int(11) NOT NULL AUTO_INCREMENT,
`page_name` varchar(255) NOT NULL,
PRIMARY KEY (`page_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `pages`
--
INSERT INTO `pages` (`page_id`, `page_name`) VALUES
(1, 'My Page'),
(2, 'My other page');
-- --------------------------------------------------------
--
-- Table structure for table `page_groups`
--
CREATE TABLE IF NOT EXISTS `page_groups` (
`page_group_id` int(11) NOT NULL AUTO_INCREMENT,
`page_group_name` varchar(255) NOT NULL,
`page_id` int(11) NOT NULL,
PRIMARY KEY (`page_group_id`),
KEY `page_id` (`page_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `page_groups`
--
INSERT INTO `page_groups` (`page_group_id`, `page_group_name`, `page_id`) VALUES
(1, 'My Group', 1),
(2, 'My Group', 2);
-- --------------------------------------------------------
--
-- Table structure for table `page_group_items`
--
CREATE TABLE IF NOT EXISTS `page_group_items` (
`page_group_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
KEY `item_id` (`item_id`),
KEY `page_group_id` (`page_group_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `page_group_items`
--
INSERT INTO `page_group_items` (`page_group_id`, `item_id`) VALUES
(1, 1),
(1, 2),
(1, 3),
(2, 1),
(2, 2);
Since you're replacing the only unique identifier in each table (the primary key) when copying, I can't see a way of doing it without adding temporary anchor columns to the tables, do the copy and remove them again. Something like this;
ALTER TABLE pages ADD originalpageid INT;
UPDATE pages set originalpageid=page_id;
ALTER TABLE page_groups ADD originalpagegroupid INT;
UPDATE page_groups SET originalpagegroupid=page_group_id;
INSERT INTO pages (page_name,originalpageid)
SELECT page_name,originalpageid FROM pages;
INSERT INTO page_groups (page_group_name,page_id,originalpagegroupid)
SELECT page_group_name,MAX(pages.page_id),originalpagegroupid
FROM page_groups
JOIN pages
ON page_groups.page_id=originalpageid
GROUP BY originalpageid,page_group_name,originalpagegroupid;
INSERT INTO page_group_items(page_group_id,item_id)
SELECT MAX(page_groups.page_group_id),item_id
FROM page_group_items
JOIN page_groups
ON page_group_items.page_group_id=originalpagegroupid
GROUP BY originalpagegroupid,item_id;
ALTER TABLE pages DROP COLUMN originalpageid;
ALTER TABLE page_groups DROP COLUMN originalpagegroupid;
An SQLfiddle to test with
If the use case is doing it all the time in the system, it may not be the solution you're looking for, but for manual intervention it should work well.
As always, always back your database up before running SQL from random strangers on the Internet :)