Multiple Join on same table with different columns - mysql

I have problems making a SQL request.
Here is my tables:
CREATE TABLE dates(
id INT PRIMARY KEY,
obj_id INT,
dispo_date text
);
CREATE TABLE option(
id INT PRIMARY KEY,
obj_id INT,
random_option INT
);
CREATE TABLE obj(
id INT PRIMARY KEY,
);
and a random date that the user gives me and some options.
I'd like to select everything on both tables which correspond to an obj having his date equal to the user's date.
let's say that DATE = "22/01/2013" and OPTIONS = 3.
SELECT * FROM obj
INNER JOIN dates
ON dates.obj_id=obj.id
INNER JOIN option
ON option.obj_id=obj.id
WHERE dates.dispo_date="22/01/2013"
AND option.random_option=3;
That just gives me everything from my obj table with, for each one, the same dates and options without filtering anything.
Can someone give me some pointers about what I'm doing wrong ?
SOLUTION:
Since everybody seemed to get what I was looking for I restarted my SQL server and since, everything works ...
Thanks for your help and sorry for the time-loss :-(

As far as I can see, there is nothing wrong with the query.
When I try it, it returns only the obj rows where there is a corresponding date and a corresponding option.
insert into dates values
(1, 1, '22/01/2013'),
(2, 1, '23/01/2013'),
(3, 2, '22/01/2013'),
(4, 2, '23/01/2013'),
(5, 3, '23/01/2013'),
(6, 3, '24/01/2013');
insert into `option` values
(1, 1, 4),
(2, 1, 5),
(3, 2, 3),
(4, 2, 4),
(5, 3, 3),
(6, 3, 4);
insert into obj values
(1),
(2),
(3)
With this data it should filter out obj 1 because there is no option 3 for it, and filter out obj 3 because there is no date 22 for it.
Result:
ID OBJ_ID DISPO_DATE RANDOM_OPTION
-------------------------------------
2 2 22/01/2013 3
Demo: http://sqlfiddle.com/#!2/a398f/1

Change your line
WHERE dates.dispo_date="22/01/2013"
for
WHERE DATE(dates.dispo_date)="22/01/2013"
Handling dates in text fields is a little tricky (also bad practice). Make sure both dates are in the same format.

First, I'm a little confused on which ID's map to which tables. I might respectfully suggest that the id field in DATES be renamed to date_id, the id in OPTION be renamed to option_id, and the id in obj to obj_id. Makes those relationships MUCH clearer for folks looking in through the keyhole. I'm going in a bit of a circle making sure I understand your relationships properly. On that basis, I may be understanding your problem incorrectly.
I think you have obj.id->dates.obj_id, and option.obj_id->dates.obj_id, so on that basis, I think your query has to be a bit more complicated:
This gives you object dates:
Select *
from obj obj
join dates d
on obj.id=d.obj_id
This gives you user dates:
select *
from option o
join dates d
on o.obj_id=d.obj_id
To get the result of objects and users having the same dates, you'd need to hook these two together:
select *
from (Select *
from obj obj
join dates d
on obj.id=d.obj_id) a
join (select *
from option o
join dates d
on o.obj_id=d.obj_id) b
on a.dispo_date=b.dispo_date
where b.random=3
I hope this is useful. Good luck.

Related

Is it possible to pass several rows of data into a MySQL subquery instead of using a temporary table?

I'd like to know if it's possible to pass rows of data directly into a select subquery, rather than setting up a temporary table and joining on that.
My actual use case is trying to prevent thousands of individual queries, and for architectural reasons adding a temporary table would be a pain (but not impossible, so it's where I may have to go.)
An simplified example of my issue is :
I have a table giving the number plate of the cars in a car park, with each row containing section (a letter), space (a number), and reg_plate (a tinytext).
My boss gives me a list of 3 places and wants the reg number of the car in each (or null if empty).
Now I can do this by creating a temporary table containing the section/space sequence I'm interested in, and then join my carpark table against that to give me each of the rows.
I'm wondering is there a syntax where I could do this in a select, perhaps with a subselect something like this - obviously invalid, but hopefully it shows what I'm getting at:
SELECT targets.section, targets.space, cp.reg_plate
FROM carpark cp
LEFT JOIN (
SELECT field1 AS section, field2 AS space
FROM (
('a', 7), ('c', 14), ('c', 23)
)
) targets ON (cp.section = targets.section AND cp.space = targets.space)
Any ideas gratefully received!
You can use UNION:
SELECT targets.section, targets.space, cp.reg_plate
FROM carpark cp
LEFT JOIN (
SELECT 'a' as section, 7 AS space
UNION ALL
SELECT 'c', 14
UNION ALL
SELECT 'c', 23
) targets ON cp.section = targets.section AND cp.space = targets.space
A followup;
I realised I was hoping to find something analagous to the VALUES used in an insert statement.
It turns out this is available in MySQL 8+, and VALUES is available as a table constructor:
https://dev.mysql.com/doc/refman/8.0/en/values.html
SELECT targets.section, targets.space, cp.reg_plate
FROM carpark cp
LEFT JOIN (
VALUES
ROW ('a', 7), ROW('c', 14), ROW('c', 23)
)
) targets ON (cp.section = targets.column_0 AND cp.space = targets.column_1)
Not available in earlier MySQL (so the UNION method is OK) but great in 8+.

Subquery returns different result than when called on its own

Using GROUP BY in a MySQL subquery returns results not appearing when calling the GROUP BY query on its own.
This example is about as stripped down as I can make it.
I feel like I'm missing something elementary about using GROUP BY in a subquery, but haven't found an existing question or tutorial that addresses this particular issue.
First, build the table:
CREATE TABLE Person (
Id INT
Email VARCHAR(20)
);
INSERT INTO Person VALUES (1, "a#b.com");
INSERT INTO Person VALUES (2, "c#d.com");
INSERT INTO Person VALUES (3, "a#b.com");
INSERT INTO Person VALUES (5, "c#d.com");
INSERT INTO Person VALUES (7, "e#f.com");
INSERT INTO Person VALUES (11, "e#f.com");
INSERT INTO Person VALUES (13, "g#h.com");
Now compare these two pairs of queries:
Test 1: subquery using explicit IDs
SELECT Id FROM Person WHERE Id IN (1, 2, 7, 13)
returns Id (1, 2, 7, 13)
SELECT Id FROM Person WHERE Id IN (SELECT Id FROM Person WHERE Id IN (1, 2, 7, 13))
returns Id (1, 2, 7, 13)
Test 2: subquery using GROUP BY for Email uniqueness (gives first in each group)
SELECT Id FROM Person GROUP BY Email
returns Id (1, 2, 7, 13)
SELECT Id FROM Person WHERE Id IN (SELECT Id FROM Person GROUP BY Email)
returns Id (1, 2, 3, 5, 7, 11, 13)... not (1, 2, 7, 13), as expected.
I expected the output of the compound query on both of these tests to be
Id (1, 2, 7, 13)
since the subquery in each outputs
Id (1, 2, 7, 13)
as input to the top-level query.
This leads me to believe that the displayed results are not actually the full results (at least, when it comes to a GROUP BY). Any elucidation on this confusing situation would be greatly appreciated.
SELECT Id FROM Person GROUP BY Email
This query is invalid according to the SQL standard. You group by Email. So you get one result row per Email. Then you want to show the ID for the Email. But there is not the ID per Email, there can be many. For Email = 'a#b.com' for instance there are the IDs 1 and 3. The DBMS should raise an error. But MySQL silently substitutes this with
SELECT ANY_VALUE(Id) FROM Person GROUP BY Email
i.e. returns an arbitrarily chosen value. It's left to chance whether the query returns ID 1 or 3 for Email = 'a#b.com'.
This explains why you get different results. One time the DBMS chooses this value one time the other.
Group BY should be use with aggregation function.
(If you need distinct result use the DISTINCT clause and not use group by improperly)
In mysql versone < 5.7 the The use of group by without aggregation function produce unpredictable result ..
in mysql >= 5.7 is not allowed by default and this kind of use produce error.
If you want control the result based on group by you should use a proper aggregation function eg: min() or max()
SELECT Id
FROM Person
WHERE Id IN ( SELECT min(Id )
FROM Person
GROUP BY Email
)

How to check a column for duplicate values and return rows containing them

I am writing a script operating on SQL. The query is to check picked column (in this case kolumna1, kolumna2 or kolumna3) for duplicate values. Then, after finding these values I want to return every row containing such value. For example, looking at the table I have, if I look through kolumna1 column one of the duplicate values would be in row (id) 2 and 8. So in this case I would want to return whole second and eighth rows.
Of course main goal is to return every row with a duplicate value, this was just a simpler example.
The table:
INSERT INTO `tabela_testowa` (`id`, `kolumna1`, `kolumna2`, `kolumna3`, `kolumna4`) VALUES
(1, 'wartosc1', 'wartosc2', 'wartosc3', 1),
(2, 'warosc21', 'wartosc22', 'wartosc23', 5),
(3, 'wartosc31', 'wartosc22', 'wartosc32', 6),
(4, 'wartosc54', 'wartosc43', 'wartosc45', 4),
(5, 'wartosc43', 'wartosc23', 'wartosc34', 4),
(6, 'wartosc43', 'wartosc54', 'wartosc43', 2),
(7, 'wartosc54', 'wartosc52', 'wartosc53', 8),
(8, 'wartosc21', 'wartosc22', 'wartosc43', 4),
(9, 'wartosc43', 'wartosc33', 'wartosc45', 9),
(10, 'wartosc87', 'wartosc62', 'wartosc11', 3);
so far I've managed to write a query that almost works properly. By almost I mean that it returns the duplicates, but only one time for each one.
Query:
SELECT id, kolumna1
FROM tabela_testowa
GROUP BY kolumna1
HAVING ( COUNT(kolumna1) > 1 );
Edit: I am using mysql. Also, to clarify I want to search through only one column that I choose in search for the duplicate values, then display whole rows containing them (of course I mean both rows, like the 2nd and 8th ones).
If am not wrong you are looking for this
Works in both Mysql And Sql Server
Correlated Sub-Query method
select *
from Yourtable A
Where Exists (select 1 from Yourtable B where a.kolumna1 =b.kolumna1
HAVING COUNT(1) > 1 )
Another approach, filtering kolumna1 which is having more than one record and joining with your table
Select A.* From Yourtable A
(
select kolumna1
From Yourtable
Group by kolumna1
Having Count(1) > 1
) B
ON A.kolumna1= B.kolumna1
Update : Scope of the alias name ends when the select query ends you cannot use it like that. Try this way
SELECT *
FROM tabela_testowa tab1
WHERE EXISTS (SELECT 1
FROM tabela_testowa tab2
WHERE tab1.kolumna1 = tab2.kolumna1
HAVING Count(1) > 1)
If you are using SQL SERVER
Select * From
(
select *,Count(1)Over(Partition by kolumna1) as cnt
from Yourtable
)A
Where cnt > 1

MySQL order by IN order

i have simple query:
SELECT data FROM table WHERE id IN (5, 2, 8, 1, 10)
Question is, how can i select my data and order it like in my IN.
Order must be 5, 2, 8, 1, 10.
Problem is that i have no key for order. IN data is from other query (1), but i need to safe order.
Any solutions?
(1)
SELECT login
FROM posts
LEFT JOIN users ON posts.post_id=users.id
WHERE posts.post_n IN (
2280219,2372244, 2345146, 2374106, 2375952, 2375320, 2371611, 2360673, 2339976, 2331440, 2279494, 2329266, 2271919, 1672114, 2301856
)
Thanx for helping, solutions works but very slow, maybe find something better later, thanx anyway
The only way I can think to order by an arbitrary list would be to ORDER BY comparisons to each item in that list. It's ugly, but it will work. You may be better off sorting in whatever code you are doing the selection.
SELECT data FROM t1 WHERE id IN (5, 2, 8, 1, 10)
ORDER BY id = 10, id = 1, id = 8, id = 2, id = 5
The order is reversed because otherwise you would have to add DESC to each condition.
You can use a CASE statement
SELECT data
FROM table WHERE id IN (5, 2, 8, 1, 10)
ORDER BY CASE WHEN id = 5 THEN 1 WHEN id = 2 THEN 2 WHEN id = 8 THEN 3 WHEN id = 1 THEN 4 WHEN id = 10 THEN 5 END
SELECT data FROM table
WHERE id IN (5, 2, 8, 1, 10)
ORDER BY FIELD (id, 5, 2, 8, 1, 10)
http://dev.mysql.com/doc/refman/5.5/en/string-functions.html#function_field
Might be easier to auto-generate (because it basically just needs inserting the wanted IDs comma-separated in the same order a second time) than the other solutions suggested using CASE or a number of ID=x, ID=y ...
http://sqlfiddle.com/#!2/40b299/6
I think that's what you're looking for :D Adapt it to your own situation.
To do this dynamically, and within MySql, I would suggest to do the following:
Create a temp table or table variable (not sure if MySql has these), with two columns:
OrderID mediumint not null auto_increment
InValue mediumint -(or whatever type it is)
Insert the values of the IN clause in order, which will generate ID's in order of insertion
Add a JOIN to your query on this temp table
Change your Order By to be
order by TempTable.OrderID
Drop temp table (again, in SQL inside a stored proc, this is automatic, not sure about MySql so mentioning here for full disclosure)
This effectively circumvents the issue of you not having a key to order by in your table ... you create one. Should work.

SQL Query to get column values that correspond with MAX value of another column?

Ok, this is my query:
SELECT
video_category,
video_url,
video_date,
video_title,
short_description,
MAX(video_id)
FROM
videos
GROUP BY
video_category
When it pulls the data, I get the correct row for the video_id, but it pulls the first row for each category for the others. So when I get the max result for the video_id of category 1, I get the max ID, but the first row in the table for the url, date, title, and description.
How can I have it pull the other columns that correspond with the max ID result?
Edit: Fixed.
SELECT
*
FROM
videos
WHERE
video_id IN
(
SELECT
DISTINCT
MAX(video_id)
FROM
videos
GROUP BY
video_category
)
ORDER BY
video_category ASC
I would try something like this:
SELECT
s.video_id
,s.video_category
,s.video_url
,s.video_date
,s.video_title
,short_description
FROM videos s
JOIN (SELECT MAX(video_id) AS id FROM videos GROUP BY video_category) max
ON s.video_id = max.id
which is quite faster that your own solution
I recently released a new technique to handle this type of problem in MySQL.
SCALAR-AGGREGATE REDUCTION
Scalar-Aggregate Reduction is by far the highest-performance approach and simplest method (in DB engine terms) for accomplishing this, because it requires no joins, no subqueries, and no CTE.
For your query, it would look something like this:
SELECT
video_category,
MAX(video_id) AS video_id,
SUBSTRING(MAX(CONCAT(LPAD(video_id, 11, '0'), video_url)), 12) AS video_url,
SUBSTRING(MAX(CONCAT(LPAD(video_id, 11, '0'), video_date)), 12) AS video_date,
SUBSTRING(MAX(CONCAT(LPAD(video_id, 11, '0'), video_title)), 12) AS video_title,
SUBSTRING(MAX(CONCAT(LPAD(video_id, 11, '0'), short_description)), 12) AS short_description
FROM
videos
GROUP BY
video_category
The combination of scalar and aggregate functions does the following:
LPADs the intra-aggregate correlated identifier to allow proper string comparison (e.g. "0009" and "0025" will be properly ranked). I'm LPADDING to 11 characters here assuming an INT primary key. If you use a BIGINT, you will want to increase this to support your table's ordinality. If you're comparing on a DATETIME field (fixed length), no padding is necessary.
CONCATs the padded identifier with the output column (so you get "00000000009myvalue" vs "0000000025othervalue")
MAX the aggregate set, which will yield "00000000025othervalue" as the winner.
SUBSTRING the result, which will truncate the compared identifier portion, leaving only the value.
If you want to retrieve values in types other than CHAR, you may need to performa an additional CAST on the output, e.g. if you want video_date to be a DATETIME:
CAST(SUBSTRING(MAX(CONCAT(LPAD(video_id, 11, '0'), video_date)), 12) AS DATETIME)
Another benefit of this method over the self-joining method is that you can combine other aggregate data (not just latest values), or even combine first AND last item in the same query, e.g.
SELECT
-- Overall totals
video_category,
COUNT(1) AS videos_in_category,
DATEDIFF(MAX(video_date), MIN(video_date)) AS timespan,
-- Last video details
MAX(video_id) AS last_video_id,
SUBSTRING(MAX(CONCAT(LPAD(video_id, 11, '0'), video_url)), 12) AS last_video_url,
...
-- First video details
MIN(video_id) AS first_video_id,
SUBSTRING(MIN(CONCAT(LPAD(video_id, 11, '0'), video_url)), 12) AS first_video_url,
...
-- And so on
For further details explaining the benefits of this method vs other older methods, my full blog post is here: https://www.stevenmoseley.com/blog/tech/high-performance-sql-correlated-scalar-aggregate-reduction-queries
Here is a more general solution (handles duplicates)
CREATE TABLE test(
i INTEGER,
c INTEGER,
v INTEGER
);
insert into test(i, c, v)
values
(3, 1, 1),
(3, 2, 2),
(3, 3, 3),
(4, 2, 4),
(4, 3, 5),
(4, 4, 6),
(5, 3, 7),
(5, 4, 8),
(5, 5, 9),
(6, 4, 10),
(6, 5, 11),
(6, 6, 12);
SELECT t.c, t.v
FROM test t
JOIN (SELECT test.c, max(i) as mi FROM test GROUP BY c) j ON
t.i = j.mi AND
t.c = j.c
ORDER BY c;
A slightly more "rustic" solution, but should do the job just the same:
SELECT
video_category,
video_url,
video_date,
video_title,
short_description,
video_id
FROM
videos
ORDER BY video_id DESC
LIMIT 1;
In other words, just produce a table with all of the columns that you want, sort it so that your maximum value is at the top, and chop it off so you only return one row.
SELECT video_category,video_url,video_date,video_title,short_description,video_id
FROM videos t1
where video_id in (SELECT max(video_id) FROM videos t2 WHERE t1.video_category=t2.video_category );
Please provide your input and output records so that it can be understood properly and tested.