Check whether particular name order is available in my table - mysql

I have the following table stops how can I check whether the following stops name order GHI, JKL, MNO is available in my stops table?
stops table:
CREATE TABLE IF NOT EXISTS stops
(
stop_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
name varchar(30) NOT NULL,
lat double(10,6),
longi double(10,6)
);
Simple:
1 ABC
2 DEF
3 GHI
4 JKL
5 MNO
6 PQR
7 SDU
8 VWX

This query will return 1 when there is an ordered of 'GHI','JKL','MNO':
SELECT 1
FROM stops s1
JOIN stops s2 ON s1.stop_id = s2.stop_id - 1
JOIN stops s3 ON s2.stop_id = s3.stop_id - 1
WHERE CONCAT(s1.name, s2.name, s3.name) = CONCAT('GHI','JKL','MNO')
SQL Fiddle Demo

This is a variation of the well known "find equal sets" task.
You need to insert the searched route into a table with a sequenced stop_id:
create table my_stops(stop_id INT NOT NULL,
name varchar(30) NOT NULL);
insert into my_stops (stop_id, name)
values (1, 'GHI'),(2, 'JKL'),(3, 'MNO');
Then you join and calculate the difference between both sequences. This returns a totally meaningless number, but always the same for consecutive values:
select s.*, s.stop_id - ms.stop_id
from stops as s join my_stops as ms
on s.name = ms.name
order by s.stop_id;
Now group by that meaningless number and search for a count equal to the number of searched steps:
select min(s.stop_id), max(s.stop_id)
from stops as s join my_stops as ms
on s.name = ms.name
group by s.stop_id - ms.stop_id
having count(*) = (select count(*) from my_stops)
See Fiddle

Another alternative:
select 1
from stops x
where x.name = 'GHI'
and (select GROUP_CONCAT(name order by y.stop_id)
from stops y where y.stop_id between x.stop_id + 1
and x.stop_id + 2
) = 'JKL,MNO';

Related

Don't know how to join multipe foreign keys in a single table

I created these 4 table:
CITY
city_id
city
1
AAA
2
BBB
3
CCC
4
DDD
TRIPS
trip_id
route_id
date
time
1
1
...
...
ROUTES
route_id
city_id(from)
city_id(to)
1
1(AAA)
3(CCC)
2
1(AAA)
4(DDD)
STOPS
stop_id
route_id
city_on_the_way
1
1(AAA->CCC)
1(AAA)
2
1
2(BBB)
3
1
3(CCC)
4
2(AAA->DDD)
1(AAA)
5
2
2(BBB)
6
2
3(CCC)
7
2
4(DDD)
Im trying to get the trips that pass through city AAA and DDD at the time.
This is what I started with but I just dont know how to join the rest with each other. I tried many things and I keep getting errors. Can someone please explain to me what I need to make this work?
SELECT
trips.trip_id,
city.city AS startpoint,
city.city AS endpoint,
trips.date,
trips.time
FROM
trip
INNER JOIN
route ON trips.route_id = routes.route_id
INNER JOIN
city ON routes.city_id(from) = city.city_id;
This should do it (updated!):
WITH stps AS ( SELECT s.route_id rid, city FROM stops s INNER JOIN cities ON city_id=city_on_the_way )
SELECT trip_id, 'AAA' city1, 'DDD' city2, dt, ti
FROM trips WHERE EXISTS ( SELECT 1 FROM stps WHERE city='AAA' AND rid=route_id)
AND EXISTS ( SELECT 1 FROM stps WHERE city='DDD' AND rid=route_id)
I still believe that the table ROUTES is not really helpful in this context. The above code can be seen in action here:
dbfiddle.uk.
And I disagree with OP that there must be two results: Only trip no. 1 exists in the trips table. In my demo I actually added a second record into the trips table. Now both routes (1 and 2) are linked to trip no. 1, but only route 2 includes both destinations.
In this case, your starting point should be the stops table:
SELECT
t.trip_id
FROM stops s
JOIN trips t ON r.route_id = s.route_id
WHERE s.city_on_the_way IN (
SELECT CONCAT(city_id, "(", city, ")") as city_on_the_way
FROM city
WHERE city IN ("AAA", "DDD")
)
GROUP BY s.route_id, t.trip_id
HAVING COUNT(1) = X
// Replace X with the number of cities you desire: in this example X = 2
// So, if you want city AAA, BBB, CCC, X will be 3

