Joining table to union of two tables? - mysql

I have two tables: orders and oldorders. Both are structured the same way. I want to union these two tables and then join them to another table: users. Previously I only had orders and users, I am trying to shoehorn oldorders into my current code.
SELECT u.username, COUNT(user) AS cnt
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user
This finds the number of nonzero total orders all users have made in table orders, but I want to this in the union of orders and oldorders. How can I accomplish this?
create table orders (
user int,
shipped int,
total decimal(4,2)
);
insert into orders values
(5, 1, 28.21),
(5, 1, 24.12),
(5, 1, 19.99),
(5, 1, 59.22);
create table users (
username varchar(100),
userident int
);
insert into users values
("Bob", 5);
Output for this is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 4 |
+----------+-----+
After creating the oldorders table:
create table oldorders (
user int,
shipped int,
total decimal(4,2)
);
insert into oldorders values
(5, 1, 62.94),
(5, 1, 53.21);
The expected output when run on the union of the two tables is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 6 |
+----------+-----+
Just not sure where or how to shoehorn a union into there. Instead of running the query on orders, it needs to be on orders union oldorders. It can be assumed there is no intersect between the two tables.

You just need to union this way:
SELECT u.username, COUNT(user) AS cnt
FROM
(
SELECT * FROM orders
UNION
SELECT * FROM oldorders
) o
LEFT JOIN users u ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user;
First get the combined orders using UNION between orders and oldorders table.
The rest of the work is exactly same what you did.
SEE DEMO
Note:
Left join doesn't make sense in this case. Orders for which the users don't exist then you will get NULL 0 as output. This doesn't hold any value.
If you want <user,total orders> for all users including users who might not have ordered yet then you need to change the order of the LEFT JOIN

Related

MySQL: Trying query for client information only if that client has both a savings and checking account

The two tables below are examples:
create table client (gid bigint(3), cid bigint(3));
insert into client values (0, 1);
insert into client values (1, 2);
insert into client values (2, 3);
create table balance(gid bigint(3), bid bigint(3)), type varchar(10));
insert into balance values (0, 1, 'checking');
insert into balance values (1, 2, 'savings');
insert into balance values (2, 3, 'checking');
insert into balance values (2, 4, 'savings');
insert into balance values (2, 5, 'checking');
Client table contains a general account and client id. The balance table contains a general account and balance id with an account type. For each general id, there can be many clients, and for each general id there can be many balances.
What I am suppose to figure out is how to write a single select statement that joins the two tables into one, and then return the only the client ids that have both a checking and savings account.
gid
cid
bid
type
2
3
3
checking
2
3
4
savings
2
3
5
checking
I figured out how to join the tables with the applicable format using:
SELECT Client_Account.General_Account_General_ID,
Client_ID,
Balance_Account_ID,
Account_Type
FROM Client_Account
INNER JOIN Balance_Account
ON Client_Account.General_Account_General_ID
= Balance_Account.General_Account_General_ID
I know that my question is not written very well, but I will try to edit as many times as possible to clarify. Thank you for the time and consideration.
You can use GROUP BY and HAVING to find gid values with two entries in the balance table. The query can look like this:
SELECT
gid
FROM
balance
GROUP BY
gid
HAVING
COUNT(*) = 2;
This will generate the following result:
+------+
| gid |
+------+
| 2 |
+------+
Use this query in another query to get the rows you want based on these found gid values like this:
SELECT
b.gid,
c.cid,
b.bid,
b.type
FROM
balance b
JOIN
client c ON b.gid = c.gid
WHERE
b.gid IN (SELECT
gid
FROM
balance
GROUP BY
gid
HAVING
COUNT(*) = 2);
This will generate the following result:
+------+------+------+----------+
| gid | cid | bid | type |
+------+------+------+----------+
| 2 | 3 | 3 | checking |
| 2 | 3 | 4 | savings |
+------+------+------+----------+
You can try inner query with EXISTS inside of WHERE statement as so:
SELECT *
FROM client
INNER JOIN balance b1
ON client.gid = b1.gid
WHERE EXISTS(
SELECT '' FROM balance b2
WHERE b2.gid = b1.gid AND b2.cid != b1.cid AND b2.type != b1.type LIMIT 1);
Subquery just look inside of the same table for rows with same gid but difference cid and type. If found, the row will be in the result set.
But I am not sure about the results as I do not fully understand your question.
The problem was the way the specifications where written and my interpretation. The case is that the return does not need to be a client with both a savings and checking, but either a checking or savings. The solution given is:
SELECT Client_Account.General_Account_General_ID,
Client_ID,
Balance_Account_ID,
Account_Type
FROM Client_Account
INNER JOIN Balance_Account
ON Client_Account.General_Account_General_ID
= Balance_Account.General_Account_General_ID
WHERE Balance_Account.Type = "checking"
OR Balance_Account.Type = "savings"
In this case, not clarifying the instructions lead to me creation my own errors.

