field in subquery based on age of row instead of "group by" - mysql

I can't seem to get this query right. I have tables like this (simplified):
person: PersonID, ...other stuff...
contact: ContactID, PersonID, ContactDate, ContactTypeID, Description
I want to get a list of all the people who had a contact of a certain type (or types) but none of another type(s) that occurred later. An easy-to-understand example: Checking for records of gifts received without having sent a thank-you card afterward. There might have been other previous thank-you cards sent (pertaining to other gifts), but if the most recent occurrence of a Gift Received (we'll say that's ContactTypeID=12) was not followed by a Thank You Sent (ContactTypeID=11), the PersonID should be in the result set. Another example: A mailing list would be made up of everyone who has opted in (12) without having opted out (11) more recently.
My attempt at a query is this:
SELECT person.PersonID FROM person
INNER JOIN (SELECT PersonID,ContactTypeID,MAX(ContactDate) FROM contact
WHERE ContactTypeID IN (12,11) GROUP BY PersonID) AS seq
ON person.PersonID=seq.PersonID
WHERE seq.ContactTypeID IN (12)`
It seems that the ContactTypeID returned in the subquery is for the last record entered in the table, regardless of which record has the max date. But I can't figure out how to fix it. Sorry if this has been asked before (almost everything has!), but I don't know what terms to search for.

Wow. A system to check who has been good and sent thank yous. I think I would be in your list...
Anyway. Give this a go. The idea is to create two views: the first with personId and the time of the most recently received gift and the second with personId and the most recently sent thanks. Join them together using a left outer join to ensure that people who have never sent a thank you are included and then add in a comparison between the most recently received time and the most recent thanks time to find impolite people:
select g.personId,
g.mostRecentGiftReceivedTime,
t.mostRecentThankYouTime
from
(
select p.personId,
max(ContactDate) as mostRecentGiftReceivedTime
from person p inner join contact c on p.personId = c.personId
where c.ContactTypeId = 12
group by p.personId
) g
left outer join
(
select p.personId,
max(ContactDate) as mostRecentThankYouTime
from person p inner join contact c on p.personId = c.personId
where c.ContactTypeId = 11
group by p.personId
) t on g.personId = t.personId
where t.mostRecentThankYouTime is null
or t.mostRecentThankYouTime < g.mostRecentGiftReceivedTime;
Here is the test data I used:
create table person (PersonID int unsigned not null primary key);
create table contact (
ContactID int unsigned not null primary key,
PersonID int unsigned not null,
ContactDate datetime not null,
ContactTypeId int unsigned not null,
Description varchar(50) default null
);
insert into person values (1);
insert into person values (2);
insert into person values (3);
insert into person values (4);
insert into contact values (1,1,'2013-05-01',12,'Person 1 Got a present');
insert into contact values (2,1,'2013-05-03',11,'Person 1 said "Thanks"');
insert into contact values (3,1,'2013-05-05',12,'Person 1 got another present. Lucky person 1.');
insert into contact values (4,2,'2013-05-01',11,'Person 2 said "Thanks". Not sure what for.');
insert into contact values (5,2,'2013-05-08',12,'Person 2 got a present.');
insert into contact values (6,3,'2013-04-25',12,'Person 3 Got a present');
insert into contact values (7,3,'2013-04-30',11,'Person 3 said "Thanks"');
insert into contact values (8,3,'2013-05-02',12,'Person 3 got another present. Lucky person 3.');
insert into contact values (9,3,'2013-05-05',11,'Person 3 said "Thanks" again.');
insert into contact values (10,4,'2013-04-30',12,'Person 4 got his first present');

Related

Fuzzy match on a left join

I am looking to join two tables where there's a 90% match for example.
Taking the example below, I want to join table A and table B on the phone number. You can see that the phone numbers differ slightly (the international code). I'd like the end result to show as table C.
I imagine it'll be something like but the join would specify to match on 90% of the phone_number
select
a.*,
b.most_recent_booking_date
from a
left join b
on a.phone_number = b.phone_number
Hope that's clear and any help would be great! Cheers!
Table A
Phone number
Most recent call date
441234567891
01/05/22
441234567892
02/05/22
Table B
Phone number
Most recent booking date
+441234567891
03/05/22
+441234567892
04/05/22
Table C
Phone number
Most recent call date
Most recent bookingdate
441234567891
01/05/22
03/05/22
441234567892
02/05/22
04/05/22
You can try something like this, but I don't like it, as Demeteor says you should have an ID to join on. Note that I use a left join here, in case there is no data in table #T2. I was also considering a computed column where it removes the + and then you could join that way too. I will also get told off for SQL injection if the phone number could be dodgy.
CREATE TABLE #T1 (
PhoneNumber VARCHAR(20) NOT NULL,
CallDate DATE NOT NULL
);
CREATE TABLE #T2 (
PhoneNumber VARCHAR(20) NOT NULL,
BookingDate DATE NOT NULL
);
INSERT INTO #T1 (PhoneNumber, CallDate)
VALUES
('441234567891', '20220501'),
('441234567892', '20220502');
INSERT INTO #T2 (PhoneNumber, BookingDate)
VALUES
('+441234567891', '20220503'),
('+441234567892', '20220504');
GO
SELECT *
FROM #T1 AS T1
LEFT JOIN #T2 AS T2 ON T2.PhoneNumber LIKE '%' + T1.PhoneNumber;

Associated Name spiderweb

Say for instance I have the following entries in my table:
ID - 1
Name - Daryl
ID - 2
Name - Terry
ID - 3
Name - Dave
ID - 4
Name - Mitch
I eventually wish to search my table(s) for one specific name, but show all associated names. For instance,
Searching Daryl will return Terry, Dave & Daryl.
Searching Terry will return Dave, Daryl & Terry
Searching Mitch will only return Mitch.
The current table housing the names is as followed:
--
-- Table structure for table `members`
--
CREATE TABLE `members` (
`ID` int(255) NOT NULL,
`GuildID` int(255) NOT NULL,
`ToonName` varchar(255) NOT NULL,
`AddedOn` date NOT NULL,
`AddedByID` int(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
--
-- Dumping data for table `members`
--
INSERT INTO `members` (`ID`, `GuildID`, `ToonName`, `AddedOn`, `AddedByID`) VALUES
(1, 1, 'Daryl', '2020-01-17', 5),
(2, 1, 'Terry', '2020-01-17', 5),
(3, 1, 'Mitch', '2020-01-17', 5),
(4, 1, 'Dave', '2020-01-17', 5);
--
For Reference. GuildID will be a default search criteria based on the searchers login details. With a spiderweb like this, how would I go about creating another table (or another Column) to bring a combined search spiderweb structure based on the search criteria?
I was thinking something along the lines of:
CREATE TABLE `Associated`(
`ID` INT(255) NOT NULL,
`MainID` INT(255) NOT NULL,
`SecondaryID` INT(255) NOT NULL,
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
INSERT INTO `Associated` (`ID`, `MainID`, `SecondaryID`) VALUES
(1, 1, 2) -- Daryl Associated With Terry
(2, 1, 4) -- Daryl Associated With Dave
But I feel this will make an over complicated value structure with alot of redundant inputs. Is there a more effective way to create a unified search?
The whole idea of operation is that each name is Individual. So certain Entries can be put under Daryl, Terry acting alone. But one search will bring together all associated Names by searching one name then pull together total entries based on the alisas?
You can try This
Select IFNULL(m.ToonName , members.ToonName) as ToonName
from members
LEFT JOIN Associated on Associated.MainID = members.ID
LEFT JOIN members as m on m.ID = Associated.SecondaryID
Where members.ToonName = "Mitch"
While you have entry for
"Mitch" in Associated table it will return you Daryl and when you dont have associated Id it will return the name from members table.
And If you will check this with "Daryl", it will give you Two results,
Select IFNULL(m.ToonName , members.ToonName) as ToonName
from members
LEFT JOIN Associated on Associated.MainID = members.ID
LEFT JOIN members as m on m.ID = Associated.SecondaryID
Where members.ToonName = "Daryl"
In case you want all the names in a single column you can use GROUP_CONCAT as #flash suggested in another answer.
You can directly get the data from the following SQL statement.
For Individual row
SELECT `members`.ToonName FROM `associated` JOIN `members` ON associated.SecondaryID = members.ID WHERE `associated`.MainID = (SELECT ID FROM `members` WHERE ToonName = 'Daryl');
# Output: **ToonName**
Terry,Dave
Grouping Row
// You can also group all rows by comma from following statement
SELECT GROUP_CONCAT(`members`.ToonName) FROM `associated` JOIN `members` ON associated.SecondaryID = members.ID WHERE `associated`.MainID = (SELECT ID FROM `members` WHERE ToonName = 'Daryl');
# Output: **ToonName**
Terry
Dave
Plan A
Add a column to each member. It is the number (or name) if the one group he/she belongs to. Terry, Dave & Daryl would get one value; Mitch would get a different value. Index the column for efficient lookup of related names.
Plan B
Implement a graph, like you suggested. Some tips: Get rid of id; instead have PRIMARY KEY(MainID, SecondaryID). The is an issue to resolve... This design implies a "directedness" of the relationships: Terry --> Dave, but not necessarily Dave --> Terry. If you want to force it to be reflexive, the force two rows to be inserted or insert the two IDs in an canonical order, but then check both directions.
Also, you need to "walk the graph". This is best done with a Recursive CTE. For that feature, you need MySQL 8.0 or MariaDB 10.2.
Plan C
Without the directedness of B, you run into more difficult issues. One is "cluster analysis". Another is messy paths and loops in the 'graph'. Let's avoid these.
Short Answer: Yes you need a second table Associated and it will not make completed structure.
Below is the query to get the required result
SELECT ID, ToonName,
(
SELECT GROUP_CONCAT(ToonName) FROM Associated
JOIN members child ON SecondaryID = child.ID
WHERE MainID = parent.ID
)
FROM members parent
You can also use join but I think, in this case sub query will be better.
NOTE : your tables need some optimization like remove ID field from Associated table, Add index etc.

SQL - Column in field list is ambiguous

I have two tables BOOKINGS and WORKER. Basically there is table for a worker and a table to keep track of what the worker has to do in a time frame aka booking. I’m trying to check if there is an available worker for a job, so I query the booking to check if requested time has available workers between the start end date. However, I get stuck on the next part. Which is returning the list of workers that do have that time available. I read that I could join the table passed on a shared column, so I tried doing an inner join with the WORKER_NAME column, but when I try to do this I get a ambiguous error. This leads me to believe I misunderstood the concept. Does anyone understand what I;m trying to do and knows how to do it, or knows why I have the error below. Thanks guys !!!!
CREATE TABLE WORKER (
ID INT NOT NULL AUTO_INCREMENT,
WORKER_NAME varchar(80) NOT NULL,
WORKER_CODE INT,
WORKER_WAGE INT,
PRIMARY KEY (ID)
)
CREATE TABLE BOOKING (
ID INT NOT NULL AUTO_INCREMENT,
WORKER_NAME varchar(80) NOT NULL,
START DATE NOT NULL,
END DATE NOT NULL,
PRIMARY KEY (ID)
)
query
SELECT *
FROM WORKERS
INNER JOIN BOOKING
ON WORKER_NAME = WORKER_NAME
WHERE (START NOT BETWEEN '2010-10-01' AND '2010-10-10')
ORDER BY ID
#1052 - Column 'WORKER_NAME' in on clause is ambiguous
In your query, the column "worker_name" exists in two tables; in this case, you must reference the tablename as part of the column identifer.
SELECT *
FROM WORKERS
INNER JOIN BOOKING
ON workers.WORKER_NAME = booking.WORKER_NAME
WHERE (START NOT BETWEEN '2010-10-01' AND '2010-10-10')
ORDER BY ID
In your query, the column WORKER_NAME and ID columns exists in both tables, where WORKER_NAME retains the same meaning and ID is re-purposed; in this case, you must either specify you are using WORKER_NAME as the join search condition or 'project away' (rename or omit) the duplicate ID problem.
Because the ID columns are AUTO_INCREMENT, I assume (hope!) they have no business meaning. Therefore, they could both be omitted, allowing a natural join that will cause duplicate columns to be 'projected away'. This is one of those situations where one wishes SQL had a WORKER ( ALL BUT ( ID ) ) type syntax; instead, one is required to do it longhand. It might be easier in the long run to to opt for a consistent naming convention and rename the columns to WORKER_ID and BOOKING_ID respectively.
You would also need to identify a business key to order on e.g. ( START, WORKER_NAME ):
SELECT *
FROM
( SELECT WORKER_NAME, WORKER_CODE, WORKER_WAGE FROM WORKER ) AS W
NATURAL JOIN
( SELECT WORKER_NAME, START, END FROM BOOKING ) AS B
WHERE ( START NOT BETWEEN '2010-10-01' AND '2010-10-10' )
ORDER BY START, WORKER_NAME;
This is good, but its returning the start and end times as well. I'm just wanting the WOKER ROWS. I cant take the start and end out, because then sql doesn’t recognize the where clause.
Two approaches spring to mind: push the where clause to the subquery:
SELECT *
FROM
( SELECT WORKER_NAME, WORKER_CODE, WORKER_WAGE FROM WORKER ) AS W
NATURAL JOIN
( SELECT WORKER_NAME, START, END
FROM BOOKING
WHERE START NOT BETWEEN '2010-10-01' AND '2010-10-10' ) AS B
ORDER BY START, WORKER_NAME;
Alternatively, replace SELECT * with a list of columns you want to SELECT:
SELECT WORKER_NAME, WORKER_CODE, WORKER_WAGE
FROM
( SELECT WORKER_NAME, WORKER_CODE, WORKER_WAGE FROM WORKER ) AS W
NATURAL JOIN
( SELECT WORKER_NAME, START, END FROM BOOKING ) AS B
WHERE START NOT BETWEEN '2010-10-01' AND '2010-10-10'
ORDER BY START, WORKER_NAME;
This error comes after you attempt to call a field which exists in both tables, therefore you should make a reference. For instance in example below I first say cod.coordinator so that DBMS know which coordinator I want
SELECT project__number, surname, firstname,cod.coordinator FROMcoordinatorsAS co JOIN hub_applicants AS ap ON co.project__number = ap.project_id JOIN coordinator_duties AS cod ON co.coordinator = cod.email

Sequel to get select EVERY input

I have created three tables
CREATE TABLE guest(
name varchar(100),
ranking int,
PRIMARY KEY (name)
);
CREATE TABLE room(
roomname varchar(100),
wallcolor varchar(100),
rating int,
PRIMARY KEY(roomnane)
);
CREATE TABLE reservation(
name varchar(100),
roomname varchar(100),
day varchar(100),
moveinday int,
moveoutday int,
PRIMARY KEY(roomname, day, start, finish),
FOREIGN KEY(roomname) REFERENCES room(roomname),
FOREIGN KEY(name) REFERENCES guest(name)
);
I am trying to write a query to find the guests who reserved EVERY "LakeView" room
I tried the following
SELECT g.name
FROM guest g, reservation r, room rr
WHERE rr.name = g.name
AND rr.roomname = "LakeView"
GROUP BY g.name
This does not seem to list out every room. How could I gix this?
Input:
insert into guest values ('Andrew', 1);
insert into guest values ('Jack', 4);
insert into guest values ('Jake', 4);
insert into room values ('LakeView', 'white', 10);
insert into room values ('BayView', 'blue', 4);
insert into reservation values ('Andrew', 'LakeView', 'Friday', 10,15);
insert into reservation values ('Jake', 'LakeView', 'Monday', 10,16);
insert into reservation values ('Jack', ' BayView', 'Tuesday', 11,15);
Desired output is
Andrew
Jake
try this:
Solution 1:
SELECT
guest.name
FROM guest INNER JOIN
reservation ON reservation.name = guest.name
INNER JOIN room ON reservation.roomname = room.roomname
WHERE reservation.roomname = "LakeView"
GROUP BY guest.name
Here I've highlighted your mistakes:
You tried to match rr.name with g.name. That means you are
matching roomname with guest name which you didn't want (so far I
guess).
Another mistake is you are only checking "LakeView" only in room
table entries. So other guests who haven't reserved LakeView room
might appear in the final result set. You also need to filter those
records from reservation table which have LakeView roomname.
Here's your query modified based on the above observations:
Solution 2:
SELECT g.name
FROM guest g, reservation r, room rr
WHERE r.name = g.name
AND rr.roomname = "LakeView"
AND r.roomname = "LakeView"
GROUP BY g.name;
N:B: Solution 1 is encouraged to use. Try to avoid implicit joins which are stated in solution 2.
I personally think the INNER JOIN is better, because it is more
readable. It shows better the relations between the table. You got
those relations in the join, and you do the filtering in the WHERE
clause. This separation makes the query more readable.
Have a look at this post.
if you using php-mysql then it will be help
$qry=mysql_query("select name from reservation where roomname='LakeView'");
while($result=mysql_fetch_array($qry)
{
echo $result['name'];
}
I'm assuming that the 'name' column in the reservation table is the name of the guest or guests. If this is true it sounds like you only need the reservation table for the purposes of this query, but if you wanted to combine them all anyway:
select
rv.name as Guest
from room as r
left join reservation as rv on rv.roomname = r.roomname
left join guest as g on g.name = rv.name
where
rv.roomname = 'LakeView'

Write a big sql query or handle it through code?

I have 2 tables built in this way:
Trips
- id
- organization_id REQUIRED
- collaboration_organization_id OPTIONAL
...other useless fields...
Organizations
- id
- name REQUIRED
...other useless fields...
Now I have been asked to create this type of report:
I want the sum of all trips for each organization, considering that if
they have a collaboration_organization_id it should count as 0.5,
obviusly the organization in collaboration_organization_id get a +0.5
too
So whenever I have a trip that has organization_id AND collaboration_organization_id set, that trip count as 0.5 for both organizations. If instead only organization_id is set, it counts as 1.
Now my question is composed by two parts:
1.
Is a good idea to "solve" the problem all in SQL?
I already know how to solve it through code, my idea is currently "select all trips (only those 3 fields) and start counting in ruby". Please consider that I'm using ruby on rails so could still be a good reason to say "no because it will work only on mysql".
2.
If point 1 is YES, I have no idea how to count for 0.5 each trip where it's required, because count is a "throw-in-and-do-it" function
I'm not familiar with ruby on rails, but this is how you can do this with MySQL.
Sample data:
CREATE TABLE Trips(
id int not null primary key,
organization_id int not null,
collaboration_organization_id int null
);
INSERT INTO Trips (id,organization_id,collaboration_organization_id)
VALUES
(1,1,5),
(2,1,1),
(3,1,2),
(4,11,1),
(5,1,null),
(6,2,null),
(7,10,null),
(8,6,2),
(9,1,3),
(10,1,4);
MySQL Query:
SELECT organization_id,
sum(CASE WHEN collaboration_organization_id IS null THEN 1 ELSE 0.5 End) AS number
FROM Trips
GROUP BY organization_id;
Try it out via: http://www.sqlfiddle.com/#!2/1b01d/107
EDIT: adding collaboration organization
Sample data:
CREATE TABLE Trips(
id int not null primary key,
organization_id int not null,
collaboration_organization_id int null
);
INSERT INTO Trips (id,organization_id,collaboration_organization_id)
VALUES
(1,1,5),
(2,1,1),
(3,1,2),
(4,11,1),
(5,1,null),
(6,2,null),
(7,10,null),
(8,6,2),
(9,1,3),
(10,1,4);
CREATE TABLE Organizations(
id int auto_increment primary key,
name varchar(30)
);
INSERT INTO Organizations (name)
VALUES
("Org1"),
("Org2"),
("Org3"),
("Org4"),
("Org5"),
("Org6"),
("Org7"),
("Org8"),
("Org9"),
("Org10"),
("Org11"),
("Org12"),
("Org13"),
("Org14"),
("Org15"),
("Org16");
MySQL query:
SELECT O.id, O.name,
sum(CASE WHEN T.collaboration_organization_id IS null THEN 1 ELSE 0.5 End) AS number
FROM Organizations AS O LEFT JOIN Trips AS T
ON T.organization_id = O.id OR T.collaboration_organization_id = O.id
WHERE T.collaboration_organization_id = O.id OR O.id = T.organization_id
GROUP BY O.id;
http://www.sqlfiddle.com/#!2/ee557/15