I'm having a bit of a time trying to make this work...
I have one table that uses 1 or 2 digits for the week #. ( 1 to 52)
I have another table that uses 1 or 2 digits for the week# but also has the 2 digit year attached ( 117, 1017, 2217 etc.) ( 117 to 5217)
This is the only field that I can use to join these tables.
How can I join tableA with tableB on 1 = 117 or 12 = 1217...etc.?
The year portion can be omitted.
Give this a shot
SELECT a.Col1,b.Col1
FROM tablea a
JOIN tableb b ON (
(LEN(b.Col1) = 3 AND a.Col1 = LEFT(b.Col1,1))
OR
(LEN(b.Col1) = 4 AND a.Col1 = LEFT(b.Col1,2))
)
This is assuming your datatypes are (n)varchar. If they are INT or other numeric type you'll have to cast in the join clause.
Related
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 4 years ago.
I have an exercise on queries and I can't seem to find the solution to one of them. I have two tables : Aircrafts and Certified.
In the table Aircrafts I have the information AID (ID of the plane), Aname (Name of the plane) and Crusingrange(Max distance the plane have) :
AID Aname Crusingrange
1 BoeingFr 25000
2 BoeingUS 50000
3 Jet01 3000
4 Jet02 4000
In the table CERTIFIED I have this information AID (ID of the plane) and EID (ID of the pilot) :
AID EID
1 199
2 199
1 110
3 110
3 109
4 109
What I want is the ID of the pilot and the ID of the plane with the greatest cruising range he/she can fly.
EID AID
199 2
110 1
109 4
I know I have to use MAX within INNER JOIN but I really don't find the solution and I have tried to break down my code but still impossible.
Thank you
Firstly, get maximum value of Crusingrange for a EID, using Join and Group By. Use this result as a Derived table.
Now, again Join back to the main tables using maximum value of Crusingrange and EID.
For MySQL, try the following:
SELECT c1.EID,
a1.AID
FROM CERTIFIED AS c1
JOIN Aircrafts AS a1 ON a1.AID = c1.AID
JOIN
(
SELECT c.EID,
MAX(a.Crusingrange) AS Crusingrange
FROM CERTIFIED AS c
JOIN Aircrafts AS a ON a.AID = c.AID
GROUP BY c.EID
) AS dt ON dt.Crusingrange = a1.Crusingrange AND
dt.EID = c1.EID
If your DB supports row_number() window function, then you can join your tables, then sort rows per EID by Crusingrange, and choose longest ones:
SELECT EID, AID FROM (
SELECT CERTIFIED.AID, CERTIFIED.EID, row_number() over(partition by CERTIFIED.EID ORDER BY Aircrafts.Crusingrange DESC) rn
FROM CERTIFIED
JOIN Aircrafts
ON CERTIFIED.AID = Aircrafts.AID
) t
WHERE rn = 1
I have a user table with a couple as identifier : id and type, like this :
id | type | name
----------------
15 | 1 | AAA
16 | 1 | BBB
15 | 2 | CCC
I would like to get a list, matching both id and type.
I currently use a concat system, which works :
SELECT u.id,
u.type,
u.name
FROM user u
WHERE CONCAT(u.id, '-', u.type) IN ('15-1', '16-1', '17-1', '10-2', '15-2')
But, I have the feeling it could be better, what would be the proper way to do it ?
Thank you !
You may use the following approach in mysql
with dat as
(
select 17 id, 1 type, 'AAA' t
union all
select 16 id, 1 type, 'BBB' t
union all
select 17 id, 2 type, 'CCC' t
)
-- end of testing data
select *
from dat
where (id, type) in (
-- query data
(17, 1), (16, 1)
)
IN can operate on "tuples" of values, like this (a, b) IN ((c,d), (e,f), ....). Using this method is (should be) faster as you are not doing a concat operation on "a" and "b" and then comparing strings; instead you are comparing pairs of values, unprocessed and with an appropriate comparison operation (i.e. not always string compares).
Additionally, if "a" and/or "b" are string values themselves using the concat technique risks ambiguous results. ("1-2","3") and ("1","2-3") pairs concat to the same result "1-2-3"
You can separate them out. Not sure if it's more efficient but at least you would save the concat part :
SELECT u.id,
u.type,
u.name
FROM user u
WHERE (u.id = 15 AND u.type = 1)
OR (u.id = 16 AND u.type = 1)
OR (u.id = 17 AND u.type = 1)
OR (u.id = 10 AND u.type = 2)
OR (u.id = 15 AND u.type = 2)
I think it depends a lot on how you obtain the values for id and type that you use for filtering
If they are results of another computation they can be saved in a temporary table and used in a join
create TEMPORARY TABLE criteria as
select 15 as id, 1 as type
UNION
select 16 as id, 1 as type
UNION
select 17 as id, 1 as type
UNION
select 10 as id, 2 as type
UNION
select 15 as id, 2 as type
SELECT u.id,
u.type,
u.name
FROM user u
inner join criteria c on u.type = c.type and u.id = c.id
The other option is an inner query and then a join or a WITH clause (which is rather late addition to Mysql arsenal of tricks)
I have a query which combines a user's balance at a number of locations and uses a nested subquery to combine data from the customer_balance table and the merchant_groups table. There is a second piece of data required from the customer_balance table that is unique to each merchant.
I'd like to optimise my query to return a sum and a unique value i.e. the order of results is important.
For instance, there may be three merchants in a merchant_group:
id | group_id | group_member_id
1 12 36
2 12 70
3 12 106
The user may have a balance at 2 locations but not all in the customer_balance table:
id | group_member_id | user_id | balance | personal_note
1 36 420 1.00 "Likes chocolate"
2 70 420 20.00 null
Notice there isn't a 3rd row in the balance table.
What I'd like to end up with is the ability to pull the sum of the balance as well as the most appropriate personal_note.
So far I have this working in all situations with the following query:
SELECT sum(c.cash_balance) as cash_balance,n.customer_note FROM customer_balance AS c
LEFT JOIN (SELECT customer_note, user_id FROM customer_balance
WHERE user_id = 420 AND group_member_id = 36) AS n on c.user_id = n.user_id
WHERE c.user_id = 420 AND c.group_id IN (SELECT group_member_id FROM merchant_group WHERE group_id = 12)
I can change out the group_member_id appropriately and I will always get the combined balance as expected and the appropriate note. i.e. what I'm looking for is:
balance: 21.00
customer_note: "Likes Chocolate" OR null (depending on the group_member_id)
Is it possible to optimise this query without using resource heavy nested queries e.g. using a JOIN? (or some other method).
I have tried a number of options, but cannot get it working in all situations. The following is the closest I have gotten, except this doesn't return the correct note:
SELECT sum(cb.balance), cb.personal_note FROM customer_balance AS cb
LEFT JOIN merchant_group AS mg on mg.group_member_id = cb.group_member_id
WHERE cb.user_id = 420 && mg.group_id = 12
ORDER BY (mg.group_member_id = 106)
I also tried another option (but since lost the query) that works, but not when the group_member_id = 106 - because there was no record in one table (but this is a valid use case that I'd like to cater for).
Thanks!
This should be equivalent but without subselect
SELECT
sum(c.cash_balance) as cash_balance
, n.customer_note
FROM customer_balance AS c
LEFT JOIN customer_balance as n on ( c.user_id = n.user_id AND n.group_member_id = 36 AND n.user_id = 420 )
INNER JOIN merchant_group as mg on ( c.group_id = mg.group_member_id AND mg.group_id = 12)
WHERE c.user_id = 420
How to select in 1 query below. This query need re search that's find value to their own loop.
This is different from other sub query , using 1 table only
TAble T
| num| WHOSE
| 1 | A
| 1 | C
| 2 | B
| 2 | C
| 3 | D
Criteria to match records (conditions):
The value in column whose is not C
The value in column num does not match a value for another record in condition 1.
I want to find the record the value 3 in column num (which has D for column whose).
select * from T where whose <> C and ( num is not one of c's)
1 A can not because C has 1
2 B can not because C has 2
3 D is what I want, because it doesn't have C in column whose nor share a value in column num with a record that does have C in the column whose.
First select num of those records where whose is C. Then select those records where whose is not C and also where num is not one of the ones in subquery.
Select * from T where whose <> 'C' and num not in (Select Num from T where whose = 'C' )
Another way to achieve the same result is with a LEFT JOIN on the same table:
SELECT T.*
FROM T
LEFT JOIN T t2 on t2.num = T.num and t2.whose = 'C'
WHERE T.whose <> 'C' AND t2.whose IS NULL
Check it out on this SQL Fiddle, where the result is:
| num | whose |
| 3 | D |
Additionally, a similar way to write the query is to use the NOT EXISTS clause in the WHERE conditions, like this:
SELECT T.* from T
WHERE T.whose <> 'C' AND NOT EXISTS (SELECT 1 FROM T t2 WHERE
t2.num = T.num AND t2.whose = 'C')
Check it out in this SQL fiddle.
To read more about the comparison between EXISTS and LEFT JOIN see this article. In the summary at the end it has the following conclusions:
MySQL can optimize all three methods to do a sort of NESTED LOOPS ANTI JOIN.
...
However, these three methods generate three different plans which are executed by three different pieces of code. The code that executes EXISTS predicate is about 30% less efficient than those that execute index_subquery and LEFT JOIN optimized to use Not exists method.
That's why the best way to search for missing values in MySQL is using a LEFT JOIN / IS NULL or NOT IN rather than NOT EXISTS.
I basically have a table that holds counts for every date. I want to create a query that gives me the total # of counts over the entire table, as well as the total for yesterday. But when I try to join the table twice, the aggregates are off. Below is how you can replicate the results.
CREATE TABLE a (id int primary key);
CREATE TABLE b (a_id int, b_id int, date date, count int, primary key (a_id,b_id,date));
INSERT INTO a VALUES (1);
INSERT INTO b VALUES (1, 1, UTC_DATE(), 5);
INSERT INTO b VALUES (1, 2, UTC_DATE(), 10);
INSERT INTO b VALUES (1, 1, UTC_DATE()-1, 7);
INSERT INTO b VALUES (1, 2, UTC_DATE()-1, 12);
SELECT A.id,SUM(B.count) AS total_count,SUM(Y.count) AS y FROM a AS A
LEFT JOIN b AS B ON (B.a_id=A.id)
LEFT JOIN b AS Y ON (Y.a_id=A.id AND Y.date=UTC_DATE()-1)
GROUP BY A.id;
Results in:
+----+-------------+------+
| id | total_count | y |
+----+-------------+------+
| 1 | 68 | 76 |
+----+-------------+------+
The correct result should be:
+----+-------------+------+
| id | total_count | y |
+----+-------------+------+
| 1 | 34 | 22 |
+----+-------------+------+
What's going on here? Is this a bug in mysql or am I not understanding how the joins are working.
No, it's not a bug in MySQL.
Your JOIN conditions are generating "duplicate" rows. (Remove the aggregate functions and the GROUP BY, and you'll see what's happening.
That row from table "a" is matching four rows from table "b". That's all fine and good. But when you add the join to the third table ("y"), each row returned from that third "y" table (two rows) is being "matched" to every row from the "b" table... so you wind up with a total of eight rows in your result set. (That's why the "total_count" is getting doubled.)
To get the result set you specify, you don't need to join that table "b" second time. Instead, just use a conditional test to determine whether that "count" should be included in the "y" total or not.
e.g.
SELECT a.id
, SUM(b.count) AS total_count
, SUM(IF(b.date=UTC_DATE()-1 ,b.count,0)) AS y
FROM a a
LEFT
JOIN b b ON (b.a_id=a.id)
GROUP BY a.id;
Note that the MySQL IF expression can be replaced with an equivalent ANSI CASE expression for improved portability:
, SUM(CASE WHEN b.date=UTC_DATE()-1 THEN b.count ELSE 0 END) AS y
If you did want to do JOIN to that "b" table a second time, you would want the JOIN condition to be such that a row from "y" would match, at most, ONE row from "b", so as not to introduce any duplicates. So you'd basically need the join condition to include all of the columns in the primary key.
(Note that the predicates in the join condition for table "y" guarantee that each from from "y" will match no more than ONE row from "b"):
SELECT a.id
, SUM(b.count) AS total_count
, SUM(y.count) AS y
FROM a a
LEFT
JOIN b b
ON b.a_id=a.id
LEFT
JOIN b y
ON y.a_id = b.a_id
AND y.b_id = b.b_id
AND y.date = b.date
AND y.date = UTC_DATE()-1
GROUP BY a.id;
(To get the first statement to return an identical resultset, with a potential NULL in place of a zero, you'd need to replace the '0' constant in the IF expression with 'NULL'.
, SUM(IF(b.date=UTC_DATE()-1 ,b.count,NULL)) AS y
SELECT A.id,b_count AS total_count,y_count as y
FROM a AS A
LEFT JOIN (select a_id,SUM(B.Count) b_count from b
group by B.A_id) AS B1 ON (B1.a_id=A.id)
LEFT JOIN (select a_id,SUM(Count) y_count from b
where date=UTC_DATE()-1
group by B.A_id) AS Y ON (Y.a_id=A.id)
SQLFiddle Demo