Return the first occurrence of a user_id from a result set

I have two tables (simplified to):
+----------------+
| attendances |
+-----+----------+
| int | user_id |
+-----+----------+
| int | event_id |
+-----+----------+
+-------------------------+
| events |
+------+------------------+
| int | id |
+------+------------------+
| date | performance_date |
+------+------------------+
And a simple query:
SELECT count(DISTINCT user_id), events.performance_date
FROM attendances
INNER JOIN events
ON event_id = events.id
GROUP BY performance_date
I only wish to count each user_id once, but the above query only removes the duplicates from each performance_date (allowing them to be duplicated across multiple dates).
Is there a query that can remove duplicate user_ids from the entire result set, and only include the first occurence (date wise)? I'm suspecting it might not be possible.
Input/output examples:
If a user attended an event on 2010-10-10 and again on 2010-10-11, then the results would be:
1, 2010-10-10
Not:
1, 2010-10-10
1, 2010-10-11
Or:
2, 2010-10-10
If another user was added to the above, and they attended on 2010-10-10 and on 2010-10-12, then the results would be:
2, 2010-10-10
1, 2020-10-12
As I say, this may not be possible. The actual output isn't strictly important -- just so long as the unique number of people who attended a particular performance can be derived somehow.
The data will be used to construct a cumulative graph of the growth in the number of unique users by event.
If you want the earliest date per user, you can use aggregation:
select u.id user_id, min(e.date) first_event_date
from users u
inner join events e on u.event_id = e.id
group by u.id
Actually, you might be looking for histogram, that is the number of users per their earliest event date. You can do this by adding another level of aggregation:
select first_event_date, count(*) no_users
from (
select min(e.date) first_event_date
from users u
inner join events e on u.event_id = e.id
group by u.id
) t
group by first_event_date
If you want to count all new users per event, you could use the following query:
SELECT Count(u.user_id),
e.performance_date
FROM attendances u
INNER JOIN `events` e
ON u.event_id = e.id
WHERE NOT EXISTS(SELECT u1.user_id
FROM attendances u1
INNER JOIN `events` e1
ON u1.event_id = e1.id
WHERE u1.user_id = u.user_id
AND e1.performance_date < e.performance_date)
GROUP BY performance_date
ORDER BY performance_date
I tested it with the following set:
CREATE TABLE attendances
(
user_id INT,
event_id INT
);
CREATE TABLE `events`
(
id INT,
performance_date DATE
);
INSERT INTO attendances
(user_id,
event_id)
VALUES ( 1, 1),
( 1, 2),
( 2, 1),
( 2, 2),
( 3, 1),
( 4, 2);
INSERT INTO `events`
(id,
performance_date)
VALUES ( 1, '2020-07-24'),
( 2, '2020-07-25');
And then the result is
3 2020-07-24
1 2020-07-25

How to get multiple columns on subquery or group by

