Union two queries and flag duplicates - mysql

I have two tables. One has all options and one has the user selected options as well as some they created which may not be in the options table.
I need to union the data so I get back a result set which includes all of the options, as well as the users options and somehow flag which are user only and which overlap with the main options data...
Example...
OPTIONS
`COLOR` | `ANIMAL`
---------------------
RED | DOG
BLUE | DOG
GREEN | DOG
YELLOW | CAT
PINK | CAT
ORANGE | CAT
USER SELECTED OPTIONS
`COLOR` | `ANIMAL`
------------------
GREEN | SNAKE
BLUE | DOG
PINK | CAT
PURPLE | CAT
My results need to look like...
`COLOR` | `ANIMAL`| `DUPLICATE_OR_NEW`
----------------------------------------
RED | DOG | 0
BLUE | DOG | 1
GREEN | DOG | 0
YELLOW | CAT | 0
PINK | CAT | 1
ORANGE | CAT | 0
PURPLE | CAT | 1
GREEN | SNAKE | 1
The sort order does not matter in this scenario. I was trying with UNIONS but I think I need to do it by joining the two tables together. I am not coming up with a solution so far.

This may be called cheating but it will work
SELECT COLOR, ANIMAL, sum(DUPLICATE_OR_NEW)
FROM
(
SELECT COLOR, ANIMAL, 1 as DUPLICATE_OR_NEW FROM options
UNION ALL
SELECT COLOR, ANIMAL, 2 as DUPLICATE_OR_NEW FROM UserSelection
) as UTable
GROUP BY COLOR, ANIMAL
-- 1 = unchosen option
-- 2 = new user added option
-- 3 = option exsisted chosen by user
see SQL Fiddle http://sqlfiddle.com/#!2/01c79/2

Another way to approach this:
select color, animal, 1 as duplicate_or_new
from UserSelected
union all
select color, animal, 0 as duplicate_or_new
from options o
where not exists (select 1 from UserSelected us where us.color = o.color and us.animal = o.animal)
The correct way to do it with the union all/group by:
select color, animal, max(which) as duplicate_or_new
from (select color, animal, 1 as which
from UserSelected
union all
select color, animal, 0 as which
from options
) t
group by color, animal
The following query creates two separate flags:
select color, animal, max(isUser) as IsUser, max(isOption) as IsOption
from (select color, animal, 1 as IsUser, 0 as IsOption
from UserSelected
union all
select color, animal, 0 as IsUser, 1 as IsOption
from options
) t
group by color, animal
You can put them in a case statement to format the information:
(case when max(isUser) = 1 and max(isOption) = 1 then 'both'
when max(isUser) = 1 then 'user'
when max(isOption) = 1 then 'option'
else 'impossible'
end)

Related

How to ORDER a list where the same value doesn't appear twice in a row?