Select distinct one column by conditions from the other 2 columns (how to pair them?)

I need to get the id_user by values given in other columns. My table look like this:
id(AI,PK) id_user attr_name attr_value
----------------------------------------------------
1 1 hair brown
2 1 eyes green
3 2 hair blond
4 1 age 40
5 1 sex male
6 2 eyes green
7 2 age 40
8 2 sex male
When I try a query like this:
select distinct id_user where (attr_name='hair' and attr_value='blond') or (attr_name='eyes' and attr_value='green')
I will obviously get id_user=1 and 2, because both of them have green eyes.
If I change "or" to "and" it seems that the query does not work at all. But I need "and" because 2 (or even more, I shortened my example) conditions must be met, to get the specified id_user:
select distinct id_user where (attr_name='hair' and attr_value='blond') and (attr_name='eyes' and attr_value='green')
How to "pair" those 2 brackets, so I will get only a user where both conditions met: green eyes and blond hair?
Use post aggregate filtering with HAVING.
WHERE filters rows, HAVING with aggregate functions filters groups.
SELECT id_user FROM t
GROUP BY id_user
HAVING SUM(attr_name='hair')>0 AND SUM( attr_value='blond') >0
AND SUM(attr_name='eyes')>0 AND SUM( attr_value='green') >0
Another approach is.
SELECT `u1`.`id_user`,
IF(`u1`.`attr_name` RLIKE 'hair' AND `u1`.`attr_value` = 'blond',1,0) AS `blond`,
IF(`u2`.`attr_name` RLIKE 'eyes' AND `u2`.`attr_value` = 'green',1,0) AS `green`
FROM `users` `u1`
INNER JOIN `users` `u2`
ON `u1`.`id_user`=`u2`.`id_user`
HAVING `green`+`blond` = 2;
OR
SELECT `u1`.`id_user`
FROM `users` `u1`
INNER JOIN `users` `u2`
ON `u1`.`id_user`=`u2`.`id_user`
WHERE IF(`u1`.`attr_name` RLIKE 'hair' AND `u1`.`attr_value` = 'blond',1,0) + IF(`u2`.`attr_name` RLIKE 'eyes' AND `u2`.`attr_value` = 'green',1,0) = 2;
The result is
id_user blond green
2 1 1
To re-create the example use:
create table `users` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`id_user` INT,
`attr_name` VARCHAR(255) NOT NULL,
`attr_value` VARCHAR(255) NOT NULL
) ENGINE=INNODB;
INSERT INTO `users` (`id`,`id_user`,`attr_name`,`attr_value`) VALUES (1,1,'hair','blond'),(2,1,'eyes','blue'),(3,2,'hair','blond'),(4,2,'eyes','green'),(5,2,'sex','male'),(6,2,'age','42'),(7,1,'sex','female'),(8,1,'age','39');
Try it on SQL Fiddle

How to get Database-table entries where a certain column is not linked to a certain value by another table?

