MySQL if/else for tables - mysql

Is there a way to if else tables in mysql?
Example:
select * from tableA A, if(tableA.find=1,TableBA B,TableBB B) where A.id = B.aid
Real Example:
tables are: location_order, order_contract, order_gm, order_gm_hh:
select *
from location order lo, order_contract oc, if(lo.hh=0,order_gm,order_gm_hh) as og
where lo.orderid = oc.orderid && oc.gmid = og.id

You can left outer join both tables, and then use the IF() function in the SELECT clause:
SELECT a.*,
IF(a.find = 1, b1.value, b2.value) b_value
FROM tableA a
LEFT JOIN tableBA b1 ON (b1.aid = a.id)
LEFT JOIN tableBB b2 ON (b2.aid = a.id);
Test case:
CREATE TABLE tableA (id int, find int, value int);
CREATE TABLE tableBA (id int, aid int, value int);
CREATE TABLE tableBB (id int, aid int, value int);
INSERT INTO tableA VALUES (1, 1, 100);
INSERT INTO tableA VALUES (2, 0, 200);
INSERT INTO tableA VALUES (3, 1, 300);
INSERT INTO tableA VALUES (4, 0, 400);
INSERT INTO tableBA VALUES (1, 1, 10);
INSERT INTO tableBA VALUES (2, 3, 20);
INSERT INTO tableBB VALUES (1, 2, 30);
INSERT INTO tableBB VALUES (2, 4, 40);
Result:
+------+------+-------+---------+
| id | find | value | b_value |
+------+------+-------+---------+
| 1 | 0 | 100 | 10 |
| 2 | 1 | 200 | 30 |
| 3 | 0 | 300 | 20 |
| 4 | 1 | 400 | 40 |
+------+------+-------+---------+
4 rows in set (0.00 sec)

Do an outer join on both tables and use a case statement in the select to pull the value from whichever table meets the Find=1 criteria using a case statement.
SELECT Case A.Find
WHEN 1 THEN B1.SomeField
ELSE B2.SomeField
END as SomeField
FROM tableA A
LEFT JOIN tableBA b1 ON (b1.aid = A.id)
LEFT JOIN tableBB b2 ON (b2.aid = A.id);

You could have to separate this out into two select statements. For example:
select * from tableA A
inner join TableBA B on A.id = B.aid
where A.find = 1
UNION
select * from tableA A
inner join TableBB B on A.id = B.aid
where A.find <> 1

Related

MySQL COUNT and GROUP BY subquery

I'm trying to find the count of posts grouped by branch and category. I'm not getting the categories with count 0.
CREATE TABLE branches
(`id` serial primary key, `name` varchar(7) unique)
;
INSERT INTO branches
(`id`, `name`)
VALUES
(1, 'branch1'),
(2, 'branch2'),
(3, 'branch3')
;
CREATE TABLE categories
(`id` serial primary key, `category` varchar(4) unique)
;
INSERT INTO categories
(`id`, `category`)
VALUES
(1, 'cat1'),
(2, 'cat2')
;
CREATE TABLE posts
(`id` serial primary key, `branch_id` int, `category_id` int, `title` varchar(6), `created_at` varchar(10))
;
INSERT INTO posts
(`id`, `branch_id`, `category_id`, `title`, `created_at`)
VALUES
(1, 1, 1, 'Title1', '2017-12-14'),
(2, 1, 2, 'Title2', '2018-01-05'),
(3, 2, 1, 'Title3', '2018-01-10')
;
Expected Output:
+---------+----------+----+----+
| branch | category | c1 | c2 |
+---------+----------+----+----+
| branch1 | cat1 | 1 | 0 |
| branch1 | cat2 | 0 | 1 |
| branch2 | cat1 | 0 | 1 |
| branch2 | cat2 | 0 | 0 |
+---------+----------+----+----+
Query tried:
SELECT b.name, x.c1, y.c2 FROM branches b
LEFT JOIN (
SELECT COUNT(id) c1 FROM posts WHERE created_at < '2018-01-01'
GROUP BY posts.branch_id, posts.category_id
) x x.branch_id = b.id
LEFT JOIN (
SELECT COUNT(id) c2 FROM posts WHERE created_at BETWEEN '2018-01-01' AND '2018-01-31'
GROUP BY posts.branch_id, posts.category_id
) y y.branch_id = b.id
GROUP BY b.id
You need to CROSS JOIN branches and categories first; then LEFT JOIN to posts and do conditional counts based on your WHERE criteria.
Generic format:
SELECT x.data, y.data
, COUNT(CASE WHEN conditionN THEN 1 ELSE NULL END) AS cN
FROM x CROSS JOIN y
LEFT JOIN z ON x.id = z.x_id AND y.id = z.y_id
GROUP BY x.data, y.data
;
Note: COUNT (and pretty much all aggregate functions) ignore NULL values.
It looks like this might do what you want.
Explanation: Get each possible combination of branch/category for branches which exists in posts. Do a conditional sum to get the counts by date range and branch/category. Then join back to branch.
SELECT b.b_id branch,
b.category,
COALESCE(Range_Sum.C1,0) C1,
COALESCE(Range_Sum.C2,0) C2
FROM ( SELECT b.id b_id,
c.id c_id,
c.category
FROM branches b,
categories c
WHERE EXISTS
( SELECT 1
FROM posts
WHERE b.id = posts.branch_id
)
) b
LEFT
JOIN (SELECT p.branch_id,
c.id c_id,
c.category,
SUM
( CASE WHEN p.created_at < '2018-01-01' THEN 1
ELSE 0
END
) C1,
SUM
( CASE WHEN p.created_at BETWEEN '2018-01-01' AND '2018-01-31' THEN 1
ELSE 0
END
) C2
FROM posts p
INNER
JOIN categories c
ON p.category_id = c.id
GROUP
BY p.branch_id,
c.category,
c.id
) Range_Sum
ON b.b_id = Range_Sum.branch_id
AND b.c_id = Range_Sum.c_id;
Also, just a thing for writing easily readable queries - NEVER use x and y as aliases. Choose anything else that could possibly be more informative.
Maybe a little contrived...
SELECT DISTINCT x.branch_id
, y.category_id
, COALESCE(z.created_at < '2018-01-01',0) c1
, COALESCE(z.created_at BETWEEN '2018-01-01' AND '2018-01-31',0) c2
FROM posts x
JOIN posts y
LEFT
JOIN posts z
ON z.branch_id = x.branch_id
AND z.category_id = y.category_id;
http://sqlfiddle.com/#!9/8aabf2/31