I have two tables on MySql, the first contains an ID and the name of some products. I have to get the cheapest combination of brand/market for each product. So, I've inserted some itens into both tables:
UPDATE: Inserted new product (bed) with no 'Product_Brand_Market' to test LEFT JOIN.
UPDATE: Changed some product prices for better testing.
CREATE TABLE Product(
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL);
CREATE TABLE Product_Brand_Market(
product INT UNSIGNED,
market INT UNSIGNED, /*this will be a FOREIGN KEY*/
brand INT UNSIGNED, /*this will be a FOREIGN KEY*/
price DECIMAL(10,2) UNSIGNED NOT NULL,
PRIMARY KEY(product, market, brand),
CONSTRAINT FOREIGN KEY (product) REFERENCES Product(id));
INSERT INTO Product
(name) VALUES
('Chair'), /*will get id=1*/
('Table'), /*will get id=2*/
('Bed'); /*will get id=3*/
INSERT INTO Product_Brand_Market
(product, market, brand, price) VALUES
(1, 1, 1, 8.00), /*cheapest chair (brand=1, market=1)*/
(1, 1, 2, 8.50),
(1, 2, 1, 9.00),
(1, 2, 2, 9.50),
(2, 1, 1, 11.50),
(2, 1, 2, 11.00),
(2, 2, 1, 10.50),
(2, 2, 2, 10.00); /*cheapest table (brand=2, market=2)*/
/*no entries for bed, must return null*/
And tried the following code to get the desired values:
UPDATE: Changed INNER JOIN for LEFT JOIN.
SELECT p.id product, MIN(pbm.price) price, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON p.id = pbm.product
GROUP BY p.id;
The returned price is OK, but I'm getting the wrong keys:
| product | price | brand | market |
|---------|-------|-------|--------|
| 1 | 8 | 1 | 1 |
| 2 | 10 | 1 | 1 |
| 3 | null | null | null |
So the only way I could think to solve it is with subqueries, but I had to use two subqueries to get both brand and market:
SELECT
p.id product,
(
SELECT pbm.brand
FROM Product_Brand_Market pbm
WHERE p.id = pbm.product
ORDER BY pbm.price
LIMIT 1
) as brand,
(
SELECT pbm.market
FROM Product_Brand_Market pbm
WHERE p.id = pbm.product
ORDER BY pbm.price
LIMIT 1
) as market
FROM Product p;
It returns the desired table:
| product | brand | market |
|---------|-------|--------|
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| 3 | null | null |
But I want to know if I really should use these two similar subqueries or there is a better way to do that on MySql, any ideas?
Use a correlated subquery with LIMIT 1 in the WHERE clause:
SELECT product, brand, market
FROM Product_Brand_Market pbm
WHERE (pbm.brand, pbm.market) = (
SELECT pbm1.brand, pbm1.market
FROM Product_Brand_Market pbm1
WHERE pbm1.product = pbm.product
ORDER BY pbm1.price ASC
LIMIT 1
)
This will return only one row per product, even if there are two or many of them with the same lowest price.
Demo: http://rextester.com/UIC44628
Update:
To get all products even if they have no entries in the Product_Brand_Market table, you will need a LEFT JOIN. Note that the condition should be moved to the ON clause.
SELECT p.id as product, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON pbm.product = p.id
AND (pbm.brand, pbm.market) = (
SELECT pbm1.brand, pbm1.market
FROM Product_Brand_Market pbm1
WHERE pbm1.product = pbm.product
ORDER BY pbm1.price ASC
LIMIT 1
);
Demo: http://rextester.com/MGXN36725
The follwing query might make a better use of your PK for the JOIN:
SELECT p.id as product, pbm.brand, pbm.market
FROM Product p
LEFT JOIN Product_Brand_Market pbm
ON (pbm.product, pbm.market, pbm.brand) = (
SELECT pbm1.product, pbm1.market, pbm1.brand
FROM Product_Brand_Market pbm1
WHERE pbm1.product = p.id
ORDER BY pbm1.price ASC
LIMIT 1
);
An index on Product_Brand_Market(product, price) should also help to improve the performance of the subquery.

Get total of referenced tables' column

