mysql | Facet search advanced - mysql

I have tables:
CREATE TABLE IF NOT EXISTS `category` (
`id` int(11) NOT NULL,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `category` (`id`, `name`) VALUES
(1, 'Computers'),
(2, 'Bikes');
CREATE TABLE IF NOT EXISTS `fields` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`field_name` varchar(255) DEFAULT NULL,
`cid` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;
INSERT INTO `fields` (`id`, `field_name`, `cid`) VALUES
(1, 'Processor', '1'),
(2, 'Display', '1'),
(3, 'Brand', '2');
CREATE TABLE IF NOT EXISTS `fields_values` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`field_id` int(11) DEFAULT NULL,
`field_value` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=7 ;
INSERT INTO `fields_values` (`id`, `field_id`, `field_value`) VALUES
(1, 1, 'Intel Pentium 3'),
(2, 2, '27 inch'),
(3, 3, 'BMX'),
(4, 1, 'AMD Radeon'),
(5, 1, 'Intel Atom'),
(6, 2, '22 inch');
CREATE TABLE IF NOT EXISTS `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`cid` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=6 ;
INSERT INTO `products` (`id`, `name`, `cid`) VALUES
(1, 'Computer1', 1),
(2, 'Bike1.BMX', 2),
(3, 'Bike3', 2),
(4, 'Intel Atom', 1),
(5, 'Computer Radeon', 1);
CREATE TABLE IF NOT EXISTS `products_to_fields_values` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`product_id` int(11) DEFAULT NULL,
`field_value_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=5 ;
INSERT INTO `products_to_fields_values` (`id`, `product_id`, `field_value_id`) VALUES
(1, 1, 1),
(2, 2, 3),
(3, 1, 2),
(4, 4, 5);
My request looks like:
SELECT ft.id field_id, ft.field_name, fvt.field_value, fvt.id field_value_id, COUNT( DISTINCT pid ) count
FROM FIELDS ft
JOIN fields_values fvt ON ( ft.id = fvt.field_id )
JOIN products_to_fields_values pfv ON ( pfv.field_value_id = fvt.id )
JOIN products pt ON ( pt.id = pfv.product_id )
LEFT JOIN (
SELECT ft.id field_id, ft.field_name, fvt.field_value, fvt.id field_value_id, pt.name, pt.id pid
FROM FIELDS ft
JOIN fields_values fvt ON ( ft.id = fvt.field_id )
JOIN products_to_fields_values pfv ON ( pfv.field_value_id = fvt.id )
JOIN products pt ON ( pt.id = pfv.product_id )
GROUP BY pt.id
)LJ ON pfv.product_id = LJ.pid
WHERE FIND_IN_SET( 1, pt.cid )
GROUP BY ft.field_name, fvt.field_value
LIMIT 0 , 30
This request will return (I'm trying to build faceted filter):
field_id field_name field_value field_value_id count
2 Display 27 inch 2 1
1 Processor Intel Atom 5 1
1 Processor Intel Pentium 3 1
But I have other values in this table: fields_values like: AMD Radeon and 22 inch.
Where is my mistake in the request?
Thanks!
EDIT:
I'm expect to getting result:
field_id field_name field_value field_value_id count
2 Display 22 inch 6 0
2 Display 27 inch 2 1
1 Processor AMD Radeon 4 0
1 Processor Intel Atom 5 1
1 Processor IntelPentium3 1 1
Where count is a products count.

Here is a work SQL, but im not sure you have build your structure correctly.
SELECT
ft.id field_id, ft.field_name, fvt.field_value, fvt.id as field_value_id, COUNT( DISTINCT pid ) count
FROM products AS pt
JOIN products_to_fields_values AS pfv ON pfv.product_id = pt.id
JOIN fields_values AS fvt ON fvt.field_id = pfv.field_value_id
JOIN fields AS ft on ft.id = fvt.field_id
LEFT JOIN (
SELECT ft.id field_id, ft.field_name, fvt.field_value, fvt.id field_value_id, pt.name, pt.id pid
FROM fields ft
JOIN fields_values fvt ON ( ft.id = fvt.field_id )
JOIN products_to_fields_values pfv ON ( pfv.field_value_id = fvt.id )
JOIN products pt ON ( pt.id = pfv.product_id )
GROUP BY pt.id
)LJ ON pfv.product_id = LJ.pid
WHERE FIND_IN_SET( 1, pt.cid )
GROUP BY ft.field_name, fvt.field_value
LIMIT 0 , 30
In short - u have error in this line:
JOIN products_to_fields_values pfv ON ( pfv.field_value_id = fvt.id )
right one:
JOIN products_to_fields_values pfv ON ( pfv.field_value_id = fvt.field_id )

I tried for your question, check this query
select fValue.field_id, f.field_name as field_name, fValue.field_value, fValue.id as field_value_id
from products_to_fields_values as productValue
left join fields_values as fValue on(fValue.field_id=productValue.field_value_id)
left join fields as f on (fValue.field_id=f.id)
left join products as p on (productValue.product_id=p.id)
where p.cid=1

I am quite sure the problem, i.e. the 'missing' records, is being caused by the 'group by' commands. It's a bit difficult to come up with a solution for you as I am not too sure what you are trying to achieve. The expected result you posted hasn't helped me much in this regard. The query you are trying to run is rather complex, and on a populated database is going to start running very, very slowly. As such it would suggest a better database design is required. If you can explain what you are trying to achieve I will gladly look at a solution for you.

Ignoring the limit and where clauses this is the query you need to use:
SELECT
fields.id AS field_id
, fields.field_name
, fields_values.field_value
, fields_values.id AS field_value_id
, COUNT(products.id) AS `count`
FROM fields_values
JOIN fields ON fields.id = fields_values.field_id
JOIN category AS field_category ON field_category.id = fields.cid
LEFT JOIN products_to_fields_values AS product_fields ON fields_values.id = product_fields.field_value_id
LEFT JOIN products ON products.id = product_fields.product_id
GROUP BY 1, 2, 3, 4;
This produces the following result:
| field_id | field_name | field_value | field_value_id | count |
+----------+------------+-----------------+----------------+-------+
| 1 | Processor | AMD Radeon | 4 | 0 |
| 1 | Processor | Intel Atom | 5 | 1 |
| 1 | Processor | Intel Pentium 3 | 1 | 1 |
| 2 | Display | 22 inch | 6 | 0 |
| 2 | Display | 27 inch | 2 | 1 |
| 3 | Brand | BMX | 3 | 1 |
In your where clause you can specify
WHERE category.id IN (1)
to get the result you want.
The mistake you were making (and a previous answer as well) was that you were joining the category through the product giving you the product_category instead of the field_category.
When you applied a where condition on the product_category, it removed all products not part of the set, so you would never get the count = 0

Related

Find DISTINCT LAST record with SQL LEFT JOIN

I'm running MySQL 5.6.
I have two related tables:
CREATE TABLE Cars (
id INT NOT NULL AUTO_INCREMENT,
plate VARCHAR(16) NOT NULL,
flag TINYINT,
PRIMARY KEY(id)
)
and:
CREATE TABLE Rents (
id INT NOT NULL AUTO_INCREMENT,
out_date DATE NOT NULL,
in_date DATE,
car_id INT,
FOREIGN KEY (car_id) REFERENCES Cars(id),
PRIMARY KEY(id)
)
I can have multiple rents for each car (0 to many).
I need to select all vehicles in table Cars (with flag = 1) along with their status i.e. I need to know if each car is currently unavailable (only out_date is filled) or availabe (out_date and in_date filled) of course also vehicles without any rents are to be considered available.
The result set need to include out_date and in_date values [Update 17/07/2022].
I tought to use something like:
SELECT
*,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rent.Car_id WHERE Cars.Flag = 1
but this of course will just return all the rows with positive flag match and a status evaluation (0 unavailable, 1 available):
id | plate | flag | id | out_date | in_date | car_id | status
---------------------------------------------------------------------
'1', 'FA787MX', '1', '1', '2022-07-14', '2022-07-15', '1', '1'
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
While I need to have this (edited 17/07/2022):
'1', 'FA787MX', '1', '2', '2022-07-16', NULL, '1', '0'
'3', 'AB124DF', '1', '4', '2022-07-13', '2022-07-14', '3', '1'
'4', 'CC666VC', '1', NULL, NULL, NULL, NULL, '1'
'5', 'GG435ED', '1', '5', '2022-07-16', NULL, '5', '0'
i.e. only the second row of FA787MX car should be mantained since it's the most recent out_date value (no matter if it's id is higher or lower).
For the sake of completeness: There is no guarantee that rental ids will be kept consistent with their rental history. In other words you cannot be sure that for a given car the rental where in_date = NULL is the correct one but you should compare them by out_date value.
Data sample:
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (1, 'FA787MX', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (2, 'EX431YY', 0);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (3, 'AB124DF', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (4, 'CC666VC', 1);
INSERT INTO `Cars` (`id`, `plate`, `flag`) VALUES (5, 'GG435ED', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (1, '2022-07-14', '2022-07-15', 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (2, '2022-07-16', NULL, 1);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (3, '2022-07-16', NULL, 2);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (4, '2022-07-13', '2022-07-14', 3);
INSERT INTO `Rents` (`id`, `out_date`, `in_date`, `car_id`) VALUES (5, '2022-07-16', NULL, 5);
One option is to join to find only those rentals that are still outstanding (in_date IS NULL). That will drop the old rentals having in_date not null.
Based on the updated requirements, there are a few ways to do it. One is a simple outer join to find the most recent rental per car to obtain the corresponding in_date as well...
MySQL 5.6 fiddle
SELECT Cars.*
, Rents.out_date
, Rents.in_date
, Rents.id IS NULL OR Rents.in_date IS NOT NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
LEFT JOIN Rents AS r2
ON Rents.out_date < r2.out_date
AND Rents.Car_id = r2.Car_id
WHERE Cars.Flag = 1
AND r2.Car_id IS NULL
ORDER BY Cars.id
;
The result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
2022-07-13
2022-07-14
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
Based on the original requirements: Try this (fiddle):
SELECT Cars.*
, Rents.in_date
, CASE WHEN in_date IS NOT NULL OR Rents.id IS NULL THEN 1 ELSE 0 END AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
and if the results contain only those with in_date IS NULL, this reduces to:
SELECT Cars.*
, out_date
, Rents.in_date
, Rents.id IS NULL AS status_final
FROM Cars
LEFT JOIN Rents
ON Cars.id = Rents.Car_id
AND in_date IS NULL
WHERE Cars.Flag = 1
;
Result:
id
plate
flag
out_date
in_date
status_final
1
FA787MX
1
2022-07-16
0
3
AB124DF
1
1
4
CC666VC
1
1
5
GG435ED
1
2022-07-16
0
If your version of MySql is 8.0+ use ROW_NUMBER() window function to pick the latest row for each car in Rents:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT *, ROW_NUMBER() OVER (PARTITION BY car_id ORDER BY out_date DESC) rn
FROM Rents
) r ON r.car_id = c.id AND r.rn = 1
WHERE c.flag = 1;
For previous versions use NOT EXISTS:
SELECT c.*, r.*,
r.out_date IS NULL OR r.in_date IS NOT NULL status
FROM Cars c
LEFT JOIN (
SELECT r1.*
FROM Rents r1
WHERE NOT EXISTS (
SELECT *
FROM Rents r2
WHERE r2.car_id = r1.car_id AND r2.out_date > r1.out_date
)
) r ON r.car_id = c.id
WHERE c.flag = 1;
See the demo.
If you imagine the result of your query as a table, you can easily write a query that would give you what you need (the subquery is just yours with the select spelled out to give a unique column name to the second id column, as it seemed useful - the only way to uniquely identify a row):
SELECT MAX(rent_id) FROM (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
) as rental_status
WHERE status = 0
GROUP BY car_id;
Which tells you which rows are interesting:
+--------------+
| MAX(rent_id) |
+--------------+
| 2 |
| 5 |
+--------------+
Now you can use a join to return the results of your initial query only for the interesting rows. To avoid having to spell out that query all over again, MySQL 8 has a way to stash the results of your core query and use it like a table:
WITH
status_data AS (
SELECT
Cars.id as id,
plate,
flag,
Rents.id as rent_id,
out_date,
in_date,
car_id,
IF(Rents.in_date IS NOT NULL AND Rents.out_date IS NOT NULL, 1, IF(Rents.id IS NULL, 1, 0)) AS status
FROM Cars
LEFT JOIN Rents ON Cars.id = Rents.car_id WHERE Cars.Flag = 1
)
SELECT * from status_data
JOIN (
SELECT MAX(rent_id) as rent_id FROM status_data
WHERE status = 0
GROUP BY car_id
) as ids using(rent_id);
Giving the result:
+---------+----+---------+------+------------+---------+--------+--------+
| rent_id | id | plate | flag | out_date | in_date | car_id | status |
+---------+----+---------+------+------------+---------+--------+--------+
| 2 | 1 | FA787MX | 1 | 2022-07-16 | NULL | 1 | 0 |
| 5 | 5 | GG435ED | 1 | 2022-07-16 | NULL | 5 | 0 |
+---------+----+---------+------+------------+---------+--------+--------+

Joining a single row to a query

I need to get a value from another table but where there may be 5/6 results, I only need to show the latest one. I've tried the following:
SELECT s.Mileage
, s.PurchasePrice
, v.make
, v.model
, v.vrm
, c.CleanLive
FROM StockBook s
LEFT
JOIN Vehicles v
ON v.VehicleID = s.VehicleID
LEFT
JOIN CapVals c
ON c.LeadID = (SELECT C1.CleanLive
FROM CapVals C1
WHERE s.LeadID = c.LeadID
ORDER
BY C1.Date
LIMIT 1
)
ORDER
BY StockBookID
Which is working as a query but not showing CleanLive value.
I've set up a sample data set and DB Fiddle here:
CREATE TABLE `Vehicles` (
`VehicleID` int(11) NOT NULL,
`vrm` varchar(15) NOT NULL,
`make` varchar(40) NOT NULL,
`model` varchar(40) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `StockBook` (
`StockBookID` int(11) NOT NULL,
`VehicleID` int(11) NOT NULL,
`LeadID` int(11) NOT NULL,
`Mileage` int(11) NOT NULL,
`PurchasePrice` decimal(15,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `CapVals` (
`CapValsID` int(11) NOT NULL,
`LeadID` int(11) DEFAULT NULL,
`CleanLive` int(11) DEFAULT NULL,
`Date` datetime DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `Vehicles` (`VehicleID`, `vrm`, `make`, `model`) VALUES
(1, 'M900WRD', 'Vauxhall', 'Signum');
INSERT INTO `StockBook` (`StockBookID`, `LeadID`, `VehicleID`, `Mileage`, `PurchasePrice`) VALUES
(1, 1, 1, 17000, 15000.00);
INSERT INTO `CapVals` (`CapValsID`, `LeadID`, `CleanLive`, `Date`) VALUES
(6455, 1, 1540, '2019-12-04 15:02:29'),
(6456, 1, 1540, '2019-12-04 15:02:29'),
(6457, 1, 1540, '2019-12-04 15:02:29');
https://www.db-fiddle.com/f/b4fQuMVpXHGxqgYJ4ia92w/4
You can try this
SELECT Stock.Mileage, Stock.PurchasePrice, Vehi.make, Vehi.model, Vehi.vrm,
(SELECT CleanLive from CapVals a WHERE a.LeadID = Stock.LeadID ORDER BY DATE DESC LIMIT 1) AS CleanLive
FROM StockBook Stock
LEFT JOIN Vehicles Vehi
ON Stock.VehicleID=Vehi.VehicleID
ORDER BY StockBookID
SELECT v.vehicleID
, v.vrm
, v.make
, v.model
, s.stockbookid
, s.leadid
, s.mileage
, s.purchaseprice
, c.capvalsid
, c.cleanlive
, c.date
FROM vehicles v
JOIN stockbook s
ON s.vehicleid = v.vehicleid
JOIN capvals c
ON c.leadid = s.leadid
JOIN
( SELECT leadid,MAX(capvalsid) capvalsid FROM capvals GROUP BY leadid ) x
ON x.leadid = c.leadid
AND x.capvalsid = c.capvalsid;
+-----------+---------+----------+--------+-------------+--------+---------+---------------+-----------+-----------+---------------------+
| vehicleID | vrm | make | model | stockbookid | leadid | mileage | purchaseprice | capvalsid | cleanlive | date |
+-----------+---------+----------+--------+-------------+--------+---------+---------------+-----------+-----------+---------------------+
| 1 | M900WRD | Vauxhall | Signum | 1 | 1 | 17000 | 15000.00 | 6457 | 1540 | 2019-12-04 15:02:29 |
+-----------+---------+----------+--------+-------------+--------+---------+---------------+-----------+-----------+---------------------+
Use Row_number concept, which will avoid the duplicate rows and give you recent one,
FIDDLE DEMO
SELECT Stock.Mileage, Stock.PurchasePrice, Vehi.make, Vehi.model, Vehi.vrm,
X.CleanLive as CleanLive
FROM StockBook Stock
LEFT JOIN Vehicles Vehi ON Stock.VehicleID=Vehi.VehicleID
LEFT JOIN (SELECT #LeadID:=LeadID,C1.LeadID, CleanLive, C1.Date, #row_number:=CASE WHEN #LeadID = LeadID THEN #row_number + 1 ELSE 1 END AS num
FROM CapVals AS C1, (SELECT #LeadID:=0,#row_number:=0) as t ORDER BY C1.Date DESC) X ON X.LeadID = Stock.LeadID AND X.num = 1
ORDER BY StockBookID

How to use group by and count in mysql

Here I am having 3 table using I have to take the count
trip_details
id allocationId tripId
1 7637 aIz2o
2 7626 osseC
3 7536 01LEC
4 7536 78w2w
5 7640 S60zF
6 7548 ruaoR
7 7548 Qse6s
escort_allocation
id allocationId escortId
3 7637 1
4 7626 1
5 7627 1
6 7536 1
7 7640 1
7 7548 1
cab_allocation
allocationId allocationType
7637 Daily Trip
7626 Daily Trip
7627 Daily Trip
7536 Adhoc Trip
7640 Adhoc Trip
7548 Daily Trip
Using above table I have to get the count, I tried but it is not happening my expected results.
I tried sql query
SELECT a.`tripId`
FROM `trip_details` a
INNER JOIN escort_allocation b ON a.`allocationId` = b.`allocationId`
GROUP BY a.`allocationId`
LIMIT 0 , 30
I am getting like this
tripId
01LEC
ruaoR
osseC
aIz2o
S60zF
total 6 tripId i got ,so now I want to take the count so I am using this query
SELECT COUNT(*)
FROM `trip_details` a
INNER JOIN escort_allocation b ON a.`allocationId` = b.`allocationId`
GROUP BY a.`allocationId`
LIMIT 0 , 30
but this query is not working.I am getting results like below
2
2
1
1
1
MY MYSQL TABLES AND VALUES LOOK LIKE THIS
CREATE TABLE `trip_details` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`allocationId` int(11) NOT NULL,
`tripId` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
KEY `tripId` (`tripId`),
KEY `allocationId` (`allocationId`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 ;
INSERT INTO trip_details
(id, allocationId, tripId)
VALUES
(1, 7637, '00SwM'),
(2, 7626, '00SwM'),
(3, 7536, '00SwM'),
(4, 7536, '01hEU'),
(5, 7640, '01hEU'),
(6, 7548, 'IRZMS'),
(7, 7548, 'IRZMS');
CREATE TABLE `escort_allocation` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`allocationId` int(11) NOT NULL,
`escortId` varchar(100) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1 ;
INSERT INTO escort_allocation
(id, allocationId, escortId)
VALUES
(1, 7637, 'ssda'),
(2, 7626, 'adad'),
(3, 7627, 'sfsaf'),
(4, 7536, 'ssaf'),
(5, 7640, 'asf'),
(6, 7548, 'a3r');
CREATE TABLE `cab_allocation` (
`allocationId` int(11) NOT NULL AUTO_INCREMENT,
`allocationType` enum('Daily Trip','Adhoc Trip') NOT NULL,
PRIMARY KEY (`allocationId`)
) ENGINE=InnoDB AUTO_INCREMENT=7695 DEFAULT CHARSET=latin1;
INSERT INTO cab_allocation
(allocationId, allocationType)
VALUES
(7637, 'Daily Trip'),
(7626, 'Daily Trip'),
(7627, 'Daily Trip'),
(7536, 'Adhoc Trip'),
(7640, 'Adhoc Trip'),
(7548, 'Daily Trip');
You can try this
SELECT COUNT(DISTINCT (a.`tripId`))
FROM `trip_details` a
INNER JOIN escort_allocation b ON a.`allocationId`=b.`allocationId`
LIMIT 0 , 30
because of GROUP BY there is separate count for all allocationId.
With this, you should get the tripid and the amount:
SELECT COUNT(a.tripId) as total, a.tripId as tripId
FROM trip_details a INNER JOIN escort_allocation b
ON a.allocationId = b.allocationId
GROUP BY a.allocationId LIMIT 0 , 30
You may use:
SELECT COUNT(DISTINCT a.allocationId)
FROM
trip_details a
INNER JOIN
escort_allocation b
ON a.allocationId = b.allocationId
Previously you used COUNT(*) and also used GROUP BY so the counts of rows you were getting are from individual groups.
Update-2:
SELECT
(
SELECT COUNT(*) FROM trip_details
) AS Total_Trip_Count,
COUNT(T.tripId) as Escort_Count,
(
SELECT COUNT(*) FROM
(
SELECT a.allocationId
FROM escort_allocation a
INNER JOIN cab_allocation c ON a.allocationId = c.allocationId
WHERE c.allocationType = 'Adhoc Trip'
GROUP BY a.allocationId
) AS Ad
) AS Adhoc_Trip_Count
FROM
(
SELECT a.tripId FROM
trip_details a
INNER JOIN
escort_allocation b
ON a.allocationId = b.allocationId
GROUP BY a.allocationId
) AS T

Creating a weighted sum of values from different tables

I'm trying to create a list of students whose behaviour is statistically worst across each of our school's year groups.
We have a table named students.
We then have behavioural flags and alerts, plus sanctions.
However, different categories of flag/alert/sanction are deemed more serious than others. These are stored with labels in their respective _categories table, e.g. flag_categories and sanction_categories. The flag table will then have a column called Category_ID (alerts is a bit different as it's just a Type field with 'A', 'C', 'P' and 'S' values).
If I want to look at data which shows our highest-flagged students in a specific year group, I'd run this query:
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
COUNT(f.ID) AS `Flags`
FROM `students` stu
LEFT JOIN `flags` f ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
GROUP BY stu.id
ORDER BY `Flags` DESC
LIMIT 0, 20
If I wanted to show our students with the most Crisis alerts in a specific year group, I'd run this query:
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
COUNT(f.ID) AS `Flags`
FROM `students` stu
LEFT JOIN `flags` f ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
AND f.Category_ID = 10
GROUP BY stu.id
ORDER BY `Flags` DESC
LIMIT 0, 20
If I want to find how many Late or Mobile flags a student has, and perhaps add these together (with weightings), I can run the following query:
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) AS `Late Flags`,
SUM(CASE WHEN f.Category_ID = 12 THEN 2 ELSE 0 END) AS `Mobile Flags`,
## not sure about this line below... is there a nicer way of doing it? `Late Flags` isn't recognised as a field apparently
## so I can't just do ( `Late Flags` + `Mobile Flags` )
(SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) + SUM(CASE WHEN f.Category_ID = 12 THEN 2 ELSE 0 END)) AS `Points`
FROM `flags` f
LEFT JOIN `students` stu ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
GROUP BY stu.id
ORDER BY `Points` DESC
LIMIT 0, 20
What I don't understand is how I would do this across myriad tables. I need to be able to weight:
Late (flags, Category_ID = 10), Absconded (flags, Category_ID = 15) and Community flags (flags, Category_ID = 13) plus Safeguarding alerts (alerts, Type = 'S') are all worth 1 point
Behavioural flags (flags, Category_ID IN (1, 7, 8)) are worth 2 points
Process alerts (alerts, Type = 'P') and detention sanctions (sanctions, Category_ID = 1) are worth 3 points
So on and so forth. That's far from an exhaustive list but I've included enough variables to help me get my head round a multi-table weighted sum.
The outcome I'm looking for is just 2 columns - student's name and weighted points.
So, according to the bullet points above, if a student has received 2 Late flags (1 point each) and 1 Process alert (3 points), the output should just say Joe Bloggs and 5.
Can anyone help me to understand how I can get these weighted values from different tables into one SUM'd output for each student?
[edit] SQLFiddle here: http://sqlfiddle.com/#!9/449218/1/0
Note, I am not doing this for the bounty. Please give to someone else.
This could be done with a few LEFT JOINs of derived tables. Note you did not supply the sanctions table. But the below would appear to be well illustrative. So I created a temp table. It would seem to allow for maximum flexibility without overcomplicating a larger left join notion that might be hard to debug. Afterall, you said your real querying will be much more complicated than this. As such, build out the temp table structure more.
This loads a tmp table up with default 0's for the students in the "passed by parameter Student Year" to a stored procedure. Two updates are performed. Then selects for a result set.
Schema / Load:
create schema s38741386; -- create a test database
use s38741386;
CREATE TABLE `students` (
`id` int(11) PRIMARY KEY,
`Firstname` varchar(50) NOT NULL,
`Surname` varchar(50) NOT NULL,
`Year_Group` int(2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
# STUDENT INSERTS
INSERT INTO `students`
(`id`, `Firstname`, `Surname`, `Year_Group`)
VALUES
(201, 'Student', 'A', 9),
(202, 'Student', 'B', 9),
(203, 'Student', 'C', 9),
(204, 'Student', 'D', 9),
(205, 'Student', 'E', 9);
CREATE TABLE `alert` (
`ID` int(11) PRIMARY KEY,
`Staff_ID` int(6) NOT NULL,
`Datetime_Raised` datetime NOT NULL,
`Room_Label` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
`Type` enum('A','C','P','Q','S') COLLATE utf8_unicode_ci NOT NULL COMMENT 'A=Absconded, C=Crisis, P=Process, Q=Quiet, S=Safeguarding',
`Details` text COLLATE utf8_unicode_ci,
`Responder` int(8) DEFAULT NULL,
`Datetime_Responded` datetime DEFAULT NULL,
`Room_ID` int(11) NOT NULL COMMENT 'will be linked to internal room id.',
`Status` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'O:ngoing, R:esolved'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# ALERT INSERTS
INSERT INTO `alert`
(`ID`, `Staff_ID`, `Datetime_Raised`, `Room_Label`, `Type`, `Details`, `Responder`, `Datetime_Responded`, `Room_ID`, `Status`)
VALUES
(1, '101', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R'),
(2, '102', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R'),
(3, '102', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R'),
(4, '101', '2016-08-04 00:00:00', NULL, 'P', NULL, '103', '2016-08-04 00:00:01', '15', 'R');
CREATE TABLE `alert_students` (
`ID` int(11) PRIMARY KEY,
`Alert_ID` int(6) NOT NULL,
`Student_ID` int(6) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# ALERT_STUDENT INSERTS
INSERT INTO `alert_students`
(`ID`, `Alert_ID`, `Student_ID`)
VALUES
(1, '1', '201'),
(2, '1', '202'),
(3, '2', '201'),
(4, '3', '202'),
(5, '4', '203'),
(6, '5', '204');
CREATE TABLE `flags` (
`ID` int(11) PRIMARY KEY,
`Staff_ID` int(11) NOT NULL,
`Student_ID` int(11) NOT NULL,
`Datetime` datetime NOT NULL,
`Category_ID` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
# ALERT INSERTS
-- TRUNCATE TABLE flags;
INSERT INTO `flags`
(`ID`, `Staff_ID`, `Student_ID`, `Datetime`, `Category_ID`)
VALUES
(1, '101', '201', '2016-08-04 00:00:01', 10),
(2, '102', '202', '2016-08-04 00:00:02', 12),
(3, '102', '203', '2016-08-04 00:00:03', 10),
(4, '101', '204', '2016-08-04 00:00:04', 13),
(5, '102', '202', '2016-08-04 00:00:02', 12),
(6, '102', '203', '2016-08-04 00:00:03', 10),
(7, '101', '204', '2016-08-04 00:00:04', 13),
(8, '102', '202', '2016-08-04 00:00:02', 10),
(9, '102', '203', '2016-08-04 00:00:03', 10),
(10, '101', '204', '2016-08-04 00:00:04', 7),
(11, '101', '204', '2016-08-04 00:00:07', 8),
(12, '101', '204', '2016-08-04 00:00:08', 1),
(13, '101', '204', '2016-08-04 00:00:09', 8);
Stored Procedure:
DROP PROCEDURE IF EXISTS rptSM_by_year;
DELIMITER $$
CREATE PROCEDURE rptSM_by_year
( pSY INT -- parameter student year
)
BEGIN
DROP TEMPORARY TABLE IF EXISTS tmpStudentMetrics;
CREATE TEMPORARY TABLE tmpStudentMetrics
( `StudentId` int(11) PRIMARY KEY,
LateFP INT NOT NULL,
MobiFP INT NOT NULL,
AbscFP INT NOT NULL,
CommFP INT NOT NULL,
SafeAP INT NOT NULL,
BehaFP INT NOT NULL,
ProcAP INT NOT NULL
)ENGINE=InnoDB;
INSERT tmpStudentMetrics (StudentId,LateFP,MobiFP,AbscFP,CommFP,SafeAP,BehaFP,ProcAP)
SELECT id,0,0,0,0,0,0,0
FROM students
WHERE Year_Group = pSY;
UPDATE tmpStudentMetrics tmp
JOIN
( SELECT
stu.id,
SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) AS `LateFP`,
SUM(CASE WHEN f.Category_ID = 15 THEN 1 ELSE 0 END) AS `AbscFP`,
SUM(CASE WHEN f.Category_ID = 13 THEN 1 ELSE 0 END) AS `CommFP`,
SUM(CASE WHEN f.Category_ID = 12 THEN 2 ELSE 0 END) AS `MobiFP`,
SUM(CASE WHEN f.Category_ID IN (1,7,8) THEN 2 ELSE 0 END) AS `BehaFP`
FROM `flags` f
LEFT JOIN `students` stu ON f.Student_ID = stu.id
WHERE stu.Year_Group = pSY
GROUP BY stu.id
) xDerived
ON xDerived.id=tmp.StudentId
SET tmp.LateFP=xDerived.LateFP,
tmp.AbscFP=xDerived.AbscFP,
tmp.CommFP=xDerived.CommFP,
tmp.MobiFP=xDerived.MobiFP,
tmp.BehaFP=xDerived.BehaFP;
UPDATE tmpStudentMetrics tmp
JOIN
( SELECT
stu.id,
SUM(CASE WHEN a.Type = 'S' THEN 1 ELSE 0 END) AS `SafeAP`,
SUM(CASE WHEN a.Type = 'P' THEN 3 ELSE 0 END) AS `ProcAP`
FROM `alert_students` als
JOIN `alert` a
ON a.ID=als.Alert_ID
JOIN `students` stu
ON stu.id=als.Student_ID and stu.Year_Group = pSY
GROUP BY stu.id
) xDerived
ON xDerived.id=tmp.StudentId
SET tmp.SafeAP=xDerived.SafeAP,
tmp.ProcAP=xDerived.ProcAP;
-- SELECT * FROM tmpStudentMetrics; -- check detail
SELECT stu.id,
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
tmp.LateFP+tmp.MobiFP+tmp.AbscFP+tmp.CommFP+tmp.SafeAP+tmp.BehaFP+tmp.ProcAP AS `Points`
FROM `students` stu
JOIN tmpStudentMetrics tmp
ON tmp.StudentId=stu.id
WHERE stu.`Year_Group` = pSY
ORDER BY stu.id;
-- SELECT * FROM tmpStudentMetrics; -- check detail
DROP TEMPORARY TABLE IF EXISTS tmpStudentMetrics;
-- TEMP TABLES are connection based. Explicityly dropped above for safety when done.
-- Depends on your connection type and life-span otherwise.
END$$
DELIMITER ;
Test:
call rptSM_by_year(9);
+-----+-----------+--------+
| id | Student | Points |
+-----+-----------+--------+
| 201 | Student A | 7 |
| 202 | Student B | 11 |
| 203 | Student C | 6 |
| 204 | Student D | 10 |
| 205 | Student E | 0 |
+-----+-----------+--------+
Cleanup:
drop schema s38741386; -- drop the test database
Think all you have asked can be done with a subquery and a couple of sub-SELECTs:
SELECT `Student`,
`Late Flags` * 1
+ `Absconded Flags` * 1
+ `Community Flags` * 1
+ `Safeguarding Alerts Flags` * 1
+ `Behavioural flags` * 2
+ `Process Alerts Flags` * 3 AS `Total Points`
FROM
(
SELECT
CONCAT(stu.Firstname, " ", stu.Surname) AS `Student`,
SUM(CASE WHEN f.Category_ID = 10 THEN 1 ELSE 0 END) AS `Late Flags`,
SUM(CASE WHEN f.Category_ID = 12 THEN 1 ELSE 0 END) AS `Mobile Flags`,
SUM(CASE WHEN f.Category_ID = 15 THEN 1 ELSE 0 END) AS `Absconded Flags`,
SUM(CASE WHEN f.Category_ID = 13 THEN 1 ELSE 0 END) AS `Community Flags`,
(SELECT COUNT(*) FROM `alert` a JOIN `alert_students` ast ON ast.`Alert_ID` = a.`ID`
WHERE ast.`Student_ID` = stu.`id` AND a.`Type` = 'S') AS `Safeguarding Alerts Flags`,
SUM(CASE WHEN f.Category_ID IN (1, 7, 8) THEN 1 ELSE 0 END) AS `Behavioural flags`,
(SELECT COUNT(*) FROM `alert` a JOIN `alert_students` ast ON ast.`Alert_ID` = a.`ID`
WHERE ast.`Student_ID` = stu.`id` AND a.`Type` = 'P') AS `Process Alerts Flags`
FROM `students` stu
LEFT JOIN `flags` f ON f.Student_ID = stu.id
WHERE stu.Year_Group = 9
GROUP BY stu.id
LIMIT 0, 20
) subq
ORDER BY `Total Points` DESC;
The above query includes everything you mentioned apart from sanctions (as your original SQL Fiddle demo didn't include this table).
Demo
An updated fiddle with the above query is here: http://sqlfiddle.com/#!9/449218/39.
You could use union all
Basically you create all your individual queries for each table and connect them all together using union all.
Here is an example, I used your student table twice but you would change the second one to what ever other table you want. SQLFiddle
You can do it with LEFT JOINS:
SELECT CONCAT(stu.firstname,' ', stu.surname) student,
COALESCE(f_group.weight_sum,0) + COALESCE(a_group.weight_sum,0) + COALESCE(s_group.weight_sum,0) points
FROM students stu
LEFT JOIN (
SELECT s_f.id, SUM(f.category_id IN (10,13,15) + 2 * f.category_id IN (1,7,8)) weight_sum
FROM students s_f
JOIN flags f
ON f.student_id = s_f.id
AND f.category_id IN (1,7,8,10,13,15)
WHERE s_f.year_group = :year_group
GROUP BY s_f.id
) f_group
LEFT JOIN (
SELECT s_a.id, 3 * COUNT(*) weight_sum
FROM students s_a
JOIN alerts a
ON a.student_id = s_a.id
AND a.type = 'P'
WHERE s_a.year_group = :year_group
GROUP BY s_a.id
) a_group
LEFT JOIN (
SELECT s_s.id, COUNT(*) weight_sum
FROM students s_s
JOIN sanctions s
ON s.student_id = s_s.id
AND s.category_id = 1
WHERE s_s.year_group = :year_group
GROUP BY s_s.id
) s_group
WHERE stu.year_group = :year_group
ORDER BY points DESC
LIMIT 0, 20
BUT if you have full access to the DB I'd be putting those weights in the respective categories and types, which will simplify the logic.

MySQL, conditionally adding columns

I have two tables, the first holds data about patients, it looks like this.
id patient sex impact
------------------------------------------
1 Bill Jones male .1
2 Sarah Smith female .4
The second holds "multipliers". These multipliers will be used to multiply the impact in the table above.
id type type_value multiplier
-----------------------------------------------
1 patient Bill Jones .5
2 sex male .3
3 sex male .8
4 sex female .7
I am trying to run a query that will return the following:
patient patient_total sex_total new_impact
-------------------------------------------------------------------------------
Bill Jones .5 1.1 .16
Sarah Smith 0 .7 .28
Where the new impact is the (patient_total + sex_total) * impact for each patient.
Here are the create statements:
--
-- Table structure for table `impact`
--
CREATE TABLE IF NOT EXISTS `impact` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`patient` varchar(20) NOT NULL,
`sex` varchar(7) NOT NULL,
`impact` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=3 ;
--
-- Dumping data for table `impact`
--
INSERT INTO `impact` (`id`, `patient`, `sex`, `impact`) VALUES
(1, 'Bill Jones', 'male', 0.1),
(2, 'Sarah Smith', 'female', 0.4);
--
-- Table structure for table `multipliers`
--
CREATE TABLE IF NOT EXISTS `multipliers` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(20) NOT NULL,
`type_value` varchar(60) NOT NULL,
`multiplier` float NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=14 ;
--
-- Dumping data for table `multipliers`
--
INSERT INTO `multipliers` (`id`, `type`, `type_value`, `multiplier`) VALUES
(1, 'patient', 'Bill Jones', 0.5),
(2, 'sex', 'male', 0.3),
(3, 'sex', 'male', 0.8),
(13, 'sex', 'female', 0.7);
I have tried several iterations of the following query, but can't get it to work :/
Updated quer - working for sex_total column, not for patient_total column :/
select p.patient, ifnull(sum(ipatient.multiplier), 0) as patient_total, ifnull(sum(isex.multiplier), 0) as sex_total, (ifnull(sum(ipatient.multiplier), 0) + ifnull(sum(isex.multiplier), 0) * p.impact) as new_impact
from impact p
left outer join multipliers ipatient
on ipatient.type = 'patient' and ipatient.type_value = p.patient
left outer join multipliers isex
on isex.type = 'sex' and isex.type_value = p.sex
group by p.patient
Could someone please help?
Thanks
You just need to join in the impact rows and then aggregate the results. The following does two joins, one for each type of impact:
select p.patient, sum(ipatient.multiplier) as patient_total,
sum(isex.multiplier) as sex_total,
(sum(ipatient.multiplier) * sum(isex.multiplier) * p.imact
) as new_impact
from impact p left outer join
multipliers ipatient
on ipatient.type = 'patient' and ipatient.type_value = p.patient left outer join
multipliers isex
on isex.type = 'sex' and isex.type_value = p.sex
group by p.patient;
You might need a coalesce() if there might be some rows that have no matches for either the patient name or sex.
EDIT:
Dumb. Dumb. Dumb. The above fails because the isex multiplies the rows, so it affects the sum() of ipatient. This version works:
select p.patient,
sum(case when m.type = 'patient' then m.multiplier else 0 end) as patient_total,
sum(case when m.type = 'sex' then m.multiplier else 0 end) as sex_total,
(sum(m.multiplier)* p.impact
) as new_impact
from impact p left outer join
multipliers m
on m.type = 'patient' and m.type_value = p.patient or
m.type = 'sex' and m.type_value = p.sex
group by p.patient;
You can see it here.
If you don't like all the decimal places from the float, you can switch to a numeric/decimal data type.
This query will give you the columns you need, but you'll have to do the multiplication yourself.
SELECT i.id, i.patient, i.impact,
(SELECT IFNULL(SUM(multiplier), 0) FROM multipliers
WHERE type='patient' AND type_value=i.patient) `patient_total`,
(SELECT IFNULL(SUM(multiplier), 0) FROM multipliers
WHERE type='sex' AND type_value=i.sex) `sex_total`
FROM impact i