Order by joined table through table - mysql

my mySQL (pun intended) is a bit rusty. I am trying to join a table through another table.
carparks has many clients
clients has many cars
This is the query
select `carparks`.* from `carparks`
left join `clients` on `carparks`.`carpark_id` = `clients`.`carpark_id`
left join `cars` on `clients`.`client_id` = `cars`.`client_id`
where `carparks`.`carpark_id` in (1, 3, 8, 33, 34, 38, 39)
order by `cars`.`created_at` desc
As you can see I am trying to order by the created_at column of cars, the above query though returns duplicated carparks for each of the cars within the carpark.
What I am looking at is to return only those carparks with the ids in the WHERE IN clause, simply ordered by the created_at column of the cars table.
Thanks

You can use aggregation in your order by clause on max created date from cars table
SELECT cp.*
FROM `carparks` cp
LEFT JOIN `clients` cl ON cp.`carpark_id` = cl.`carpark_id`
LEFT JOIN `cars` c ON cl.`client_id` = c.`client_id`
WHERE cp.`carpark_id` IN (1, 3, 8, 33, 34, 38, 39)
GROUP BY cp.`carpark_id`
ORDER BY MAX(c.`created_at`) DESC

Reduce the wanted dates to one per carpark before joining back to carparks. Note if a carpark does have no cars than a left join is logical, however I expect every carpark (that is open for business) will have cars, so that left join might not be needed.
SELECT `carparks`.*
FROM `carparks`
LEFT JOIN (
SELECT
`carparks`.`carpark_id`
, max(`cars`.`created_at`) max_car_created
FROM `clients`
INNER JOIN `cars` ON `clients`.`client_id` = `cars`.`client_id`
GROUP BY
`carparks`.`carpark_id`
) d ON `carparks`.`carpark_id` = d.`carpark_id`
WHERE `carparks`.`carpark_id` IN (1, 3, 8, 33, 34, 38, 39)
ORDER BY max_car_created DESC

Reduce the number of carparks and clients before doing the joins, this will reduce the execution time of the query.
SELECT A.* FROM (SELECT * FROM `carparks` WHERE `carpark_id` in
(1, 3, 8, 33, 34, 38, 39)) A LEFT JOIN
(SELECT `carpark_id`, `client_id` FROM `clients` WHERE `carpark_id`
in (1, 3, 8, 33, 34, 38, 39)) B ON A.`carpark_id`=B.`carpark_id` LEFT JOIN
`cars` C ON B.`client_id` = C.`client_id`
GROUP BY A.`carpark_id`
ORDER BY MAX(C.`created_at`) DESC

Related

In need of a query logic, how to group by id from the users table?

I have two tables:
INSERT INTO `companies` (`name`) VALUES
('Walmart'),
('Disney'),
('Amazon'),
('Unicom'),
('Microsoft'),
('Intel')
INSERT INTO `users` (`id`, `company`) VALUES
(1, 'Disney'),
(2, 'Amazon'),
(3, 'Intel'),
(3, 'Walmart'),
(4, 'Microsoft'),
(4, 'Unicom'),
(5, 'Microsoft')
The result should be following:
1. 'Walmart', 'Amazon', 'Unicom', 'Microsoft', 'Intel'
2. 'Walmart', 'Disney', 'Unicom', 'Microsoft', 'Intel'
3. 'Disney', 'Amazon', 'Unicom', 'Microsoft'
4. 'Walmart', 'Disney', 'Amazon', 'Intel'
5. 'Walmart', 'Disney', 'Amazon', 'Unicom', 'Intel'
I have tried with:
"SELECT a.name, b.id, b.company FROM users RIGHT JOIN companies ON b.company <> a.name"
This gives the correct logic by omitting the company name that's already on the list but the problem is that it processes the same id twice and omits a different company name. How would one approach this query?
The basic idea in the query below is to left join a calendar table containing every possible user/company combination to the users table. Those combinations which do match are removed, and the remaining companies are then rolled up into a CSV string for each user using GROUP_CONCAT.
SELECT t1.id, GROUP_CONCAT(t1.name)
FROM
(
SELECT DISTINCT u.id, c.name
FROM users u
CROSS JOIN companies c
) t1
LEFT JOIN users t2
ON t1.name = t2.company AND t1.id = t2.id
WHERE t2.company IS NULL
GROUP BY
t1.id;
Demo
Try this
"SELECT a.name, b.id, b.company FROM users RIGHT JOIN companies ON b.company <> a.name group by b.company"