I have a database with the following tables: Students, Classes, link_student_class. Where Students contains the information about the registered students and classes contains the information about the classes. As every student can attend multiple classes and every class can be attended by multiple students, I added a linking-table, for the mapping between students and classes.
Linking-Table
id | student_id | class_id
1 1 1
2 1 2
3 2 1
4 3 3
In this table both student_id as well as class_id will appear multiple times!
What I am looking for, is a SQL-Query that returns the information about all students (like in 'SELECT * FROM students') that are not attending a certain class (given by its id).
I tried the following SQL-query
SELECT * FROM `students`
LEFT JOIN(
SELECT * FROM link_student_class
WHERE class_id = $class_id
)
link_student_class ON link_student_class.student_id = students.student_id
Where $class_id is the id of the class which students i want to exclude.
In the returned object the students i want to include and those i want to exclude are different in the value of the column 'class_id'.
Those to be included have the value 'NULL' whereas those I want to exclude have a numerical value.
NOT EXISTS comes to mind:
select s.*
from students s
where not exists (select 1
from link_student_class lsc
where lsc.student_id = s.student_id and
lsc.class_id = ?
);
The ? is a placeholder for the parameter that provides the class.
you should check for NULL link_student_class.student_id
SELECT *
FROM `students`
LEFT JOIN(
SELECT *
FROM link_student_class
WHERE class_id = $class_id
) link_student_class ON link_student_class.student_id = students.student_id
where link_student_class.student_id is null
Or also a NOT IN predicate:
WITH
stud_class(id,stud_id,class_id) AS (
SELECT 1, 1,1
UNION ALL SELECT 2, 1,2
UNION ALL SELECT 3, 2,1
UNION ALL SELECT 4, 3,3
)
,
stud(stud_id,fname,lname) AS (
SELECT 1,'Arthur','Dent'
UNION ALL SELECT 2,'Ford','Prefect'
UNION ALL SELECT 3,'Tricia','McMillan'
UNION ALL SELECT 4,'Zaphod','Beeblebrox'
)
SELECT
s.*
FROM stud s
WHERE stud_id NOT IN (
SELECT
stud_id
FROM stud_class
WHERE class_id= 2
);
-- out stud_id | fname | lname
-- out ---------+--------+------------
-- out 3 | Tricia | McMillan
-- out 4 | Zaphod | Beeblebrox
-- out 2 | Ford | Prefect
-- out (3 rows)
-- out
-- out Time: First fetch (3 rows): 9.516 ms. All rows formatted: 9.550 ms

SELECT MAX DATE for each ID