I'm returning a list of results from a database but because of a design feature I need a specific order.
The results should return randomly. The only criteria is that one of the values should not appear twice in a row.
Here's the example data:
id
animals
color
1
hamster
brown
2
dog
brown
3
horse
white
4
mouse
gray
5
cat
black
6
bird
orange
7
snake
green
8
monkey
orange
9
chameleon
green
So I have a list of animals and their individual colours in the table. I want to return a list of 5 of these animals randomly ordered but without two colours show up in a row. So the dog can't show up after the mouse and the chameleon can't show up after the snake etc...
I have solved this with PHP in the past. But I'm looking for a faster and smarter solution and hopefully in MySQL only.
Let me know :-)
Well, if you're using a recent version of MySQL (8.0+), you can do something like this.
The first CTE term provides the data. You can replace that with any list of data you wish, directly from some table or the result of a complex query expression.
rn0 is the order of the randomly ordered data.
#Zakaria is correct. Here's the adjusted SQL to handle just the requirement that consecutive rows should not have the same color, after randomly ordering the data.
Basically, this randomly orders the data and then takes just the first edge of each color island, and limits the result to 5 islands.
WITH data (id,animals,color) AS (
SELECT 1 AS id, 'hamster' AS animals , 'brown' AS color UNION
SELECT 2, 'dog' , 'brown' UNION
SELECT 3, 'horse' , 'white' UNION
SELECT 4, 'mouse' , 'gray' UNION
SELECT 5, 'cat' , 'black' UNION
SELECT 6, 'bird' , 'orange' UNION
SELECT 7, 'snake' , 'green' UNION
SELECT 8, 'monkey' , 'orange' UNION
SELECT 9, 'chameleon' , 'green'
)
, list1 AS (
SELECT id, animals, color, ROW_NUMBER() OVER (ORDER BY rand()) AS rn0 FROM data
)
, list AS (
SELECT *, CASE WHEN color = LAG(color) OVER (ORDER BY rn0) THEN 0 ELSE 1 END AS good
FROM list1
)
SELECT *
FROM list
WHERE good = 1
ORDER BY rn0
LIMIT 5
;
An example result:
+----+-----------+--------+-----+------+
| id | animals | color | rn0 | good |
+----+-----------+--------+-----+------+
| 9 | chameleon | green | 1 | 1 |
| 2 | dog | brown | 3 | 1 |
| 6 | bird | orange | 4 | 1 |
| 1 | hamster | brown | 5 | 1 |
| 3 | horse | white | 6 | 1 |
+----+-----------+--------+-----+------+
The original SQL, which does more than requested, requiring distinct colors in the result. It's not what was requested.
WITH data (id,animals,color) AS (
SELECT 1, 'hamster' , 'brown' UNION
SELECT 2, 'dog' , 'brown' UNION
SELECT 3, 'horse' , 'white' UNION
SELECT 4, 'mouse' , 'gray' UNION
SELECT 5, 'cat' , 'black' UNION
SELECT 6, 'bird' , 'orange' UNION
SELECT 7, 'snake' , 'green' UNION
SELECT 8, 'monkey' , 'orange' UNION
SELECT 9, 'chameleon' , 'green'
)
, list AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY rand()) AS rn0 FROM data
)
, step1 AS (
SELECT list.*, ROW_NUMBER() OVER (PARTITION BY color ORDER BY rn0) AS rn
FROM list
)
SELECT *
FROM step1
WHERE rn = 1
ORDER BY rn0
LIMIT 5
;
Sample result:
+----+---------+--------+-----+----+
| id | animals | color | rn0 | rn |
+----+---------+--------+-----+----+
| 7 | snake | green | 1 | 1 |
| 6 | bird | orange | 2 | 1 |
| 3 | horse | white | 3 | 1 |
| 1 | hamster | brown | 5 | 1 |
| 5 | cat | black | 6 | 1 |
+----+---------+--------+-----+----+
Do you mean something like this?
select any_value(name), color from animals group by color order by rand() limit 5;
Fiddle here: https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=ff1854c9698e2c60deaf9131ea87774c

How to use variable from another table for UNION