mysql query select from one table such that its column's values are not present in the same column's values in another table

I have two mysql tables: A, and B.
A has columns id, alpha, blabla, moreblabla, with primary key id.
B has columns id, alpha, beta, somemoreblabla, with primary key (id, alpha)
I need to select all those A.id's, for which its A.alpha is not present in any of the B.alpha's respective to every B.id = A.id
How do I do it?
Your question is not entirely clear. Do you want all A.alpha's that are not in any B.alpha? In that case a simple query like this is enough:
Select A.id from A where A.alpha NOT IN (Select B.alpha from B);
If you want to select all ID's from A that have a counterpart (an equal ID) in B but where the alpha between A and B are different it is a bit more work:
SELECT A.id FROM A
INNER JOIN B on A.id = B.id
WHERE A.alpha != B.alpha
Consider the following structure:
CREATE TABLE `A` (
`id` int(11) NOT NULL,
`alpha` varchar(255) NOT NULL
);
CREATE TABLE `B` (
`id` int(11) NOT NULL,
`alpha` varchar(255) NOT NULL
);
With inserts:
insert into A set id = 1, alpha = 'a';
insert into A set id = 2, alpha = 'b';
insert into B set id = 1, alpha = 'a';
insert into B set id = 2, alpha = 'a';
If you run the query with the join your result will be:
+----+
| id |
+----+
| 2 |
+----+
This is since ID 2 in A has a different alpha than ID 2 in B.
EDIT:
It just occurred to me that you might even mean that every A.id can occur in B multiple times. If that is what can happen you need a different approach again. Lets assume the same insert as before with an addition:
insert into A set id = 1, alpha = 'a';
insert into A set id = 2, alpha = 'b';
insert into B set id = 1, alpha = 'a';
insert into B set id = 2, alpha = 'a';
insert into B set id = 2, alpha = 'b'; <- important since there is now a 2nd 2 in B that should ensure that the record with ID 2 from A should not be returned.
insert into A set id = 3, alpha = 'c';
insert into B set id = 3, alpha = 'x'; <-- only ID 3 should now be returned due to the situation above
Our tables now look like so:
A
+----+-------+
| id | alpha |
+----+-------+
| 1 | a |
| 2 | b |
| 3 | c |
+----+-------+
B
+----+-------+
| id | alpha |
+----+-------+
| 1 | a |
| 2 | a |
| 2 | b |
| 3 | x |
+----+-------+
If this is your case the following query will do the trick:
select A.id
FROM A where A.alpha NOT IN (
select B.alpha FROM B where B.id = A.id
);
SELECT A.id
FROM
A
LEFT OUTER JOIN B ON A.id = B.id AND B.alpha = A.alpha
where B.alpha IS NULL
Here is SQL Fiddle
This will be the fastest query,better than The marked Answer in terms of query optimization.
EXPLAIN SELECT A.id
FROM
A
LEFT OUTER JOIN B ON A.id = B.id AND B.alpha = A.alpha
where B.alpha IS NULL
Here is the output :
EXPLAIN select A.id
FROM A where A.alpha NOT IN (
select B.alpha FROM B where B.id = A.id
)
Here is the EXPLAIN of Marked answer.
You can see the DIFFERENCE.
SIMPLE VS PRIMARY
SIMPLE VS DEPENDENT SUBQUERY
Hope this helps.
Use MySQL NOT IN
Try this query :-
select id from a where a.id NOT IN (select id from b)
Select A.id from table_A where A.alpha NOT IN (Select B.alpha from table_B);
The Sub-query will return a table with all the records of B.alpha from table_B.
And say it returns 1 to 5 records as result.
And say your table table_A has 1 to 10 records in A.alpha
Now your parent query will check for records in table table_A for field A.alpha which do not belongs to B.alpha (using NOT IN)
Hence the expected result is 6 to 10 as result.