I have two calls this "tipo_hh" and "tipo_hh_historial".
I need to make a join between the two tables, where "id" is the same in both tables.
But I need that for each "id" in the table "tipo_hh" select the "valor" on the table "tipo_hh_historial" with the condition that is the record with "fecha_cambio" and "hora_cambio" maxima.
"id" is primary key and auto increment in the table "tipo_hh"
Something like this.
This is the table "tipo_hh"
id nombre
1 Reefer
2 Lavados
3 Dry
4 Despacho
This is the table "tipo_hh_historial"
id valor fecha_cambio hora_cambio
1 1.50 27/06/2013 19:15:05
1 5.50 27/06/2013 19:19:32
1 5.50 27/06/2013 19:20:06
1 2.50 27/06/2013 21:03:30
2 4.66 27/06/2013 19:15:17
2 3.00 27/06/2013 19:20:22
3 5.00 27/06/2013 19:20:32
4 1.50 27/06/2013 19:20:50
And I need this:
id nombre valor
1 Reefer 2.50
2 Lavados 3.00
3 Dry 5.00
4 Despacho 1.50
Using a sub query to get the max date / time for the historical record for each id, and using that to get the rest of the latest historical record:-
SELECT tipo_hh.id, tipo_hh.nombre, tipo_hh_historial.valor
FROM tipo_hh
INNER JOIN
(
SELECT id, MAX(STR_TO_DATE(CONCAT(fecha_cambio, hora_cambio), '%d/%m/%Y%k:%i:%s')) AS MaxDateTime
FROM tipo_hh_historial
GROUP BY id
) Sub1
ON tipo_hh.id = Sub1.id
INNER JOIN tipo_hh_historial
ON tipo_hh_historial.id = Sub1.id
AND STR_TO_DATE(CONCAT(fecha_cambio, hora_cambio), '%d/%m/%Y%k:%i:%s') = Sub1.MaxDateTime
SQL Fiddle:-
http://www.sqlfiddle.com/#!2/68baa/2
First of all you should use proper data types for your columns like for date there should a column of type data same as for the time column in you sample data set you have date formatted as '%d/%m/%Y' id this could be change to standard format '%Y-%m-%d' this will be good to so the below query is for proper types for the columns
SELECT t.* ,new_tipo_hh_historial.`valor`
FROM tipo_hh_new t
JOIN (
SELECT th.*
FROM tipo_hh_historial_new th
JOIN (
SELECT id,valor,
MAX(fecha_cambio ) fecha_cambio
,MAX(hora_cambio) hora_cambio
FROM `tipo_hh_historial_new`
GROUP BY id
) thh
ON (
th.`id` =thh.`id`
AND th.fecha_cambio=thh.`fecha_cambio`
AND th.hora_cambio = thh.`hora_cambio`
)
) new_tipo_hh_historial
USING (id)
Fiddle Demo
And for in case you have date and time stored as string then you need to format them as real types you can use below query but not recommended
SELECT t.* ,new_tipo_hh_historial.`valor`
FROM tipo_hh t
JOIN (
SELECT th.*
FROM tipo_hh_historial th
JOIN (
SELECT id,valor,
MAX(STR_TO_DATE(fecha_cambio , '%d/%m/%Y')) fecha_cambio
,MAX(TIME_FORMAT(hora_cambio,'%H:%i:%s')) hora_cambio
FROM `tipo_hh_historial`
GROUP BY id
) thh
ON (
th.`id` =thh.`id`
AND STR_TO_DATE(th.fecha_cambio , '%d/%m/%Y')=thh.`fecha_cambio`
AND TIME_FORMAT(th.hora_cambio,'%H:%i:%s') = thh.`hora_cambio`
)
) new_tipo_hh_historial
USING (id)
Fiddle Demo
Your problem seems like the greatest-n-per-group problem so you can first get the maxima from your table tipo_hh_historial maxima of fecha_cambio and hora_cambio and need to self join with multiple conditions to get the maximums like i.e
ON (
th.`id` =thh.`id`
AND th.fecha_cambio=thh.`fecha_cambio`
AND th.hora_cambio = thh.`hora_cambio`
)
and then join with your first table to get the expected results
Edit: the problem spotted by #Kickstart he already answered so i will provide the another way to overcome.There should be single field to store the date and time for the record like for fecha_cambio DATETIME so there will no chance to miss the id and get the correct maxima for date and time.See below updated query
SELECT t.* ,new_tipo_hh_historial.`valor`
FROM tipo_hh_new t
JOIN (
SELECT th.*
FROM tipo_hh_historial_alter th
JOIN (
SELECT id,valor,
MAX(fecha_cambio ) fecha_cambio
FROM `tipo_hh_historial_alter`
GROUP BY id
) thh
ON (
th.`id` =thh.`id`
AND th.fecha_cambio=thh.`fecha_cambio`
)
) new_tipo_hh_historial
USING (id)
Updated fiddle demo
try this:
SELECT A.id, B.nombre, A.valor, MAX(A.hora_cambio) AS hora_cambio_time
FROM tipo_hh_historial AS A
INNER JOIN tipo_hh AS B
ON(A.id = B.id)
GROUP BY A.id
SELECT tipo_hh.id, tipo_hh.nombre, tipo_hh_historial.valor
FROM tipo_hh INNER JOIN tipo_hh_historial
ON tipo.id = tipo_hh_historial.id AS
group by tipo_hh_historial.id
Having max(tipo_hh_historial.hora_cambio);

Find differences in name-value pairs across version IDs