I have two sql commands I want to combine. I have changed the variables I am actually using in an attempt make it simpler to explain.
I would like to get the name of all fruits and vegetables with the colors that are a favoriteColor of everyone who's age is equal to the given value.
Currently I have these queries split up and I get the favorite color of people with SELECT favoriteColor FROM people WHERE age = ? and then I get all the fruits and vegetables where the color matches the favoriteColor of each person.
I get the matching fruits and vegetables like this:
SELECT * FROM ((SELECT 1 as type, name FROM fruits WHERE color = ?)
UNION ALL
(SELECT 2 as type, name FROM vegetables WHERE color = ?)) results
I basically want something like this, but I haven't been able to get it to work and I also do not want to have to run the same SELECT query twice:
SELECT * FROM ((SELECT 1 as type, name FROM fruits WHERE color =
(SELECT favoriteColor FROM people WHERE age = ?))
UNION ALL
(SELECT 2 as type, name FROM vegetables WHERE color =
(SELECT favoriteColor FROM people WHERE age = ?))) results
And I don't mind if I get duplicated fruits and vegetables, I need the duplicates for my situation.
For example:
If there are 2 people who are 30 years old and both of them like the color red, I want to get all fruits and vegetables that are red twice.
If there are 2 people who are 10 years old and one of them likes the color red and the other one also likes the color green, I want to get all fruits and vegetables that are red and green.
Not sure why you thought you had to test the colour in the union since the driver is people. And I have guessed what our output should be.
create table people(id int,name varchar(10),colour varchar(1),age int);
insert into fruits values
(1,'a','a'),(2,'b','a'),(3,'b','b'),(4,'b','c');
insert into vegetables values
(1,'t','a'),(2,'t','u'),(3,'v','v'),(4,'v','w');
insert into people values
(1,'aa','a',10),(2,'bb','b',10),(3,'cc','c',10),(4,'dd','c',11);
select p.name,p.age,p.name,s.`type`,s.name,s.colour
from people p
join
(
select 1 as type, name,colour from fruits
union
select 2 as type, name,colour from vegetables
) s
on s.colour = p.colour
where p.age = 10;
+------+------+------+------+------+--------+
| name | age | name | type | name | colour |
+------+------+------+------+------+--------+
| aa | 10 | aa | 2 | t | a |
| aa | 10 | aa | 1 | b | a |
| aa | 10 | aa | 1 | a | a |
| bb | 10 | bb | 1 | b | b |
| cc | 10 | cc | 1 | b | c |
+------+------+------+------+------+--------+
5 rows in set (0.00 sec)
i don't know but the simplied version may be
SELECT * FROM ((SELECT 1 as type, name, color FROM fruits WHERE color = ?) UNION ALL
(SELECT 2 as type, name, color FROM vegetables WHERE color = ?)) results
where results.color= (SELECT favoriteColor FROM people WHERE age = ?)
sorry for indentation
I'd do it as a pair of unions to create one unified dataset, joined to another (then filtered) dataset:
SELECT * FROM
(
SELECT 1 as type, name, color FROM fruits
UNION ALL
SELECT 2 as type, name, color FROM vegetables
) plants pl
INNER JOIN
people pe ON pl.color = pe.favoriteColor
WHERE
pe.age = 30
If you want different columns out of fruit and veg, and there might not be a fruit or veg row for a given color:
SELECT * FROM
people pe
LEFT JOIN fruits f on pe.favoriteColor = f.color
LEFT JOIN veg v on pe.favoriteColor = f.color
WHERE
pe.age = 30
But bear in mind that multiple fruits or veg of a given color will cause the result set to multiply in duplicate for the other plant, which could become a nightmare to deal with on the front end

Select count group by another count (My)SQL

Let's say I have a table that lists cars by user.
id | user_id | color
1 | 1 | red
2 | 1 | red
3 | 2 | blue
4 | 2 | red
5 | 3 | red
Now, I want to know how much red cars each client has, so I've done this SQL :
SELECT user_id, COUNT(color)
FROM cars
WHERE color = 'red'
GROUP BY user_id
Which lists me :
1 | 2
2 | 1
3 | 1
But what I really want is the count of each count. Something like :
Users with 1 red car : 2
Users with 2 red car : 1
...
So is there a way to count my select which already includes a count() grouped by ?
Thank you in advance !
Use an aggregation of aggregations:
SELECT redCount, COUNT(*)
FROM (SELECT user_id, COUNT(color) as redCount
FROM cars
WHERE color = 'red'
GROUP BY user_id
) uc
GROUP BY redCount;

MYSQL condense group by two columns into group by one column with breakdown

