Mysql subquery with joins - mysql

I have a table 'service' which contains details about serviced vehicles. It has an id and Vehicle_registrationNumber which is a foreign key. Whenever vehicle is serviced, a new record is made. So, for example if I make a service for car with registration ABCD, it will create new row, and I will set car_reg, date and car's mileage in the service table (id is set to autoincreament) (e.g 12 | 20/01/2012 | ABCD | 1452, another service for the same car will create row 15 | 26/01/2012 | ABCD | 4782).
Now I want to check if the car needs a service (the last service was either 6 or more months ago, or the current mileage of the car is more than 1000 miles since last service), to do that I need to know the date of last service and the mileage of the car at the last service. So I want to create a subquery, that will return one row for each car, and the row that I'm interested in is the newest one (either with the greatest id or latest endDate). I also need to join it with other tables because I need this for my view (I use CodeIgniter but don't know if it's possible to write subqueries using CI's ActiveRecord class)
SELECT * FROM (
SELECT *
FROM (`service`)
JOIN `vehicle` ON `service`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
JOIN `branch_has_vehicle` ON `branch_has_vehicle`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
JOIN `branch` ON `branch`.`branchId` = `branch_has_vehicle`.`Branch_branchId`
GROUP BY `service`.`Vehicle_registrationNumber` )
AS temp
WHERE `vehicle`.`available` != 'false'
AND `service`.`endDate` <= '2011-07-20 20:43'
OR service.serviceMileage < vehicle.mileage - 10000

SELECT `service`.`Vehicle_registrationNumber`, Max(`service`.`endDate`) as lastService,
MAX(service.serviceMileage) as lastServiceMileage, vehicle.*
FROM `service`
INNER JOIN `vehicle`
ON `service`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
INNER JOIN `branch_has_vehicle`
ON `branch_has_vehicle`.`Vehicle_registrationNumber` = `vehicle`.`registrationNumber`
INNER JOIN `branch`
ON `branch`.`branchId` = `branch_has_vehicle`.`Branch_branchId`
WHERE vehicle.available != 'false'
GROUP BY `service`.`Vehicle_registrationNumber`
HAVING lastService<=DATE_SUB(CURDATE(),INTERVAL 6 MONTH)
OR lastServiceMileage < vehicle.mileage - 10000
;
I hope I have no typo in it ..

If instead of using * in the subquery you specify the fields you need (which is always good practice anyway), most databases have a MAX() function that returns the maximum value within the group.
Actually, you don't even need the subquery. You can do the joins and use the MAX in the SELECT statement. Then you can do something like
SELECT ...., MAX('service'.'end_date') AS LAST_SERVICE
...
GROUP BY 'service'.'Vehicle_registrationNumber'
Or am I missing something?

Related

Joining and selecting multiple tables and creating new column names