Say I have a table with 3 columns:version_id, name, value.
Conceptually, this table has a bunch of name-value pairs for each version_id.
How can I write a query that will show only the name value pairs of the top two version_ids where the name value pair is not the same across version-ids?
Additionally, I am wondering if there is a way to put the differing name-value pairs from the different version_ids side by side, or have the rows be right next to each other in the results.
Basically, I want like a diff of the two versions.
Example:
version_id name value
23459 jsLibrary2 JQuery_1_4_3
23459 jsLibrary1 CrossDomainAjax_1_0
23456 jsLibrary2 JQuery_1_4_2
23456 jsLibrary1 CrossDomainAjax_1_0
23456 groovyInclude2 GroovyUtilities
23454 jsLibrary2 JQuery_1_4_2
23454 jsLibrary1 CrossDomainAjax_1_0
23454 groovyInclude2 GroovyUtilities
Ideal query result:
23456 jsLibrary2 JQuery_1_4_2
23459 jsLibrary2 JQuery_1_4_3
23456 groovyInclude2 GroovyUtilities
23459 NULL NULL
Note that ideally it would note new name-value pairs (where the name doesn't exist in the smaller version_id) and deleted name-value pairs (where the name doesn't exist in the larger version_id)
I'm sure this can be simplified — or at least, I really hope it can — but:
SELECT name,
version_id_before,
( SELECT value
FROM property_history
WHERE name = t.name
AND version_id = version_id_before
) AS value_before,
( SELECT MIN(version_id)
FROM property_history
WHERE version_id > version_id_before
) AS version_id_after,
( SELECT value
FROM property_history
WHERE name = t.name
AND version_id =
( SELECT MIN(version_id)
FROM property_history
WHERE version_id > version_id_before
)
) AS value_after
FROM ( SELECT name,
CASE WHEN EXISTS
( SELECT 1
FROM property_history
WHERE name = ph1.name
AND version_id =
( SELECT MAX(version_id)
FROM property_history
)
)
THEN ( SELECT MAX(version_id)
FROM property_history ph2
WHERE NOT EXISTS
( SELECT 1
FROM property_history
WHERE name = ph1.name
AND version_id = ph2.version_id
AND value =
( SELECT value
FROM property_history
WHERE name = ph1.name
AND version_id =
( SELECT MAX(version_id)
FROM property_history
)
)
)
)
ELSE ( SELECT MAX(version_id)
FROM property_history
WHERE name = ph1.name
)
END AS version_id_before
FROM property_history ph1
GROUP
BY name
) AS t
WHERE version_id_before IS NOT NULL
;
(Disclaimer: tested only using your example data-set, for which it gives the result:
+----------------+-------------------+-----------------+------------------+--------------+
| name | version_id_before | value_before | version_id_after | value_after |
+----------------+-------------------+-----------------+------------------+--------------+
| groovyInclude2 | 23456 | GroovyUtilities | 23459 | NULL |
| jsLibrary2 | 23456 | JQuery_1_4_2 | 23459 | JQuery_1_4_3 |
+----------------+-------------------+-----------------+------------------+--------------+
I haven't made any effort to construct other data-sets to test it on.)
I think you'll need to use a couple of subqueries to get the desired results since you are looking for the first and second values. I'm assuming that the name is the 'key' that you have to group on, in which case something along these lines should work:
Select
firstVersion.firstVersionId,
firstVersionDetails.name as firstVersionName,
firstVersionDetails.value as firstVersionValue,
--second version values will be null if there is no second value
secondVersion.secondVersionId,
secondVersionDetails.name as secondVersionName, --always the same as firstVersionName because name is a key field
secondVersionDetails.value as secondVersionValue
From
(
Select
name,
Max(version_id) as firstVersionId
From versions
Group by name
) as firstVersion
join versions as firstVersionDetails--inner join because every name has a first version
on firstVersions.version_id = firstVersion.firstVersionId
left outer Join --outer join so we always get the first version and get the second version whenever there is one (in other words, does *not* limit data to names with at least 2 versions)
(
select
name,
Max(version_id) as secondVersionId
from versions
Group by name
) as secondVersion
on firstVersion.name=secondVersion.name
and secondVersion.version_id < firstVersion.firstVersionId --exclude the first version when calculating the 'max'. This is the part of the join that allows us to identify the second version
left outer join versions as secondVersionDetails --using outer join again so we don't limit our data to names with 2 versions
on secondVersion.secondVersionId = secondVersionDetails.version_id
Happy querying! :-)
How about this approach -
SELECT MAX(version_id) INTO #cur FROM tbl;
SELECT MAX(version_id) INTO #prev FROM tbl WHERE version_id < #cur;
SELECT name, #prev, MAX(IF(version_id = #prev, value, '')) AS prev_val, #cur, MAX(IF(version_id = #cur, value, '')) AS cur_val
FROM tbl
WHERE version_id IN (#prev, #cur)
GROUP BY name
HAVING cur_val <> prev_val;