I am trying to condense an order by 2 columns query, into an order by 1 column query with a breakdown column. If this has a proper name I would greatly appreciate knowing what it is!
Example:
ocean
name | type | colour
fishy | salmon | red
splishy | salmon | red
splashy | salmon | pink
sploshy | salmon | pink
floaty | whale | blue
humprhey | whale | grey
wilson | whale | grey
Looking for (I'm not sure what doing this is called):
type | count | colour breakdown
salmon| 4 | 2 red, 2 pink
whale | 3 | 1 blue, 2 grey
Preferentially with minimal use of string functions.
Not sure how to proceed from:
(Order by one column)
SELECT o.type, count(*)
FROM ocean o
GROUP BY `type`
type | count
salmon| 4
whale | 3
(Order by two columns)
SELECT o.type, o.colour, count(*)
FROM ocean o
GROUP BY `type`, `colour`
type | colour | count
salmon| red | 2
salmon| pink | 2
whale | blue | 1
whale | grey | 2
Ugly, untested, but may just do what you want:
SELECT type, SUM(color_count),
GROUP_CONCAT(CONCAT(color_count, ' ', colour) SEPARATOR ', ') AS colour_breakdown
FROM (
SELECT type, colour, COUNT(colour) AS colour_count
FROM ocean
GROUP BY type, colour
) AS subt
GROUP BY type
Using your second query as a data source, you can use group_concat() to get the output you want:
select type, sum(`count`), group_concat(colBreak separator ',') as color_breakdown
from
(
SELECT
o.type,
o.colour,
count(o.type) as `count`,
concat(o.colour, ' ', count(o.type)) as colBreak
FROM ocean o
GROUP BY `type`, `colour`
) as a
group by type;

MySQL Subquery using SUM, WHERE, GROUP_BY

I'm a beginner (not a DBA).
The simple version of my data ==> My hoped for result:
|ball |color|count| |ball |Total Blue|Total Red|
------------------- ----------------------------
|b1 |red | 2 | ====> |b1 | 5 | 2 |
|b1 |blue | 3 | |b2 | 3 | 1 |
|b1 |blue | 2 |
|b2 |red | 1 |
|b2 |blue | 3 |
I want to tabulate each ball (b1, b2, etc).
Then the total instance of each color for each ball.
There are multiple entries for each color of each ball (in my real world data).
But here I show multiple entries for the blue #1-balls only.
I can easily do this:
SELECT ball,
SUM(count) AS 'Total Blue'
FROM t1
WHERE color = 'blue'
GROUP BY ball
To obtain the first (good) result:
|ball |Total Blue|
-----------------
|b1 | 5 |
|b2 | 3 |
To go further, I think I need a subquery.
But I have not been able to get the subquery to process the same way as the regular query.
Below is the closest result I've gotten so far (simple attempt):
SELECT ball,
SUM(count) AS 'Total Blue',
(SELECT SUM(count) FROM t1 WHERE color = 'red') AS 'Total Red'
FROM t1
WHERE color = 'blue'
GROUP BY ball
I get this:
|ball |Total Blue| Total Red|
---------------------------
|b1 | 5 | 3 |
|b2 | 3 | 3 |
Total Red shows the total of all red balls regardless of ball number.
This more involved subquery (for red) produces the exact same result:
(SELECT SUM(cc) FROM
(SELECT DISTINCT count AS cc FROM t1 WHERE color = 'red') AS dd )
AS 'Total Red'
I've added GROUP BY to this subquery to no added effect.
This is as close as I've been able to get.
Many other tries have given a variety of results.
Try combining SUM and IF:
SELECT
ball,
SUM(IF(color = 'blue', count, 0)) AS 'Total Blue'
SUM(IF(color = 'red', count, 0)) AS 'Total Red'
FROM t1
GROUP BY ball
I just want to offer the following, because CASE is standard SQL and IF is not:
select ball,
sum(case when color = 'blue' then `count` else 0 end) as TotalBlue,
sum(case when color = 'red' then `count` else 0 end) as TotalRed
from t
group by ball
order by 1
Also, having "count" as the name of a column is a bad idea, because it is an SQL reserved word.