I have very limited experience with MySQL past standard queries, but when it comes to joins and relations between multiple tables I have a bit of an issue.
I've been tasked with creating a job that will pull a few values from a mysql database every 15 minutes but the info it needs to display is pulled from multiple tables.
I have worked with it for a while to figure out the relationships between everything for the phone system and I have discovered how I need to pull everything out but I'm trying to find the right way to create the job to do the joins.
I'm thinking of creating a new table for the info I need, with columns named as:
Extension | Total Talk Time | Total Calls | Outbound Calls | Inbound Calls | Missed Calls
I know that I need to start with the extension ID from my 'user' table and match it with 'extensionID' in my 'callSession'. There may be multiple instances of each extensionID but each instance creates a new 'UniqueCallID'.
The 'UniqueCallID' field then matches to 'UniqueCallID' in my 'CallSum' table. At that point, I just need to be able to say "For each 'uniqueCallID' that is associated with the same 'extensionID', get the sum of all instances in each column or a count of those instances".
Here is an example of what I need it to do:
callSession Table
UniqueCallID | extensionID |
----------------------------
A 123
B 123
C 123
callSum table
UniqueCallID | Duration | Answered |
------------------------------------
A 10 1
B 5 1
C 15 0
newReport table
Extension | Total Talk Time | Total Calls | Missed Calls
--------------------------------------------------------
123 30 3 1
Hopefully that conveys my idea properly.
If I create a table to hold these values, I need to know how I would select, join and insert those things based on that diagram but I'm unable to construct the right query/statement.
You simply JOIN the two tables, and do a group by on the extensionID. Also, add formulas to summarize and gather the info.
SELECT
`extensionID` AS `Extension`,
SUM(`Duration`) AS `Total Talk Time`,
COUNT(DISTINCT `UniqueCallID`) as `Total Calls`,
SUM(IF(`Answered` = 1,0,1)) AS `Missed Calls`
FROM `callSession` a
JOIN `callSum` b
ON a.`UniqueCallID` = b.`UniqueCallID`
GROUP BY a.`extensionID`
ORDER BY a.`extensionID`
You can use a join and group by
select
a.extensionID
, sum(b.Duration) as Total_Talk_Time
, count(b.Answered) as Total_Calls
, count(b.Answered) -sum(b.Answered) as Missed_calls
from callSession as a
inner join callSum as b on a.UniqueCallID = b.UniqueCallID
group by a.extensionID
This should do the trick. What you are being asked to do is to aggregate the number of and duration of calls. Unless explicitly requested, you do not need to create a new table to do this. The right combination of JOINs and AGGREGATEs will get the information you need. This should be pretty straightforward... the only semi-interesting part is calculating the number of missed calls, which is accomplished here using a "CASE" statement as a conditional check on whether each call was answered or not.
Pardon my syntax... My experience is with SQL Server.
SELECT CS.Extension, SUM(CA.Duration) [Total Talk Time], COUNT(CS.UniqueCallID) [Total Calls], SUM(CASE CS.Answered WHEN '0' THEN SELECT 1 ELSE SELECT 0 END CASE) [Missed Calls]
FROM callSession CS
INNER JOIN callSum CA ON CA.UniqueCallID = CS.UniqueCallID
GROUP BY CS.Extension

SQL Validate a column with the same column

I have the following situation. I have a table with all info of article. I will like to compare the same column with it self. because I have multiple type of article. Single product and Master product. the only way that I have to differences it, is by SKU. for example.
ID | SKU
1 | 11111
2 | 11112
3 | 11113
4 | 11113-5
5 | 11113-8
6 | 11114
7 | 11115
8 | 11115-1-W
9 | 11115-2
10 | 11116
I only want to list or / and count only the sku that are full unique. follow th example the sku that are unique and no have variant are (ID = 1, 2, 6 and 10) I will want to create a query where if 11113 are again on the column not cout it. so in total I will be 4 unique sku and not "6 (on total)". Please let me know. if this are possible.
Assuming the length of master SKUs are 5 characters, try this:
select a.*
from mytable a
left join mytable b on b.sku like concat(a.sku, '%')
where length(a.sku) = 5
and b.sku is null
This query joins master SKUs to child ones, but filters out successful joins - leaving only solitary master SKUs.
You can do this by grouping and counting the unique rows.
First, we will need to take your table and add a new column, MasterSKU. This will be the first five characters of the SKU column. Once we have the MasterSKU, we can then GROUP BY it. This will bundle together all of the rows having the same MasterSKU. Once we are grouping we get access to aggregate functions like COUNT(). We will use that function to count the number of rows for each MasterSKU. Then, we will filter out any rows that have a COUNT() over 1. That will leave you with only the unique rows remaining.
Take that unique list and LEFT JOIN it back into your original table to grab the IDs.
SELECT ID, A.MasterSKU
FROM (
SELECT
MasterSKU = SUBSTRING(SKU,1,5),
MasterSKUCount = COUNT(*)
FROM MyTable
GROUP BY SUBSTRING(SKU,1,5)
HAVING COUNT(*) = 1
) AS A
LEFT JOIN (
SELECT
ID,
MasterSKU = SUBSTRING(SKU,1,5)
FROM MyTable
) AS B
ON A.MasterSKU = B.MasterSKU
Now one thing I noticed from you example. The original SKU column really looks like three columns in one. We have multiple values being joined with hypens.
11115-1-W
There may be a reason for it, but most likely this violates first normal form and will make the database hard to query. It's part of the reason why such a complicated query is needed. If the SKU column really represents multiple things then we may want to consider breaking it out into MasterSKU, Version, and Color or whatever each hyphen represents.