MySQL outer join substitute

I am working on a game inventory management system and would like to display the owner's restock wish list and a count of customer buy reservations for each game in a single table. I wrote a query that I thought was working, but then I noticed that it actually omits any games for which there are reservations but that aren't initially in the restock wish list. The query is below:
SELECT rwl.*, g.gameName, coalesce(payYes, 0) payYes, coalesce(payNo, 0) payNo FROM RestockWishList AS rwl, Games AS g
LEFT JOIN
(SELECT gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes, COUNT(if(prepaid='No', 1, NULL)) payNo FROM ReservationsBuy GROUP BY gameID) AS res
ON res.gameID = g.gameID
WHERE rwl.gameID = g.gameID;
Query results:
gameID,
quantity,
gameName,
payYes,
payNo
1,
4,
A Castle for all Seasons,
0,
0
2,
2,
A Few Acres of Snow,
0,
0
18,
4,
Alhambra,
0,
0
54,
2,
Big Boggle,
2,
0
Apparently the solution to this problem is to use FULL OUTER JOIN instead of LEFT JOIN, but MySQL doesn't support that function. I have spent hours trying to translate it to a UNION structure, but can't quite get it to work correctly. This is as close as I've got:
SELECT rwl.*, res.gameID, res.payYes, res.payNo FROM RestockWishList rwl
LEFT JOIN
(SELECT gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes, COUNT(if(prepaid='No', 1, NULL)) payNo FROM ReservationsBuy GROUP BY gameID) AS res
ON res.gameID = rwl.gameID
UNION
SELECT rwl.*, res.gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes, COUNT(if(prepaid='No', 1, NULL)) payNo FROM ReservationsBuy res
LEFT JOIN RestockWishList rwl ON rwl.gameID = res.gameID;
Query results:
gameID, quantity, gameID, payYes, payNo
1, 4, NULL, NULL, NULL
2, 2, NULL, NULL, NULL
18, 4, NULL, NULL, NULL
54, 2, 54, 2, 0
NULL, NULL, 30, 3, 1
(Sorry, I don't know how to nicely format query table results in StackOverflow.)
I want the query to display as I originally wrote it, just with the missing values from ReservationsBuy. Specific help please?
Tables:
CREATE TABLE IF NOT EXISTS RestockWishList (
gameID INT(6),
quantity INT(3) NOT NULL,
PRIMARY KEY (gameID),
FOREIGN KEY (gameID) REFERENCES Games(gameID) ON UPDATE CASCADE ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS ReservationsBuy (
gameID INT(6),
customerEmail VARCHAR(25) NOT NULL,
customerName VARCHAR(25) NOT NULL,
dateReserved DATETIME NOT NULL, #date customer files game reservation
datePurchased DATETIME, #date Board and Brew restocks game
dateClaimed DATETIME, #date customer physically claims game
prepaid ENUM('Yes', 'No') NOT NULL,
PRIMARY KEY (gameID, customerEmail),
FOREIGN KEY (gameID) REFERENCES Games (gameID) ON UPDATE CASCADE ON DELETE CASCADE);
Sample data:
RestockWishList:
gameID,
quantity
1,
4
2,
2
18,
4
54,
2
ReservationsBuy:
gameID,
customerEmail,
customerName,
dateReserved,
datePurchased,
dateClaimed,
prepaid
30,
wonder#woman.com,
Diana,
2015-04-24 14:46:05,
NULL,
NULL,
Yes
54,
boggie#marsh.com,
boggie,
2015-04-24 14:43:32,
NULL,
NULL,
Yes
54,
manny#second.com,
manny,
2015-04-27 19:48:22,
NULL,
NULL,
Yes
43,
old#mom.com,
grandma,
2015-04-23 22:32:03,
NULL,
NULL,
No
Expected output:
gameID, quantity, gameName, payYes, payNo
1, 4, A Castle for all Seasons, 0, 0
2, 2, A Few Acres of Snow, 0, 0
18, 4, Alhambra, 0, 0
30, 0, Arkham Horror, 1, 0
43, 0, Bananagrams, 0, 1
54, 2, Big Boggle, 2, 0
(Games table not particularly important for this query. Only relevance is that both ReservationsBuy and RestockWishList are connected to Games by gameID)
I think maybe you want a query like this - not a full outer join:
select q.id, q.name, q.reservations, ifnull(q2.wishcount, 0) wishcount, q.payYes, q.payNo
from (
select g.*, count(rb.gameid) reservations, count(case when prepaid = 'Yes' then 1 end) payYes, count(case when prepaid = 'No' then 1 end) payNo
from games g
left join reservationsbuy rb
on g.id = rb.gameid
group by g.id
) q
left join (
select g.id, sum(quantity) wishcount
from games g
left join restockwishlist rwl
on g.id = rwl.gameid
group by g.id
) q2
on q.id = q2.id;
There's a demo here, but the gist of it is, for each game in the game table, it will give you the total number of reservations, the quantity from the wish list, and we use a conditional count to provide the count ofprepaid = yes, or prepaid = no. Effectively it is just joining together two small queries on the shared gameid.
If you want this to include filtering by date etc, you might need to be a bit more explicit about how you want the results to work, or display
You're on the right track with using a FULL OUTER JOIN, you just have the implementation incorrect.
A FULL OUTER JOIN in MySQL can be thought of as the UNION of a LEFT JOIN and a RIGHT JOIN. In your query, you're trying to approximate this by treating the RIGHT JOIN part of the logic as an inverse LEFT JOIN of the two tables, but your first part doesn't work because it's not a subselect with the same GROUP BY sequence as your first LEFT JOIN.
Simplest thing to do is simply take your first LEFT JOIN query stanza, copy it to the second stanza, and replace LEFT JOIN with RIGHT JOIN, then link the results to your games table, like so:
SELECT g.gameID, IFNULL(q.quantity, 0) AS quantity, g.gameName,
IFNULL(q.payYes, 0) AS payYes, IFNULL(q.payNo, 0) AS payNo
FROM games g
INNER JOIN (
SELECT IFNULL(rwl.gameID, res.gameID) AS gameID, rwl.quantity,
res.payYes, res.payNo
FROM RestockWishList rwl
LEFT JOIN (
SELECT gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes,
COUNT(if(prepaid='No', 1, NULL)) payNo
FROM ReservationsBuy
GROUP BY gameID
) AS res ON res.gameID = rwl.gameID
UNION
SELECT IFNULL(rwl.gameID, res.gameID) AS gameID, rwl.quantity,
res.payYes, res.payNo
FROM RestockWishList rwl
RIGHT JOIN (
SELECT gameID, COUNT(IF(prepaid='Yes', 1, NULL)) payYes,
COUNT(IF(prepaid='No', 1, NULL)) payNo
FROM ReservationsBuy
GROUP BY gameId
) AS res ON res.gameID = rwl.gameID
) AS q ON g.gameID = q.gameID
SQL Fiddle Results
ok. So we now that always exists a record in game table, right?
Then start your FROM with this table, then you just have to do a LEFT JOIN for each table as follow:
SELECT
rwl.*
, g.gameName
, coalesce(payYes, 0) payYes
, coalesce(payNo, 0) payNo
FROM
Games AS g LEFT JOIN
RestockWishList AS rwl ON rwl.gameID = g.gameID LEFT JOIN
(
SELECT
gameID
, COUNT(if(prepaid='Yes', 1, NULL)) payYes
, COUNT(if(prepaid='No', 1, NULL)) payNo
FROM
ReservationsBuy
GROUP BY gameID) AS res ON res.gameID = g.gameID
;
As you can say, the only changes were: Start the FROM with Games table and use LEFT JOIN, also remove the condition from WHERE and put it in the LEFT JOIN

Select query with left outer join and sum with group by

I have 3 tables for example
Parent Table :TEST_SUMMARY
Child Tables : TEST_DETAIL, TEST_DETAIL2
I have data show in image, and want output result shown in image,
I tried below 2 query, but not giving expected output
SELECT s.NAME, sum(s.AMT), sum(d.d_amt), sum(d2.d2_amt)
FROM TEST_SUMMARY s LEFT OUTER JOIN TEST_DETAIL d
ON s.ID = d.SUMMARY_ID
LEFT OUTER JOIN TEST_DETAIL2 d2
ON s.ID =d2.SUMMARY_ID
GROUP BY s.NAME
ORDER BY s.NAME;
select rs1.*,rs2.total1,rs3.total2
FROM
(select id, name,amt from TEST_SUMMARY a) RS1,
(select SUMMARY_ID, sum(d_amt) over(partition by summary_id ) total1 from TEST_DETAIL a) RS2,
(select SUMMARY_ID, sum(d2_amt) over(partition by summary_id ) total2 from TEST_DETAIL2 a) RS3
where rs1.id(+)= RS2.SUMMARY_ID
and rs1.id(+)= RS3.SUMMARY_ID;
Create table and insert data test Queries
CREATE TABLE TEST_SUMMARY(ID NUMBER, NAME VARCHAR2(20 BYTE),AMT NUMBER(10,2));
CREATE TABLE TEST_DETAIL (ID NUMBER, SUMMARY_ID NUMBER, NAME VARCHAR(20), D_AMT NUMBER(10,2));
CREATE TABLE TEST_DETAIL2 (ID NUMBER, SUMMARY_ID NUMBER, NAME VARCHAR(20), D2_AMT NUMBER(10,2));
INSERT INTO TEST_SUMMARY VALUES (1, 'NAME1', 100);
INSERT INTO TEST_SUMMARY VALUES (4, 'NAME1', 150);
INSERT INTO TEST_SUMMARY VALUES (6, 'NAME1', 50);
INSERT INTO TEST_SUMMARY VALUES (2, 'NAME2', 200);
INSERT INTO TEST_SUMMARY VALUES (3, 'NAME3', 300);
INSERT INTO TEST_DETAIL VALUES (1, 1, 'NAME11', 11);
INSERT INTO TEST_DETAIL VALUES (2, 1, 'NAME12', 12);
INSERT INTO TEST_DETAIL2 VALUES (1, 1, 'NAME_2_11', 1);
INSERT INTO TEST_DETAIL2 VALUES (2, 1, 'NAME_2_12', 1);
One way to solve it for both MySQL and Oracle is to use subqueries to help solve the duplication for you by aggregating the sums from the details tables by name, so you can summarise with a normal join;
SELECT ts.name, SUM(ts.amt) amt1, MAX(td1.amt) amt2, MAX(td2.amt) amt3
FROM TEST_SUMMARY ts
LEFT JOIN (
SELECT ts.name, SUM(td.d_amt) amt
FROM TEST_DETAIL td JOIN TEST_SUMMARY ts ON td.summary_id = ts.id
GROUP BY ts.name) td1 ON ts.name = td1.name
LEFT JOIN (
SELECT ts.name, SUM(td.d2_amt) amt
FROM TEST_DETAIL2 td JOIN TEST_SUMMARY ts ON td.summary_id = ts.id
GROUP BY ts.name) td2 ON ts.name = td2.name
GROUP BY ts.name
ORDER BY ts.name
A MySQL SQLfiddle and an Oracle SQLfiddle to test with.
You could try this:
SELECT
TEST_SUMMARY.NAME,
TEST_SUMMARY.AMT AS AMT1,
(
SELECT
SUM(TEST_DETAIL.D_AMT)
FROM
TEST_DETAIL
WHERE
TEST_DETAIL.SUMMARY_ID=TEST_SUMMARY.ID
) AS AMT2,
(
SELECT
SUM(TEST_DETAIL2.D2_AMT)
FROM
TEST_DETAIL2
WHERE
TEST_DETAIL2.SUMMARY_ID=TEST_SUMMARY.ID
) AS AMT3
FROM
TEST_SUMMARY
Update
You could basically do this if you have many name that are the same. But the question comes what you should do with the other fields (AMT1,AMT2)? Should you sum them for the same name or maybe a max is enough. Depends on what your requirement are :
SELECT
TEST_SUMMARY.NAME,
SUM(TEST_SUMMARY.AMT) AS AMT,
SUM(tblAMT2.AMT2) AS AMT2,
SUM(tblAMT3.AMT3) AS AMT3
FROM
TEST_SUMMARY
LEFT JOIN
(
SELECT
SUM(TEST_DETAIL.D_AMT) AS AMT2,
TEST_DETAIL.SUMMARY_ID
FROM
TEST_DETAIL
GROUP BY
TEST_DETAIL.SUMMARY_ID
) AS tblAMT2
ON TEST_SUMMARY.ID=tblAMT2.SUMMARY_ID
LEFT JOIN
(
SELECT
SUM(TEST_DETAIL2.D2_AMT) AS AMT3,
TEST_DETAIL2.SUMMARY_ID
FROM
TEST_DETAIL2
GROUP BY
TEST_DETAIL2.SUMMARY_ID
) AS tblAMT3
ON TEST_SUMMARY.ID=tblAMT3.SUMMARY_ID
GROUP BY
TEST_SUMMARY.NAME
Try this:
SELECT TS.NAME, TS.AMT AS AMT1, SUM(TD.D_AMT) AS AMT2, SUM(TD2.D2_AMT) AS AMT3
FROM TEST_SUMMARY TS LEFT OUTER JOIN TEST_DETAIL TD ON TS.ID = TD.SUMMARY_ID
LEFT OUTER JOIN TEST_DETAIL2 TD2 ON TS.ID = TD2.SUMMARY_ID
GROUP BY TS.NAME, TS.AMT
ORDER BY TS.NAME, TS.AMT

Mysql multiple inner join don't work

I have two tables A & B
A :
id
data
B :
key
value
A_id
I have a problem with my sql query (its hard to explain it, so i create an sqlfiddle)
SELECT A.id
FROM A
INNER JOIN B b1 ON b1.key = '20' AND b1.A_id = A.id
INNER JOIN B b2 ON b2.key = '18' AND b2.A_id = A.id
WHERE
b1.value = '1900' AND
b2.value >= '1900'
in this example, I'm supposed to get (A.id = 12 & A.id = 13) but nothing
CREATE TABLE A
(`id` int, `data` int)
;
INSERT INTO A
(`id`, `data`)
VALUES
(11, 11),
(12, 11),
(13, 12)
;
CREATE TABLE B
(`key` int, `value` int, `A_id` int)
;
INSERT INTO B
(`key`, `value`, `A_id`)
VALUES
(20, 1900, 12),
(2, 19, 11),
(11, 19, 11),
(9, 19, 11),
(18, 1950, 13),
(19, 1950, 12)
;
Any idea ?
thanks
First, if you code for more than 10 minutes, you will learn to despise the phrase "don't work"... "don't work" is the phrase that doesn't work.
/rant
You are trying to join tables in an effort to filter. Instead, filter accordingly. Check this out:
SELECT A.id
FROM A
INNER JOIN B b1 ON b1.A_id = A.id
WHERE
(b1.key = '20' AND b1.value = '1900')
OR
(b1.key = '18' AND b1.value >= '1900')
That asks for what you want and joins only when necessary.
INNER JOIN means that it will only return a result if a result exists in both tables. Since you inner join the same table twice each time with unique ids you will never get a result with those three results (table A inner join B inner join b).
I'd suggest something different:
SELECT A.id
FROM A
INNER JOIN B on A.id = B.A_id
WHERE
(B.id = '20' AND B.value = '1980') OR (B.id = '18' AND B.value >= '1990')
;
Your query is using the same A.id for both joins - so it means they must be the same on the rows returned. For b2.key = 18, b2.A_id is 13, while b1.key = 20 forces a b1.A_id of 12. So you want to get a return where b1.A_id = b2.A_id, where one is 12 and the other is 13. If you change the query to
SELECT A.id
FROM A
INNER JOIN B b1 ON b1.key = '20' AND b1.A_id = A.id
INNER JOIN B b2 ON b2.key = '19' AND b2.A_id = A.id
WHERE
b1.value = '1900' AND
b2.value >= '1900'
you'll get a return of 12 for A.id.

Optimize multiple row count

There are two tables: question and answer. In answer I hold user_id and question_id. I want to count how many times each choice is selected.
Below is a working query, but instead of joining the same table 4 times, what is a faster way i.e. joining the answer table only once.
SELECT question.question_id,
question.correct_choice,
COUNT(DISTINCT a.user_id) as num_of_a,
COUNT(DISTINCT b.user_id) as num_of_b,
COUNT(DISTINCT c.user_id) as num_of_c,
COUNT(DISTINCT d.user_id) as num_of_d
FROM answer a,
answer b,
answer c,
answer d,
question
WHERE a.question_id = question.question_id
AND b.question_id = question.question_id
AND c.question_id = question.question_id
AND d.question_id = question.question_id
AND a.choice = 'A'
AND b.choice = 'B'
AND c.choice = 'C'
AND d.choice = 'D'
GROUP BY question.question_id
ORDER BY question.question_id asc;
returns
273, D, 5, 2, 8, 39
274, C, 2, 14, 50, 2
277, C, 3, 5, 41, 17
278, C, 16, 9, 34, 9
279, C, 8, 30, 24, 12
280, B, 17, 21, 20, 3
284, C, 2, 3, 19, 1
286, A, 16, 3, 2, 2
287, D, 1, 2, 1, 18
289, B, 3, 18, 2, 2
290, D, 6, 9, 8, 6
This solution only does a single join... additionally, I converted your implicit joins to explicit, and rounded out your GROUP BY:
SELECT
q.question_id,
q.correct_choice,
COUNT(DISTINCT CASE WHEN a.choice = 'A' THEN a.user_id END) as num_of_a,
COUNT(DISTINCT CASE WHEN a.choice = 'B' THEN a.user_id END) as num_of_b,
COUNT(DISTINCT CASE WHEN a.choice = 'C' THEN a.user_id END) as num_of_c,
COUNT(DISTINCT CASE WHEN a.choice = 'D' THEN a.user_id END) as num_of_d
FROM
answer a
JOIN question q ON a.question_id = q.question_id
GROUP BY q.question_id, q.correct_choice
ORDER BY q.question_id asc;
This works because when the CASE statement doesn't evaluate to true, it returns NULL, which won't be included in the COUNT DISTINCT of user Ids.
You might consider using a SELECT... UNION SELECT style if you are concerned about performance.
Although I would agree with #benjam that you should EXPLAIN the results to see what optimizer is saying, since you do not have a dependent queries.
Make sure that you have indexes on question.question_id, and on answer.question_id, answer.choice, and answer.user_id and your query should be just as fast as any other that does not join answer for each choice. Then use the following query:
SELECT `question`.`question_id`,
`question`.`correct_choice`,
COUNT(DISTINCT `a`.`user_id`) as `num_of_a`,
COUNT(DISTINCT `b`.`user_id`) as `num_of_b`,
COUNT(DISTINCT `c`.`user_id`) as `num_of_c`,
COUNT(DISTINCT `d`.`user_id`) as `num_of_d`
FROM `question`
LEFT JOIN `answer` AS `a`
USING(`a`.`question_id` = `question`.`question_id`
AND `a`.`choice` = 'A'),
LEFT JOIN `answer` AS `b`
USING(`b`.`question_id` = `question`.`question_id`
AND `b`.`choice` = 'B'),
LEFT JOIN `answer` AS `c`
USING(`c`.`question_id` = `question`.`question_id`
AND `c`.`choice` = 'C'),
LEFT JOIN `answer` AS `d`
USING(`d`.`question_id` = `question`.`question_id`
AND `d`.`choice` = 'D')
GROUP BY `question`.`question_id` ;
The ORDER BY clause is not needed and implied from the GROUP BY clause.