I'm trying to run an aggregate query where a join can find 0, 1 or 2 rows in the join table.
I want to aggregate "once-only" regardless of whether the join finds 1 or 2 matching rows.
Minimal example.
+--------------+--------+-----------+
| container_id | thing | alternate |
+--------------+--------+-----------+
| 1 | box | 0 |
| 1 | box | 1 |
| 1 | hat | 0 |
| 2 | monkey | 0 |
| 3 | monkey | 1 |
| 3 | chair | 1 |
+--------------+--------+-----------+
+--------------+------+
| container_id | uses |
+--------------+------+
| 1 | 3 |
| 2 | 1 |
| 3 | 2 |
+--------------+------+
You can see that 'box' is associated with container_id number 1 twice. Once with alternate=0 and once with alternate=1.
SELECT
thing, COUNT(DISTINCT ct.container_id) AS occurrencs, SUM(uses) AS uses
FROM
container_thing AS ct
INNER JOIN
container_usage AS cu ON cu.container_id = ct.container_id
GROUP BY
thing
gives:
+--------+------------+------+
| thing | occurrencs | uses |
+--------+------------+------+
| box | 1 | 6 |
| chair | 1 | 2 |
| hat | 1 | 3 |
| monkey | 2 | 3 |
+--------+------------+------+
but I really want is:
+--------+------------+------+
| thing | occurrencs | uses |
+--------+------------+------+
| box | 1 | 3 |
| chair | 1 | 2 |
| hat | 1 | 3 |
| monkey | 2 | 3 |
+--------+------------+------+
I want 3 as the value for uses in the first row because 'box' was in containers that were used a total of three times. Because of the 'alternate' column I get 6 for that value. Can I either join differently or group by differently or express in the SUM expression to only SUM once for each distinct thing regardless of the value of alternate?
(Note that a thing can appear in a container with alternate, without alternate or both.)
SQL necessary to set up the minimal example:
-- Set up db
CREATE DATABASE sumtest;
USE sumtest;
-- Set up tables
CREATE TABLE container (id INT PRIMARY KEY);
CREATE TABLE container_thing (container_id INT, thing NVARCHAR(10), alternate BOOLEAN);
CREATE TABLE container_usage (container_id INT, uses INT);
-- Insert data
INSERT INTO container (id) VALUES (1), (2), (3);
INSERT INTO container_thing (container_id, thing, alternate) VALUES (1, 'box', FALSE), (1, 'box', TRUE), (1, 'hat', FALSE), (2, 'monkey', FALSE), (3, 'monkey', TRUE), (3, 'chair', TRUE);
INSERT INTO container_usage VALUES (1, 3), (2, 1), (3, 2);
-- Query
SELECT thing, COUNT(DISTINCT ct.container_id) AS occurrencs, SUM(uses) AS uses FROM container_thing AS ct INNER JOIN container_usage AS cu ON cu.container_id = ct.container_id GROUP BY thing;
You can work around this by only selecting DISTINCT values of container_id and thing from container_thing in a derived table and JOINing that to container_usage:
SELECT thing, COUNT(ct.container_id) AS occurrences, SUM(uses) AS uses
FROM (SELECT DISTINCT container_id, thing
FROM container_thing) AS ct
INNER JOIN container_usage AS cu ON cu.container_id = ct.container_id
GROUP BY thing;
Output
thing occurrences uses
box 1 3
chair 1 2
hat 1 3
monkey 2 3
Demo on dbfiddle
If you want only the use .. then you should not perform the sum in join .. because the join produce T1xT2 rows for each macthing ON clause
where N is the number of row from table1 and M is the number of rows from table2 so in the case of box you have 2 x 1 with value 3 = 6.
for avoid this you should join container_usage with the subqiery for aggreated result for count of container_thing
select t.thing, t.count_container, cu.uses
from (
SELECT thing, container_id, COUNT(DISTINCT ct.container_id) count_container
FROM container_thing
GROUP BY thing, container_id
) t
inner join container_usage AS cu ON cu.container_id = t.container_id
Related
I have two tables in sql and I need to create another table performing the calculations based on two other tables.
The first one has the sum of revenue for each Ad Unit, table name is ad_unit_table
SELECT
d.`Date`,
'App' as `Partner`,
d.`Ad Unit`,
sum(d.`Revenue`) as `Revenue`
from
`d_master` as d
group by
`Ad Unit`, `Date`
The other table has the sum of revenue for ALL Ad Units, table name is sum_revenue
SELECT
`Date`,
`Partner`
`Ad Unit`,
sum(`Revenue`) as `Sum Revenue`
from
`ad_unit_table`
group by
`Date`
Now I have to find the percentage of the revenue for each Ad Unit. So the formula is (Ad Unit Rev / Sum Rev) * 100. My code currently looks like this:
SELECT
ad.`Date`,
ad.`Partner`,
ad.`Ad Unit`,
(ad.`Revenue` / s.`Sum Revenue`) * 100 as `Percentage`
FROM
`ad_unit_table` as ad
LEFT JOIN `sum_revenue` as s ON ad.`Partner`
GROUP BY
`Date`,
`Ad Unit`
It gives me all NULLS. I would appreciate any help. Thank you!
Are you sure you want to do LEFT JOIN sum_revenue as s ON ad.Partner in your last query?. I tested this construct and this creates a so called cartesian product.
All rows of the left table are combined with all the rows from the other table.
See: https://en.wikipedia.org/wiki/Cartesian_product
For example:
create table testing.test_a (id INT);
create table testing.test_b (id INT);
INSERT INTO test_a VALUES(1),(2),(3),(4);
INSERT INTO test_b VALUES(1),(2),(3),(5);
# Resulting in a cartesian product (4x4 entries)
SELECT * FROM test_a AS a LEFT JOIN test_b AS b ON a.id;
+------+------+
| id | id |
+------+------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
| 1 | 3 |
| 2 | 3 |
| 3 | 3 |
| 4 | 3 |
| 1 | 5 |
| 2 | 5 |
| 3 | 5 |
| 4 | 5 |
+------+------+
# Correctly LEFT joining test_a and test_b would be:
SELECT a.id, b.id FROM test_a AS a LEFT JOIN test_b AS b ON a.id = b.id
# Or use the USING clause to join on column from both tables with same name.
SELECT test_a.id, test_b.id FROM test_a LEFT JOIN test_b USING(id);
+------+------+
| id | id |
+------+------+
| 1 | 1 |
| 2 | 2 |
| 3 | 3 |
| 4 | NULL |
+------+------+
Are you sure the table sum_revenue has entries for all units in ad_unit_table. If this is not the case, some values from sum_revenue result in NULL values because of the not matching entries.
If you only want the values that match use an INNER JOIN instead of an LEFT JOIN.
Also make sure that when could the calculations. none of the values are NULL. Doing calculating with NULL values results in NULL values.
Example:
SELECT 100 / NULL; -- Result NULL
SELECT (10 * NULL) * 100; -- Result NULL
Without more information like table definitions and/or sample data this is all that I can do.
Question Mysql Random Row Query on Inner Join is much the same as mine but it was never answered.
I have a master table m and slave s. S contains 1 to many rows for each m. I would like a query that selects every master row joined to exactly one randomly chosen slave.
If the table schemas were:
M
---
id
S
---
id
mid
then, in pseudo code the query would be:
select * from m inner join s on m.id = s.mid where s.id is one randomly chosen from the values that exist
Can this be translated into real SQL?
I think the following query does the required job but using a subquery (not inner join):
SELECT *, (SELECT id FROM S WHERE S.mid = M.id ORDER BY RAND() LIMIT 1) AS S_id
FROM M
Here is a link to test it.
Hope it helps.
This can be solved using Row_Number() concept. We need to randomly assign row number values within a partition of mid in the table s. And, do a Join from the m table to s using mid and row_number = 1. This will pick a single Random row everytime.
In MySQL version below 8, we can use User-defined Variables to emulate Row_Number(). To understand how this works, you may check this answer for the explanation: https://stackoverflow.com/a/53465139/2469308
Note that this technique will be efficient on Large tables than using a Subquery (in the SELECT clause), as it will be doing overall table Sorting only once
View on DB Fiddle
create table m (id int, m_nm varchar(10));
create table s (id int,
mid int references m(mid),
s_nm varchar(10));
insert into m values(1, "a");
insert into m values(2, "b");
insert into m values(3, "c");
insert into s values(1, 1, "aa");
insert into s values(2, 1, "aa");
insert into s values(3, 2, "bb");
insert into s values(4, 2, "bbb");
insert into s values(5, 2, "bbbb");
insert into s values(6, 3, "cc");
insert into s values(7, 3, "ccc");
Query
SELECT
m.*, s_dt.id, s_dt.mid, s_dt.s_nm
FROM
m
JOIN
(
SELECT
#rn := IF(#m = dt.mid, #rn+1, 1) AS row_num,
#m := dt.mid AS mid,
dt.id,
dt.s_nm
FROM
(
SELECT
id, mid, s_nm, RAND() as rand_num
FROM s
ORDER BY mid, rand_num ) AS dt
CROSS JOIN (SELECT #rn:=0, #m:=0) AS user_vars
) AS s_dt
ON s_dt.mid = m.id AND
s_dt.row_num = 1;
Result (Run #1)
| id | m_nm | id | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1 | a | 2 | 1 | aa |
| 2 | b | 5 | 2 | bbbb |
| 3 | c | 7 | 3 | ccc |
Result (Run #2)
| id | m_nm | id | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1 | a | 1 | 1 | aa |
| 2 | b | 4 | 2 | bbb |
| 3 | c | 6 | 3 | cc |
Result (Run #3)
| id | m_nm | id | mid | s_nm |
| --- | ---- | --- | --- | ---- |
| 1 | a | 1 | 1 | aa |
| 2 | b | 3 | 2 | bb |
| 3 | c | 7 | 3 | ccc |
MySQL 8.0.2+ / MariaDB 10.3+ solution would be simply the following:
SELECT
m.*, s_dt.id, s_dt.mid, s_dt.s_nm
FROM
m
JOIN
(
SELECT
s.*,
ROW_NUMBER() OVER w AS row_num
FROM s
WINDOW w AS (PARTITION BY mid
ORDER BY RAND())
) AS s_dt
ON s_dt.mid = m.id AND
s_dt.row_num = 1
View on DB Fiddle
It's the 3rd day I'm trying to write a MySQL query. Did lots of search, but it still doesn't work as expected. I'll try to simplify tables as much as possible
System has tkr_restaurants table:
restaurant_id | restaurant_name
1 | AA
2 | BB
3 | CC
Each restaurant has a division assigned (tkr_divisions table):
division_id | restaurant_id | division_name
1 | 1 | AA-1
2 | 1 | AA-2
3 | 2 | BB-1
Then there are meals in tkr_meals_to_restaurants_divisions table, where each meal can be assigned (mapped) to whole restaurant(s) and/or specific division(s). If meal is mapped to restaurant, all restaurant's divisions should see it. If meal is mapped to division(s), only specific division(s) should see it.
meal_id | mapped_restaurant_id | mapped_division_id
1 | 1 | NULL
2 | NULL | 1
3 | NULL | 2
I need to display a list of restaurants and number of meals mapped to it depending on user permissions.
Example 1: if user has permissions to access whole restaurant_id 1 and restaurant_3 (and no specific divisions), then list should be:
AA | 3
CC | 0
(because user can access meals mapped to restaurant 1 + all its division, and restaurant 3 + all its divisions (even if restaurant 3 has no divisions/meals mapped))
Example 2: if user has permissions to access only division_id 1, then list should be:
AA | 1
(because user can only access meals mapped to division 1).
The closest query I could get is:
Example 1:
SELECT *,
(SELECT COUNT(DISTINCT meal_id)
FROM
tkr_meals_to_restaurants_divisions
WHERE
tkr_meals_to_restaurants_divisions.mapped_restaurant_id=tkr_restaurants.restaurant_id
OR tkr_meals_to_restaurants_divisions.mapped_division_id=tkr_divisions.division_id)AS total_meals
FROM
tkr_restaurants
LEFT JOIN
tkr_divisions
ON tkr_restaurants.restaurant_id=tkr_divisions.restaurant_id
WHERE
tkr_restaurants.restaurant_id IN (1, 3)
OR tkr_restaurants.restaurant_id IN (
SELECT restaurant_id
FROM tkr_divisions
WHERE division_id IN (NULL)
)
GROUP BY
tkr_restaurants.restaurant_id
ORDER BY
tkr_restaurants.restaurant_name
However, result was:
AA | 2
CC | 0
I believe I'm greatly over-complicating this query, but all the simpler queries I wrote produced even more inaccurate results.
What about this query:
SELECT
FROM tkr_restaurants AS a
JOIN tkr_divisions AS b
ON a.restaurant_id = b.restaurant_id
LEFT OUTER JOIN tkr_meals_to_restaurants_divisions AS c
ON (c.mapped_restaurant_id = a.restaurant_id OR c.mapped_division_id = b.division_id)
As a Base four your further work. It combine all information into one table. If you add e.g. this:
WHERE a.restaurant_id IN (1, 3)
the result will be
| restaurant_id | restaurant_name | division_id | restaurant_id | division_name | meal_id | mapped_restaurant_id | mapped_division_id |
|---------------|-----------------|-------------|---------------|---------------|---------|----------------------|--------------------|
| 1 | AA | 1 | 1 | AA-1 | 1 | 1 | (null) |
| 1 | AA | 2 | 1 | AA-2 | 1 | 1 | (null) |
| 1 | AA | 1 | 1 | AA-1 | 2 | (null) | 1 |
| 1 | AA | 2 | 1 | AA-2 | 3 | (null) | 2 |
just count the distinct meal ids with COUNT(DISTINCT c.meal_id) and take the restaurant name to get AA: 3 for your example 2
I used a sqlfiddle: http://sqlfiddle.com/#!9/fa2b78/18/0
[EDIT]
Change JOIN tkr_divisions AS b to LEFT OUTER JOIN tkr_divisions AS b
Change SELECT * to SELECT a.restaurant_name, COUNT(DISTINCT c.meal_id)
Add a GROUP BY a.restaurant_name at the end.
Update the SQL Fiddle (new link)
I have three tables
Table a
+-----+-------+
| aid | value |
+-----+-------+
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
+-----+-------+
Table b
+-----+------+
| bid | name |
+-----+------+
| 1 | A |
| 2 | B |
| 3 | C |
+-----+------+
Table ba (mapping of table a and table b)
+-----+-----+
| bid | aid |
+-----+-----+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 3 | 2 |
| 1 | 3 |
| 2 | 3 |
| 2 | 4 |
+-----+-----+
From these tables I want a query like
SELECT aid, mapped('true'-if(aid exist in ba) 'false'-otherwise)
FROM a
JOIN b
JOIN ba
WHERE bid=1
to get a result from where I can generate a list
(when bid=1)
A-mapped
B-not mapped
C-mapped
D-not mapped
(when bid=2)
A-mapped
B-not mapped
C-mapped
D-mapped
(when bid=3)
A-mapped
B-mapped
C-not mapped
D-not mapped
Right now I am generating the list in a while loop for all the rows of table 'a' and inside the loop a query is executed for each iteration to check the existence in table 'ba'.
I think this is supposed to be table b independent:
SELECT CONCAT_WS('-', a.value, IF(ba.aid IS NULL, "-not mapped", "-mapped"))
FROM a LEFT JOIN ba ON a.aid = ba.aid AND ba.bid = 1
ORDER BY a.aid
Note: I took "a" table as the base table since your samples included all values from "a" table.
This is a tricky question, but the difficult part is in figuring out how to formulate the query. Once that is out of the way, it is downhill from there. One approach is to use a cross join between the A and B tables to obtain all possible mappings. Then LEFT JOIN to the mapping table to determine which pairs are being mapped and which are not. Try the following query:
SELECT tb.bid, ta.value,
CASE WHEN ba.bid IS NOT NULL THEN 'mapped' ELSE 'not mapped' END AS label
FROM tb INNER JOIN ta -- cross join to obtain all bid/aid pairs
LEFT JOIN ba -- to determine which pairs are mapped/not mapped
ON ta.aid = ba.aid AND tb.bid = ba.bid
ORDER BY tb.bid, ta.value
Demo here:
SQLFiddle
I have a two tables.
work:
+----+----------+
| id | position |
+----+----------+
| 1 | 1 |
| 2 | 2 |
+----+----------+
content:
+----+---------+------+-------------+
| id | work_id | name | translation |
+----+---------+------+-------------+
| 1 | 1 | Kot | 1 |
| 2 | 1 | Cat | 2 |
| 3 | 2 | Ptak | 1 |
| 4 | 2 | Bird | 2 |
| 5 | 2 | Ssss | 3 |
+----+---------+------+-------------+
I want to get result like this:
+----+------+----------+
| id | name | sortName |
+----+------+----------+
| 1 | Kot | NULL |
| 1 | Cat | NULL |
| 2 | Ptak | Ssss |
| 2 | Bird | Ssss |
+----+------+----------+
My not working query is here:
select
w.id,
c.name,
cSort.name as sortName
from
work w
LEFT JOIN
content c
ON
(w.id=c.work_id)
LEFT JOIN
content cSort
ON
(w.id=cSort.work_id)
WHERE
c.translation IN(1,2) AND
cSort.translation=3
ORDER BY
sortName
I want to get for each work at least one translation and secound if exist (translation=1 always exist). And for every row I want special column with translation used to sort. But Not always this translation exist for work.id. In this example I want to sort work by translation=3.
Sorry for my not fluent english. Any ideas?
Best regards
/*
create table work ( id int, position int);
insert into work values
( 1 , 1 ),
( 2 , 2 );
create table content(id int, work_id int, name varchar(4), translation int);
insert into content values
( 1 , 1 , 'Kot' , 1),
( 2 , 1 , 'Cat' , 2),
( 3 , 2 , 'Ptak' , 1),
( 4 , 2 , 'Bird' , 2),
( 5 , 2 , 'Ssss' , 3);
*/
select w.id,c.name,(select c.name from content c where c.work_id = w.id and c.translation = 3) sortname
from work w
join content c on w.id = c.work_id
where c.translation <> 3;
result
+------+------+----------+
| id | name | sortname |
+------+------+----------+
| 1 | Kot | NULL |
| 1 | Cat | NULL |
| 2 | Ptak | Ssss |
| 2 | Bird | Ssss |
+------+------+----------+
So translation is also a work_id and you consider translation = 3 a translation in your example and translation <> 3 an original. You want to join each original record with every translation record where the latter's work_id matches the former's translation.
I think you are simply confusing IDs here. It should be ON (w.translation = cSort.work_id).
Another way to write the query:
select o.work_id as id, o.name, t.name as sortname
from (select * from content where translation <> 3) o
left join (select * from content where translation = 3) t
on t.work_id = o.translation
order by t.name;
There seems to be no need to join table work.
I'd like to add that the table design is a bit confusing. Somehow it is not clear from it what is a translation for what. In your example you interpret translation 3 as a translation for the non-three records, but this is just an example as you say. I don't find this readable.
UPDATE: In order to sort your results by work.position, you can join that table or use a subquery instead. Here is the order by clause for the latter:
order by (select position from work w where w.id = o.work_id);