Retrieve rows that have a first entry in 2014 in MySQL

I want to retrieve all rows from a table that have their first entry on or after 01/01/2014 but no later than 31/12/2014
Example of the table:
OID FK_OID Treatment Trt_DATE
1 100 19304 2011-05-24
2 100 19304 2011-08-01
3 100 19306 2014-03-05
4 200 19305 2012-02-02
5 300 19308 2014-01-20
6 400 19308 2014-06-06
For example. I would like to pull all entries that have STARTED treatment in 2014. So above i would to extract FK_OID's 300 and 400 because their first entry is in 2014, but i would like to omit FK_OID 100 because they have 2 entries prior to 2014.
How do i go about this? I can extract all entries within a date range etc but that brings back all entries for that date and doesn't omit anyone who has an entry prior to the start of the date range. It just returns their first entry in 2014.
For the ones who need to see that i have tried something. See below.
I am not an experienced coder and this is the best i can get because i don't have the knowledge.
SELECT
mod,
(select NHSNum from person p
WHERE
p.oid = t.fk_oid) as 'NHS'
FROM
timeline t
Where trt_date BETWEEN '2014-01-01' AND '2014-12-31'
ORDER BY trt_date ASC
This returns every treatment for 2014 regardless of whether it is the first ever one for that person. I want to omit anyone from this list who has had treatment before 01/01/2014 as well as only return the first treatment per person. For example, this code returns all treatments for all people in 2014. I only want their first one and only if it is their first one ever.
Thanks.
create table aThing
( oid int auto_increment primary key,
fk_oid int not null,
treatment int not null,
trt_date date not null
);
insert aThing (fk_oid,treatment,trt_date) values
(100, 19304, '2011-05-24'),
(100, 19304, '2011-08-01'),
(100, 19306, '2014-03-05'),
(200, 19305, '2012-02-02'),
(300, 19308, '2014-01-20'),
(400, 19308, '2014-06-06');
select fk_oid,dt
from
( select fk_oid,min(trt_date) as dt
from aThing
group by fk_oid
) xDerived
where year(dt)=2014;
+--------+------------+
| fk_oid | dt |
+--------+------------+
| 300 | 2014-01-20 |
| 400 | 2014-06-06 |
+--------+------------+
The inner part, the nested one, become a derived table, and is given a name xDerived. This means that even though it is just a result set, by making it a derived table, it can be referred to by name. So it is not a physical table, but a derived one, or virtual one.
So that derived table is a very simple group by with an aggregate function. It says, for every fk_oid, bring back one row and only 1 row, with its minimum value for trt_date.
So if you have 10 million rows in that table called aThing, but only 17 distinct values for fk_oid, it will return only 17 rows. Each row being the minimum of trt_date for its fk_oid.
So now that that is achieved, the outer wrapper says just show me those two columns (but with a year check). There is a complicated to explain reason why I had to do that, so I will try to do it here.
But I might need a little time to explain it well, so bear with me.
This will be a shortcut way to say it. I had to get the min into an alias, and I only had access to that alias if resolved in a derived table, to cleanse it so to speak, and then access it with an outer wrapper.
An alias of aggregate column, like as dt, is not available (as a pseudo like column name which is what an alias is) ... it is not available in a where clause. But by wrapping it in a derived table name, I cleanse it so to speak, and then I can access it in a where clause.
So I can't access it directly in its own query in the where clause, but when I wrap it in an envelope (a derived table), I can access it on the outside.
I will try better to explain it later, maybe, but I would have to show alternative attempts to gain access to results, and the syntax errors that would result.
There's probably a more elegant solution, but this seems to satisfy the requirement...
SELECT x.*
FROM my_table x
JOIN
( SELECT fk_oid
, MIN(trt_date) min_date
FROM my_table
GROUP
BY fk_oid
HAVING min_date > '2014-01-01'
) a
ON a.fk_oid = x.fk_oid
LEFT
JOIN my_table b
ON b.fk_oid = a.fk_oid
AND b.trt_date > '2014-12-31'
WHERE b.oid IS NULL;
Having a few years a experience with this, i decided to revisit it. The solution i now use regularly is:
SELECT t1.column1, t1.column2
FROM MyTable AS t1
LEFT OUTER JOIN MyTable AS t2
ON t1.fkoid = t2.fkoid
AND (t1.date > t2.date
OR (t1.date = t2.date AND t1.oid > t2.oId))
WHERE t2.fkoid IS NULL and t1.date >= '2014-01-01'