Consider a database of accounts and deposits:
CREATE TABLE accounts (
id int not null primary key,
name varchar(63)
);
CREATE TABLE deposits (
id int not null primary key,
account int references accounts(id),
dollars decimal(15, 2),
status enum('pending','complete')
);
insert into accounts values
(0, 'us'),
(1, 'europe'),
(2, 'asia');
insert into deposits values
(0, 0, 10, 'pending'),
(1, 0, 20, 'complete'),
(2, 1, 100, 'complete'),
(3, 1, 200, 'pending'),
(4, 1, 300, 'complete'),
(5, 2, 1000, 'pending');
I would like to get a total of all the complete deposits per bank, this is the expected result:
+--------+-----+
| us | 20 |
| europe | 400 |
| asia | 0 |
+--------+-----+
This is the SQL that I tried, but it does not work as expected:
SELECT
a.name, SUM(d.dollars)
FROM
accounts a
INNER JOIN
deposits d ON (a.id = d.account AND d.status='complete');
This is the result that it gave:
+--------+-----+
| us | 420 |
+--------+-----+
Here is an SQLfiddle of the current code.
What have I done wrong, and how can I get the expected sum?
try this
SELECT
a.name, coalesce(SUM(d.dollars),0) as sums
FROM
accounts a
left JOIN
deposits d ON (a.id = d.account AND d.status='complete')
group by a.name
order by sums desc
you should use LEFT JOIN , and you should use GROUP BY also.
LOOK DEMO
You should use grouping by a.name (or maybe even a.id) and LEFT OUTER JOIN (if you want to get non-present values).
EDIT:
SELECT
a.name, SUM(d.dollars)
FROM
accounts a
LEFT OUTER JOIN
deposits d ON (a.id = d.account AND d.status='complete')
GROUP BY a.name;
Try this query:
SELECT
a.name, IF(SUM(d.dollars) IS NULL, 0, SUM(d.dollars) )
FROM
accounts a
LEFT JOIN
deposits d ON (a.id = d.account AND d.status='complete')
GROUP BY a.name ORDER BY a.id;
By joining accounts with deposits you only make sure that you sum dollars for deposits that are linked to an account. If you would also Group by the account name, or even account Id then you will get a Sum/bank.

How to find if a list/set is contained within another list

I have a list of product IDs and I want to find out which orders contain all those products. Orders table is structured like this:
order_id | product_id
----------------------
1 | 222
1 | 555
2 | 333
Obviously I can do it with some looping in PHP but I was wondering if there is an elegant way to do it purely in mysql.
My ideal fantasy query would be something like:
SELECT order_id
FROM orders
WHERE (222,555) IN GROUP_CONCAT(product_id)
GROUP BY order_id
Is there any hope or should I go read Tolkien? :) Also, out of curiosity, if not possible in mysql, is there any other database that has this functionality?
You were close
SELECT order_id
FROM orders
WHERE product_id in (222,555)
GROUP BY order_id
HAVING COUNT(DISTINCT product_id) = 2
Regarding your "out of curiosity" question in relational algebra this is achieved simply with division. AFAIK no RDBMS has implemented any extension that makes this as simple in SQL.
I have a preference for doing set comparisons only in the having clause:
select order_id
from orders
group by order_id
having sum(case when product_id = 222 then 1 else 0 end) > 0 and
sum(case when product_id = 555 then 1 else 0 end) > 0
What this is saying is: get me all orders where the order has at least one product 222 and at least one product 555.
I prefer this for two reasons. The first is generalizability. You can arrange more complicated conditions, such as 222 or 555 (just by changing the "and" to and "or"). Or, 333 and 555 or 222 without 555.
Second, when you create the query, you only have to put the condition in one place, in the having clause.
Assuming your database is properly normalized, i.e. there's no duplicate Product on a given Order
Mysqlism:
select order_id
from orders
group by order_id
having sum(product_id in (222,555)) = 2
Standard SQL:
select order_id
from orders
group by order_id
having sum(case when product_id in (222,555) then 1 end) = 2
If it has duplicates:
CREATE TABLE tbl
(`order_id` int, `product_id` int)
;
INSERT INTO tbl
(`order_id`, `product_id`)
VALUES
(1, 222),
(1, 555),
(2, 333),
(1, 555)
;
Do this then:
select order_id
from tbl
group by order_id
having count(distinct case when product_id in (222,555) then product_id end) = 2
Live test: http://www.sqlfiddle.com/#!2/fa1ad/5
CREATE TABLE orders
( order_id INTEGER NOT NULL
, product_id INTEGER NOT NULL
);
INSERT INTO orders(order_id,product_id) VALUES
(1, 222 ) , (1, 555 ) , (2, 333 )
, (3, 222 ) , (3, 555 ) , (3, 333 ); -- order#3 has all the products
CREATE TABLE products AS (SELECT DISTINCT product_id FROM orders);
SELECT *
FROM orders o1
--
-- There should not exist a product
-- that is not part of our order.
--
WHERE NOT EXISTS (
SELECT *
FROM products pr
WHERE 1=1
-- extra clause: only want producs from a literal list
AND pr.product_id IN (222,555,333)
-- ... that is not part of our order...
AND NOT EXISTS ( SELECT *
FROM orders o2
WHERE o2.product_id = pr.product_id
AND o2.order_id = o1.order_id
)
);
Result:
order_id | product_id
----------+------------
3 | 222
3 | 555
3 | 333
(3 rows)