Joining tables doubles the values

I have this table:
CREATE TABLE table1 (
id INT NOT NULL PRIMARY KEY,
value1 INT NOT NULL,
value2 INT NOT NULL
);
CREATE TABLE table2 (
id INT NOT NULL PRIMARY KEY,
table1_id INT NOT NULL,
valuex INT NOT NULL
);
INSERT INTO table1 (id, value1, value2)
VALUES
(1, 10, 15),
(2, 5 , 3);
INSERT INTO table2 (id, table1_id, valuex)
VALUES
(1, 1, 15),
(2, 1, 25),
(3, 2, 14),
(4, 2, 10);
With this:
SELECT COUNT(`table1`.`id`) AS `orders`,
SUM(`value1`) as `sum_value1`, SUM(`value2`) as `sum_value2`,
SUM(`valuex`) as `sum_valuex`
FROM `table1`
INNER JOIN `table2`
ON `table1`.`id` = `table2`.`table1_id`
I get the output:
+----------------------------------------------+
+ orders | sum_value1 | sum_value2 |sum_valuex +
+----------------------------------------------+
+ 4 | 30 | 36 | 64 +
+----------------------------------------------+
But I have only two orders in table1. I know the duplication is being made because of the join, but how can I fix that with adding sum_valuex?
My desired result would be:
+----------------------------------------------+
+ orders | sum_value1 | sum_value2 |sum_valuex +
+----------------------------------------------+
+ 2 | 15 | 18 | 64 +
+----------------------------------------------+
EDIT: I can't use select within select
This is how joins work. If you don't want the rows to multiply before the aggregation, then aggregate before doing the join.
SELECT t2.orders, t1.value1, t1.value2, t2.sum_valuex
FROM `table1` INNER JOIN
(SELECT table1_id, SUM(valuex) as sum_valuex, COUNT(*) as orders
FROM table2
GROUP BY table1_id
) t2
ON t1.id = t2.table1_id
Which table has the orders ? Right now, your count(table1.id) is counting the records in table 2. (That's where this column is, in Table 2) if Table1 is the table with the orders in it then you should be counting the records in table 1
SELECT COUNT(distinct a.id) orders,
SUM(value1) as sum_value1`, SUM(value2) as sum_value2,
SUM(valuex) as sum_valuex
FROM table1 a
JOIN table2 b
ON b.table1_id = a.id
you can get the desire result like this :
SELECT count(orders ) as orders, SUM(t1.value1) as value1, SUM(t1.value2) as value2, SUM(t2.sum_valuex) as sumvaluex
FROM table1 t1 INNER JOIN
(SELECT table1_id, SUM(valuex) as sum_valuex, COUNT(*) as orders
FROM table2
GROUP BY table1_id
) t2
ON t1.id = t2.table1_id
Please dont forget to mark my answer:)

Update row with average from another table based on the original row

Not sure how to describe what I'm trying to get from this question, but here goes...
I have a table of customer purchases, 't1', with information about the purchase: customer id, date, boolean if the customer was alone, and the purchase amount. In a second table t2 is another list of the same customer ids with a date and a boolean saying whether they were alone.
I want to update the second table with the AVERAGE of values of the previous x purchases they did before that date with whether they were alone.
I setup the tables with:
DROP TABLE IF EXISTS t1;
DROP TABLE IF EXISTS t2;
CREATE TABLE t1 (cid INT, d DATE, i INT, v FLOAT);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-01', 0, 10);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-02', 1, 20);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-03', 1, 30);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-04', 1, 40);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-05', 0, 50);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-06', 0, 60);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-07', 0, 70);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-08', 1, 80);
INSERT INTO t1 (cid, d,i,v) VALUES (1,'2001-01-09', 0, 90);
INSERT INTO t1 (cid, d,i,v) VALUES (2,'2001-01-04', 1, 35);
CREATE TABLE t2 (cid INT, d DATE, i INT, av2 FLOAT, av3 FLOAT);
INSERT INTO t2 (cid, d,i) VALUES (1,'2001-01-07', 0);
INSERT INTO t2 (cid, d,i) VALUES (1,'2001-01-08', 1);
INSERT INTO t2 (cid, d,i) VALUES (2,'2001-01-08', 0);
INSERT INTO t2 (cid, d,i) VALUES (2,'2001-01-09', 1);
av2 and av3 are the columns where i want the average of the last 2 or 3 transactions. So i need an update statement (two statements really, one for av2 and one for av3) to say "when this customer came in on this date, and they came in alone or not, what was the average of their last x purchases.
So the resulting data should be:
cid d i av2 av3
1 2001-01-07 0 55 40
1 2001-01-08 1 35 40
2 2001-01-08 0 null null
2 2001-01-08 1 35 35
The closest I got was this:
UPDATE t2 SET av=(
SELECT AVG(tcol)
FROM (
SELECT v AS tcol FROM t1 LIMIT 2
) AS tt);
which seems to be going in the right direction (the limit 2 is the 2 or 3 from the av columns. But that just averages x prior purchases (regardless of customer or the boolean). As soon as I put in the WHERE clause to link the data, it chokes:
UPDATE t2 SET av=(
SELECT AVG(tcol)
FROM (
SELECT v AS tcol FROM t1 WHERE t1.d<t2.d and t1.i=t2.i LIMIT 2
) AS tt);
Any ideas? Is there a name for what I'm trying to do? Do I need to describe this differently? Any suggestions?
Thanks,
Philip
Try this one -
SET #r = 0;
SET #cid = NULL;
SET #i = NULL;
UPDATE t2 JOIN (
SELECT t.cid, t.d, t.i, AVG(IF(t.r < 3, t.v, NULL)) av2, AVG(IF(t.r < 4, t.v, NULL)) av3 FROM (
SELECT t.*, IF(#cid = t.cid AND #i = t.i, #r := #r + 1, #r := 1) AS r, #cid := t.cid, #i := t.i FROM (
SELECT t2.*, t1.v FROM t2
JOIN t1
ON t1.cid = t2.cid AND t1.d < t2.d AND t1.i = t2.i
ORDER BY t2.cid, t2.i, t1.d DESC
) t
) t
GROUP BY t.cid, t.i, t.d
) t
ON t2.cid = t.cid AND t2.i = t.i AND t2.d = t.d
SET t2.av2 = t.av2, t2.av3 = t.av3;
SELECT * FROM t2;
+------+------------+------+------+------+
| cid | d | i | av2 | av3 |
+------+------------+------+------+------+
| 1 | 2001-01-07 | 0 | 55 | 40 |
| 1 | 2001-01-08 | 1 | 35 | 30 |
| 2 | 2001-01-08 | 0 | NULL | NULL |
| 2 | 2001-01-09 | 1 | 35 | 35 |
+------+------------+------+------+------+
Note:
The value av3 for cid=1, d=2001-01-08, i=1 should be 30, rigth? ...not 40.

Question on multi-row insertion with subqueries

Say I have the following 2 tables,
CREATE TABLE t1(
name VARCHAR(25) NOT NULL,
time INT,
a INT
);
CREATE TABLE t2(
name VARCHAR(25) NOT NULL,
time INT,
b INT
);
and Im looking to pull all the values (a) out of t1 with a given time, all the values with the previous time (say just time-1 for convenience) then for each name subtract the newer one from the older one and insert those values into t2 with the same time. The slow way of doing this would involve doing something like
SELECT name, a FROM t1 WHERE time = x;
SELECT name, a FROM t1 WHERE time = x-1;
(subtract the as for each name)
INSERT INTO t2 VALUES ....;
From my (limited) understanding of subqueries, there should hopefully be a way to do this all in 1 query. Any ideas? Thanks in advance :)
It looks like you can use the INSERT ... SELECT syntax:
INSERT INTO t2 (name, time, b)
SELECT ta.name, ta.time time, (ta.a - tb.a) b
FROM t1 ta
JOIN t1 tb ON (tb.time = ta.time - 1 AND tb.name = ta.name);
Test case:
INSERT INTO t1 VALUES ('t1', 1, 100);
INSERT INTO t1 VALUES ('t1', 2, 200);
INSERT INTO t1 VALUES ('t1', 3, 500);
INSERT INTO t1 VALUES ('t1', 4, 600);
INSERT INTO t1 VALUES ('t1', 5, 800);
INSERT INTO t1 VALUES ('t1', 6, 900);
Result:
SELECT * FROM t2;
+------+------+------+
| name | time | b |
+------+------+------+
| t1 | 2 | 100 |
| t1 | 3 | 300 |
| t1 | 4 | 100 |
| t1 | 5 | 200 |
| t1 | 6 | 100 |
+------+------+------+
5 rows in set (0.00 sec)
there is im mysql insert ... select
INSERT INTO table ( fields )
SELECT fields FROM table;