Updating certain data from one table to another table once a week

My end goal is to update information from table 1 to table 2 on a certain condition. More specifically, I'd like to update the date from table1 to table2 where the id's match and dates from table2 are NULL.
I want this to happen every Sunday.
Here are the specifics:
** Keep in mind, payee_id and id are the same but in two different tables. **
The table I plan to copy from is called orders, but I only want to select certain data from this table. My query looks like this:
TABLE 1
SELECT movement.payee_id,
min(origin_stop.sched_arrive_early) first_date
FROM orders
LEFT JOIN movement_order ON orders.id = movement_order.order_id
LEFT JOIN movement ON movement.id = movement_order.movement_id
LEFT JOIN stop as origin_stop ON origin_stop.id = movement.origin_stop_id
WHERE orders.status <> 'V'
GROUP BY movement.payee_id
OUTPUT 1
payee_id | first_date
-------------------|-----------------
STRINGID1 | 2013-12-20 15:00:00.000
STRINGID2 | 2013-12-27 13:00:00.000
TABLE 2
SELECT id, initial_date
FROM drs_payee
OUTPUT 2
id | initial_date
-------------------|-----------------
STRINGID1 | NULL
STRINGID2 | NULL
TABLE 2 OUTPUT SHOULD BE:
id | initial_date
-------------------|-----------------
STRINGID1 | 2013-12-20 15:00:00.000
STRINGID2 | 2013-12-27 13:00:00.000
My attempt to solve this:
UPDATE drs_payee a
(SELECT movement.payee_id,
min(origin_stop.sched_arrive_early) carrier_first_shipped_date
FROM orders
LEFT JOIN movement_order ON orders.id = movement_order.order_id
LEFT JOIN movement ON movement.id = movement_order.movement_id
LEFT JOIN stop as origin_stop ON origin_stop.id = movement.origin_stop_id
WHERE orders.status <> 'V'
GROUP BY movement.payee_id) b
ON a.id = b.payee_id
SET a.initial_date = b.first_date
WHERE a.initial_date IS NULL
Not sure if the format of this is correct and if you can even do a SELECT inside an UPDATE like shown above.
I believe I have to have a loop to find id's that match (i.e. where payee_id = id and initial_date is NULL). Would this be created in a stored procedure to loop through id's and create a weekly job schedule or would some type of update query be enough? Please help or point me in some direction with the an update query or stored procedure example using my data if possible.
Thanks in advance everyone!
For scheduled queries the best thing for me is creating events. First you have to select your db, and at the top right you'll see an option in phpmyadmin called "events". You have to turn on the events planner, and to do that you mut be logged in with a super privilege user, in phpMyadmin.
After you do that the query is:
DROP EVENT `dates update` ;
CREATE DEFINER = `your_user_name`#`your_host_name`
EVENT `dates update`
ON SCHEDULE EVERY1 WEEK STARTS '2015-07-29 03:00:00'
ON COMPLETION NOT PRESERVE ENABLE DO
UPDATE movement a
INNER JOIN orders b
ON a.payee_id = b.payee_id
SET a.initial_date = b.first_ship_date
WHERE a.initial_date IS NULL;
It must work.
If you need help about super privilege check this
I hope it works for you.
Caio Ortega
Update table2
inner join table1 on payee_id = id
set initial_date=first_date where initial_date is null
Through SQL server agent we can schedule the query as per our requirement (For example every Sunday )

