I have several tables with different numbers and types of columns, and a single column in common.
+--------+---------+------------+-------------+
| person | beardID | beardStyle | beardLength |
+--------+---------+------------+-------------+
+--------+-------------+----------------+
| person | moustacheID | moustacheStyle |
+--------+-------------+----------------+
I want to fetch all the results that match a given value of the shared column. I can do it using multiple select statements like this:
SELECT * FROM beards WHERE person = "bob"
and
SELECT * FROM moustaches WHERE person = "bob"
But this requires multiple mysql API calls, which seems inefficient. I was hoping I could use UNION ALL to get all the results in a single API call, but UNION requires that the tables have the same number and similar type of columns. I could write a SELECT statement that would manually pad the results from each table by adding columns with NULL values, but that would quickly get unmanageable for a few more tables with a few more columns.
I'm looking for a result set roughly like this:
+--------+---------+------------+-------------+-------------+----------------+
| person | beardID | beardStyle | beardLength | moustacheID | moustacheStyle |
+--------+---------+------------+-------------+-------------+----------------+
| bob | 1 | rasputin | 1 | | |
+--------+---------+------------+-------------+-------------+----------------+
| bob | 2 | samson | 12 | | |
+--------+---------+------------+-------------+-------------+----------------+
| bob | | | | 1 | fu manchu |
+--------+---------+------------+-------------+-------------+----------------+
Is there a way to achieve this that's fast and maintainable? Or am I better off running a separate query for each table?
Clarification:
I'm not looking for a cartesian product. I don't want a row for every combination of beard-and-moustache, I want a row for every beard and a row for every moustache.
So if there are 3 matching beards and 2 matching moustaches I should get 5 rows, not 6.
this should be working fine:
SELECT * FROM `beards` b LEFT OUTER JOIN `mustaches` ON (0) WHERE person = "bob"
UNION ALL
SELECT * FROM `beards` b RIGHT OUTER JOIN `mustaches` ON (0) WHERE person = "bob"
you don't have to handle the columns by yourself. the left and right outer join do this job.
unfortunately mysql doesn't have a full join. that's why you have to do it this way with a union
SELECT * FROM `customer` b LEFT OUTER JOIN `charges` ON (0) LEFT OUTER JOIN `day` ON (0)
UNION
SELECT * FROM `customer` b RIGHT OUTER JOIN `charges` ON (0) LEFT OUTER JOIN `day` ON (0)
UNION
SELECT * FROM `customer` b LEFT OUTER JOIN `charges` ON (0) RIGHT OUTER JOIN `day` ON (0)
this is a local test i made
Join on person....
I.e.
Select
t1.(asterix), t2.(asterix)
FROM
beards t1
INNER JOIN
moustaches t2 On t2.person = t1.person
SELECT *
FROM beards
JOIN moustaches
ON moustaches.person = beards.person
WHERE person = "bob"
I had fun with this, not sure it's entirely manageable with what more you have to add, but it accomplished the goal.
create table beard (
person varchar(20)
,beardID int
,beardStyle varchar(20)
,beardLength int )
create table moustache(
person varchar(20)
,moustacheID int
,moustacheStyle varchar(20))
insert into beard
select 'bob', 1, 'rasputin', 1
union select 'bob', 2, 'samson', 12
insert into moustache
select 'bob', 1, 'fu manchu'
declare #facialhair table (
person varchar(20)
,beardID int
,beardStyle varchar(20)
,beardLength int
,moustacheID int
,moustacheStyle varchar(20))
declare #i int
declare #name varchar(20)
set #name = 'bob'
set #i = (select COUNT(*) from beard where person = #name)
+ (select COUNT(*) from moustache where person = #name)
print #i
while #i > 0
begin
insert into #facialhair (person, beardID, beardStyle, beardLength)
select person, beardID, beardStyle, beardLength
from beard
where person = #name
set #i = #i-##ROWCOUNT
insert into #facialhair (person, moustacheID, moustacheStyle)
select person, moustacheID, moustacheStyle
from moustache
where person = #name
set #i = #i-##ROWCOUNT
end
select *
from #facialhair
I think you would be better by making queries for data in each table.
One of other possibilities is to concatenate data from all columns into one big string (you could choose some sign to separete column's values), then you should be able to use union all clause to combine results from each query - but then you will have to parse each row.. And data types will be lost.
Related
I am working on a photography project and I am facing a bit issue with joining tables and retrieving data from mysql database.
I have created two tables for this project. One table named cm_team is for team members and another table named cm_events for photography events..Assume to shoot a event, we require 6 persons and the id of the person is stored in cm_events table.
As you can see from the above images.. I am storing the id's of members of cm_team in cm_events table.. I wish to obtain the name of the team member in the respective highlighted fields in the cm_events table..Any help is highly appreciated.
for example my desired output should be: instead of 5 under team_lead heading, I should get the name corresponding to 5 i.e Arjun
Something like this? (cleaner and faster than subqueries)
SELECT
`event`.client_name,
`event`.client_number,
# some more event cols ..
`team_lead`.`cm_name` AS `team_lead`,
`candid_photo`.`cm_name` AS `candid_photo`,
`candid_video`.`cm_name` AS `candid_video`,
`traditional_photo`.`cm_name` AS `traditional_photo`,
`traditional_video`.`cm_name` AS `traditional_video`,
`helper`.`cm_name` AS `helper`
FROM cm_events `event`
JOIN cm_team `team_lead` ON `team_lead`.`cm_code` = `event`.`team_lead`
JOIN cm_team `candid_photo` ON `candid_photo`.`cm_code` = `event`.`candid_photo`
JOIN cm_team `candid_video` ON `candid_video`.`cm_code` = `event`.`candid_video`
JOIN cm_team `traditional_photo` ON `traditional_photo`.`cm_code` = `event`.`traditional_photo`
JOIN cm_team `traditional_video` ON `traditional_video`.`cm_code` = `event`.`traditional_video`
JOIN cm_team `helper` ON `helper`.`cm_code` = `event`.`helper`
With Sub queries
DROP TABLE IF EXISTS T,t1;
CREATE TABLE T (
id int, name varchar(10));
insert into t values
(1 , 'aaa'),
(2 , 'bbb');
create table t1 (
id int, team_lead int,team_a int);
insert into t1 values
(1,1,2),
(2,2,2);
select t1.id, (select t.name from t where t.id = t1.team_lead) team_lead,
(select t.name from t where t.id = t1.team_a) team_a
from t1;
+------+-----------+--------+
| id | team_lead | team_a |
+------+-----------+--------+
| 1 | aaa | bbb |
| 2 | bbb | bbb |
+------+-----------+--------+
2 rows in set (0.001 sec)
I have a products table which contains all my products. Those products table gets filled permanently with new products. However, I want to have the possibility to "hold up"/"pin" certain products to a place in the returned query collection.
Means, I want to set something like rank_index which contains the number the product should have in the returned query collection.
Example:
id title rank_index
1 An awesome product
2 Another product 5
3 Baby car
4 Green carpet 2
5 Toy
Lets assume the default order would be the id. But because the rank_index is set for the product with the id 4 I would like to get the collection with the following order of ids returned: 1, 4, 3, 5, 2.
Is this somehow possible to do? The rank_index column was just an idea of mine. I mean.. I also could do this on the php side and do a normal query which does only include the products without an rank_index and one which only contains products with an index_rank and order them manually on the php side.
However, because this takes a lot of time and processing power I am looking for a solution which is done by the database... Any ideas?
Btw: I am using Laravel 8 if this makes any difference.
Kind regards
This is a very tricky problem. If you try the other approach setting consecutive values -- like 2 and 3 -- you will see that they do not work.
There may be simpler ways to solve this. But, here is a brute force approach.
It constructs a derived table by enumerating the rows in the original table.
It adds into this table (using a left join) all the force-ranked values.
It joins in the rest of the values by enumerating the empty slots both in table1 and in the derived table.
So:
with recursive n as (
select row_number() over (order by id) as n
from table1 t1
),
nid as (
select n.n, t1.id
from n left join
table1 t1
on t1.rank_index = n.n
),
nids as (
select n.n, coalesce(n.id, t1.id) as id
from (select nid.*, sum(nid.id is null) over (order by nid.n) as seqnum
from nid
) n left join
(select t1.*, row_number() over (order by id) as seqnum
from table1 t1
where rank_index is null
) t1
on n.seqnum = t1.seqnum
)
select t1.*
from nids join
table1 t1
on t1.id = nids.id
order by nids.n;
Use the rank_index if it's not null as the ordering, id otherwise:
Since you want the rank_index to be ahead of an id, a -0.5 adjustment is made:
SELECT *
FROM table
ORDER BY IF(rank_index IS NULL, id, rank_index - 0.5)
You can use IF clause and to have the correct the number to get te right order, so
CREATE TABLE table1 (
`id` INTEGER,
`title` VARCHAR(18),
`rank_index` INT
);
INSERT INTO table1
(`id`, `title`, `rank_index`)
VALUES
('1', 'An awesome product', NULL),
('2', 'Another product', '5'),
('3', 'Baby car', NULL),
('4', 'Green carpet', '2'),
('5', 'Toy', NULL);
SELECT *
FROM table1
ORDER BY IF(rank_index IS NULL, id, rank_index + .01)
+----+--------------------+------------+
| id | title | rank_index |
+----+--------------------+------------+
| 1 | An awesome product | NULL |
| 4 | Green carpet | 2 |
| 3 | Baby car | NULL |
| 5 | Toy | NULL |
| 2 | Another product | 5 |
+----+--------------------+------------+
db<>fiddle here
Suppose I have a table like:
ID|Word |Reference
1 |Dog |1
1 |Fish |2
1 |Sheep|3
2 |Dog |4
2 |Fish |5
3 |Sheep|6
4 |Dog |7
I want to select all ID's that have the word Dog AND Sheep. So the result should be ID's: 1 and 2. I tried using this query:
SELECT ID FROM `Table` WHERE Word='Dog' OR Word='Fish' GROUP BY ID Having Word='Dog AND Word='Fish'
However, this AND in the Having clause makes me get 0 results. So, am I doing something wrong or is there another way to achieve wat I want based on MySQL query only (to optimize speed, since it has to search through many rows with the same setup as in the example above)
Basically the problem is the AND statement over multiple rows with the same ID.
UPDATE:
I need to get the reference for the ID's that where found. E.g. when the ID 1 and 2 are returned I need to know that ID 1 has reference 1 and 2. ID 2 has reference 3 and 4. Currently, I'm using this query:
SELECT ID FROM `Test` WHERE Word in ('Dog', 'Fish') GROUP BY ID HAVING count(DISTINCT Word) = 2;
Thanks
Here are two solutions that return the correct records, the first as individual records by ID and Reference, and the second with one record per ID and the Words and References as comma separated in columns.
Setup table and populate rows:
DROP TABLE IF EXISTS `list1`;
CREATE table `list1` (
id int(10),
Word varchar(10),
Reference int(10)
);
INSERT INTO `list1` (`ID`, `Word`, `Reference`)
VALUES
(1, 'Dog',1),
(1 ,'Fish',2),
(1 ,'Sheep',3),
(2 ,'Dog',4),
(2 ,'Sheep',5),
(3 ,'Sheep',6),
(4 ,'Dog',7);
Returns one row for each combination of ID and Word
SELECT
t.`ID`,
t.`Word`,
t.`Reference`
FROM `list1` as t
JOIN (
SELECT
t1.`ID` as `ref_id`
FROM `list1` AS t1
WHERE `Word` in ('Sheep','Dog')
GROUP BY t1.`ID`
HAVING count(DISTINCT t1.`Word`) = 2
) AS ts
ON t.`ID` = ts.`ref_id`
WHERE t.`Word` in ('Sheep','Dog')
ORDER BY t.`ID`,t.`Word`;
Results
ID | Word | Reference
1 | Dog | 1
1 | Sheep | 3
2 | Dog | 4
2 | Sheep | 5
Returns one row per ID, with a comma separated list of Words in one column, and a comma separated list of Reference in another.
SELECT
t.`ID`,
GROUP_CONCAT(t.`Word`) AS `Words`,
GROUP_CONCAT(t.`Reference`) AS `References`
FROM `list1` as t
JOIN (
SELECT
t1.`ID` as `ref_id`
FROM `list1` AS t1
WHERE `Word` in ('Sheep','Dog')
GROUP BY t1.`ID`
HAVING count(DISTINCT t1.`Word`) = 2
) AS ts
ON t.`ID` = ts.`ref_id`
WHERE t.`Word` in ('Sheep','Dog')
GROUP BY t.`ID`
ORDER BY t.`ID`,t.`Word`;
Results:
ID | Words | References
1 | Dog,Sheep | 1,3
2 | Dog,Sheep | 4,5
You need to join the table on itself. This way you can pick up where the id's are the same for instances where dog and sheep overlap.
Try this:
declare #t table (id int , Word varchar(10) )
insert into #t (ID, Word) values (1, 'Dog'),
(1 ,'Fish'),
(1 ,'Sheep'),
(2 ,'Dog'),
(2 ,'Sheep'),
(3 ,'Sheep'),
(4 ,'Dog')
select t.ID
from #t as t
join #t as t1 on t1.id = t.id
where t.word = 'Dog' and t1.word = 'Sheep'
Here's one way to do it by joining your table to itself.
SELECT t1.id FROM `Table` t1
INNER JOIN `Table` t2 ON t1.id = t2.id
WHERE t1.word='Dog' AND t2.word='Sheep';
The answer for my problem is solved using underneath query:
SELECT ID, GROUP_CONCAT(Reference) as ReferencesGrouped FROM `Test` WHERE Word in ('Dog', 'Fish') GROUP BY ID HAVING count(DISTINCT Word) = 2;
This will return me:
ID|ReferencesGrouped
1 |1,4
2 |4,5
I am learning mysql, I have 2 tables, I have to compare table1 primary keys with table2 primary key, on successful match I need to get date min and max from table2 for this I have tried these commands, though I got result but it takes long time please let me know if there any good way to handle this case.
date format is like this
mysql> select sdate from table2
| 27-Apr-2000 11:50:00 AM |
| 27-Apr-2000 10:20:00 AM |
| 27-Apr-2000 08:30:00 AM |
| 20-Jan-1999 12:00:00 PM |
Commands I tried
mysql> select min(str_to_date(m.sdate,'%d-%M-%Y')) as date_min, max(str_to_date(m.sdate,'%d-%M-%Y')) as date_max from ( select distinct p.key1 as key1, p.key2 as key2 from table1 as p ) as T inner join table2 as m on T.key1 = m.key1 and T.key2 = m.key2 where m.sdate !='';
+------------+------------+
| date_min | date_max |
+------------+------------+
| 1989-02-24 | 2011-12-30 |
+------------+------------+
1 row in set, 11396 warnings (18.95 sec)
mysql> select min(str_to_date(m.sdate,'%d-%M-%Y')) as date_min, max(str_to_date(m.sdate,'%d-%M-%Y')) as date_max from ( select p.key1 as key1, p.key2 as key2 from table1 as p ) as T inner join table2 as m on T.key1 = m.key1 and T.key2 = m.key2 where m.sdate !='';
+------------+------------+
| date_min | date_max |
+------------+------------+
| 1989-02-24 | 2011-12-30 |
+------------+------------+
1 row in set, 11442 warnings (18.78 sec)
mysql> select min(str_to_date(m.sdate,'%d-%M-%Y')) as date_min, max(str_to_date(m.sdate,'%d-%M-%Y')) as date_max from table2 as m, table1 as p where p.key1 = m.key1 and p.key2 = m.key2 and m.sdate !='';
+------------+------------+
| date_min | date_max |
+------------+------------+
| 1989-02-24 | 2011-12-30 |
+------------+------------+
1 row in set, 11442 warnings (18.86 sec)
mysql>
None of the queries is able to make effective use of an index (ie. range scan operation) on the VARCHAR sdate column, because that column is "wrapped" in a function in the query. For optimum performance of queries of this form, ideally the sdate would be an actual MySQL DATETIME or TIMESTAMP datatype, or even a VARCHAR in canonical format. If that were the case, the optimizer would be able to make effective use of an index to quickly locate the "smallest" and "largest" date values, without a need to evaluate the STR_TO_DATE function for every flipping row in the table, and avoid the need for a sort operation to locate the "smallest" and "largest" values returned from the function.
With that (semi-rant) aside...
In the general case, to get a result equivalent to the first two queries in your question, a query of the form suggested in the answer from Gordon Linoff may be your best bet.
(We note the first two queries include a key2=key1 predicates, the third query has a key2=key2 predicate.)
If there are a large number of rows in table2, and if a large majority of those rows will "match" a row from table1, and there are relatively small number of distinct (key1,key2) values in table2, and if the (key1,key2) tuple is either unique or nearly unique in table1,
there's an outside chance that a query of this form may perform better:
SELECT MIN(q.sdate_min) AS date_min
, MAX(q.sdate_max) AS date_max
FROM ( SELECT m.key1
, m.key2
, MIN(STR_TO_DATE(m.sdate,'%d-%M-%Y')) AS sdate_min
, MAX(STR_TO_DATE(m.sdate,'%d-%M-%Y')) AS sdate_max
FROM table2 m
GROUP
BY m.key1
, m.key2
) q
JOIN table1 t
ON t.key1 = q.key1
AND t.key2 = q.key2
To improve performance of the inline view query, you are going to want an index on table2 with leading columns of key1 and key2 (in either order), and also including the sdate column. For example:
... ON table2 (key1, key2, sdate)
To improve performance of the JOIN operation, you are going to want an index on table1 with key1 and key2 as the leading columns in the index. For example:
... ON table1 (key1,key2)
or
... ON table1 (key2,key1)
(This assumes you will be using predicates of the form in your third query i.e. key1=key1 and key2=key**2**
If you will be using predicates of the form key1=key1 and key2=key**1**, then we'd adjust the query and indexes accordingly.
You can try this approach:
select min(str_to_date(m.sdate,'%d-%M-%Y')) as date_min,
max(str_to_date(m.sdate,'%d-%M-%Y')) as date_max
from table2 m
where exists (select 1
from table1 t
where t.key1 = m.key1 and t.key1 = m.key2
);
Then, create an index on table1(key1, key2) for performance.
i have 3 tables that looks like this:
game_table
+---------+------------+------------+----------------------+----------+
| game_id | game_title | sponser_id | game expiration date | prize_id |
+---------+------------+------------+----------------------+----------+
prize_table
+----------+---------------------------+------------+-------------+--------------------+--------------------------------------------+
| prize_id | prize_image_name | prize_cost | prize_title | remaining_quantity | prize_description |
+----------+---------------------------+------------+-------------+--------------------+--------------------------------------------+
sponser_table
+------------+--------------+
| sponser_id | sponser_name |
+------------+--------------+
how do i build query that select all data from the 3 tables that
meat the statement that go's something like pseudo code:
select all data from game_table and prize_table and sponser_table where game_table.sponser_id = 2 and game_table.prize_id = 2
i tried something like this :
SELECT game_list.*, prize_list.* ,sponser_list.* FROM game_list, prize_list,sponser_list
WHERE game_list.sponser_id=2 And game_list.prize_id = 2 And game_list.game_id=2 ;
but it gave me no good results .
You had a WHERE clause to limit to the correct ids, but you had no join conditions to relate your tables. Instead of the implicit join syntax you attempted (comma-separated table list), use a explicit JOINs with stated relating columns:
SELECT
game_list.*,
prize_list.* ,
sponser_list.*
FROM
game_list
JOIN prize_list ON game_list.prize_id = prize_list.prize_id
JOIN sponser_list ON game_list.sponser_id = sponser_list.sponser_id
WHERE game_list.sponser_id=2 And game_list.prize_id = 2 And game_list.game_id=2 ;
I would recommend against selecting all columns from each table though, since you are duplicating the id columns in at least two places. Instead, be explicit about the columns you want. This will also help you if you later add additional columns to these tables that should not be included in this query.
SELECT
game_id,
game_title,
game_list.sponser_id,
game_expiration_date,
game_list.prize_id,
prize_image_name,
prize_cost,
prize_title,
remaining_quantity,
prize_description,
sponser_name
FROM
game_list
JOIN prize_list ON game_list.prize_id = prize_list.prize_id
JOIN sponser_list ON game_list.sponser_id = sponser_list.sponser_id
WHERE game_list.sponser_id=2 And game_list.prize_id = 2 And game_list.game_id=2 ;
SELECT *
FROM game_table
JOIN prize_table USING (prize_id)
JOIN sponser_table USING (sponser_id)
WHERE sponser_id = 2
AND prize_id = 2
AND game_id = 2
SELECT
game_list.*, prize_list.* ,sponser_list.*
FROM game_list
JOIN prize_list ON game_list.prize_id = prize_list.prize_id
JOIN sponser_list ON game_list.sponser_id = sponser_list.sponser_id
WHERE
game_list.sponser_id=2 And game_list.prize_id = 2 And game_list.game_id=2 ;
From your description it appears that the tables may be related. If they are, you need to use a join, like this:
SELECT *
FROM game_table g
LEFT OUTER JOIN prize_table p ON p.prize_id=g.prize_id
LEFT OUTER JOIN sponser_table s ON s.sponser_id=g.sponser_id
WHERE g.game_id=2