Find booking overlaps to check dates availability

i asking your help to write an query for checking the availability of an room inside MySql.
Currently i have this kind of table:
ROOM | FROM | TO
-----------------------------
101 | 2014-08-09 | 2014-08-14
102 | 2014-08-09 | 2014-08-14
... ... ...
So i have the room 101 booked from 09-08-2014 to 14-08-2014, my query to check availability is look like =
SELECT order_id FROM booking
WHERE `ROOM` = '101'
AND (`FROM` BETWEEN '2014-08-08' AND '2014-08-20')
AND (`TO` BETWEEN '2014-08-08' AND '2014-08-20')
In the above example i will check the availability in the dates between
What i trying to archive is this
Order --------09++++++++++13--------------
Check1 -----08+++++++++++++++++++++++++17-- Not availble
Check2 -----------------12+++++++++++++17-- Not availble
Check3 -----------10----------------------- Not availble
Check4 -----------10+11-------------------- Not availble
Check5 -----------------------14+++++++17-- Available
Check6 --07++++09-------------------------- Not availble
Check7 --------------------------15-------- Availble
SCALE 6-07-08-09-10-11-12-13-14-15-16-17-18-19...
I must check if the room is available. So if i get some result out of that query that means that the room is already booked... if i get nothing just the opposite...
First, let's generalize an algorithm for how to check for an overlap between intervals [a,b] and [c,d]. Note the square braces on those intervals, which means an inclusive interval. We can use this logic to check for an interval overlap:
a <= d and b >= c
If that condition is true, then we have an overlap.
So to apply this algorithm to SQL, we could do something like this:
a = 2014-08-08
b = 2014-08-20
c = FROM
d = TO
SELECT order_id FROM booking
WHERE NOT EXISTS (
SELECT * FROM booking
WHERE ROOM = '101'
AND '2014-08-08' <= `TO`
AND '2014-08-20' >= `FROM`
)
AND ROOM = '101'
The other problem with your approach is that you are checking to see if a room is available, and the assumption here is that if the room is available, then you will book it with another SQL statement. This is a problematic approach, because there is the possibility that you could double book a room. Consider the possibility that two processes check for room availability at the same (or close to the same) time. Or another example would be if this code were part of a transaction that hadn't been committed yet. The other process wouldn't see your committed result, and thus, would double book the room.
To remedy this flawed approach, we need to lock the room row before we check for its availability. Assuming you have some other table called ROOM, you could lock the row using a 'FOR UPDATE' statement:
SELECT * FROM `ROOM` WHERE ROOM = '101' FOR UPDATE
The "FOR UPDATE" will lock that room row, which will prevent another process from checking that room for availability until your transaction is finished. After you lock the row, you could run your overlap check. Thus, you eliminate the double booking problem.
You can read more about 'FOR UPDATE' here.
If you want to check whether room is available for the whole period, look for existing bookings that overlap with period in question:
SELECT order_id FROM booking
WHERE `ROOM` = '101'
AND `FROM` <= '2014-08-20'
AND `TO` >= 2014-08-08'
If query returns rows, you have a reservation conflict and room is not available.
To test whether a proposed room booking conflicts with (overlaps) an existing one, you want something like this:
select count(*) as num_conflicts
from booking
where room = ?proposed_room
and (from <= ?proposed_to)
and (to >= ?proposed_from);
That counts a conflict when any day of the proposed booking is already assigned to another booking.