I need help to build a SQL query to get the data for projects created in 2020. I have two tables :
Project table :
ProjectID
ProjectName
Type
1
Project1
A
2
Project2
A
3
Project3
C
4
Project4
B
Table Task :
ProjectID
TaskID
Type
Date
1
1
TA
2020-1-1
1
4
TA
2019-12-15
1
2
TB
2020-5-1
2
3
TB
2021-1-1
1
5
TB
2020-10-1
2
6
TA
2020-3-14
3
7
TB
2020-10-1
The start date of a project equals the date of the first task (without consideration of type). I need details of the tasks (TB type) of the projects started in or after 2020.
Result :
ProjectID
TaskID
Type
Date
3
7
TB
2020-10-1
2
3
TB
2021-1-1
I have tried
SELECT
*
FROM
task t
LEFT JOIN project p ON p.projectID = t.projectID
WHERE
t.type = 'TB' year(
min(t.date)
) >= '2020'
Thank you in advance for your help
you can use HAVING with a subquery and an Group function
CREATE TABLE TASKS
(`ProjectID` int, `TaskID` int, `Type` varchar(2), `Date` varchar(10))
;
INSERT INTO TASKS
(`ProjectID`, `TaskID`, `Type`, `Date`)
VALUES
(1, 1, 'TA', '2020-1-1'),
(1, 4, 'TA', '2019-12-15'),
(1, 2, 'TB', '2020-5-1'),
(2, 3, 'TB', '2021-1-1'),
(1, 5, 'TB', '2020-10-1'),
(2, 6, 'TA', '2020-3-14'),
(3, 7, 'TB', '2020-10-1')
;
CREATE TABLE PROJECTS
(`ProjectID` int, `ProjectName` varchar(8), `Type` varchar(1))
;
INSERT INTO PROJECTS
(`ProjectID`, `ProjectName`, `Type`)
VALUES
(1, 'Project1', 'A'),
(2, 'Project2', 'A'),
(3, 'Project3', 'C'),
(4, 'Project4', 'B')
;
SELECT
t.*
FROM
TASKS t
LEFT JOIN PROJECTS p ON p.ProjectID = t.ProjectID
WHERE
t.Type = 'TB'
HAVING (SELECT MIN(year(t.`Date`)) FROM TASKS t WHERE t.ProjectID = p.ProjectID) >= 2020
ProjectID | TaskID | Type | Date
--------: | -----: | :--- | :--------
2 | 3 | TB | 2021-1-1
3 | 7 | TB | 2020-10-1
db<>fiddle here
The min date needs to be grouped. You don't need the project table at all to just get tasks. For projects:
SELECT *
FROM Project p
WHERE p.projectID IN (SELECT t.projectID FROM task t WHERE t.type = 'TB' AND year(min(t.date)) >= '2020' GROUP BY t.projectID)
And for tasks:
SELECT *
FROM task p
WHERE p.projectID IN (SELECT t.projectID FROM task t WHERE t.type = 'TB' AND year(min(t.date)) >= '2020' GROUP BY t.projectID)
Try that and see how it goes.
i have this query
select a.*,
GROUP_CONCAT(b.`filename`) as `filesnames`
from `school_classes` a
join classes_data.`classes_albums` b on a.`school_key` = b.`school_key`
and a.`class_key` = b.`class_key`
group by a.`ID`
the result of this query is
i want to add to it
ORDER BY b.added_date DESC LIMIT 2
so the output of filenames column only shows latest 2 files , ?
It is not clear from your question what your tables look like and how they relate but this might be what you are after.
drop table if exists school_classes;
create table school_classes (id int,school_key int, class_key int);
drop table if exists classes_albums;
create table classes_albums(id int,school_key int, class_key int, filename varchar(3),dateadded date);
insert into school_classes values
(1,1,1), (2,1,2),(3,1,3)
insert into classes_albums values
(1,1,1,'a','2017-01-01'),(1,1,1,'b','2017-02-01'),(1,1,1,'c','2017-03-01');
select a.*, b.filenames
from school_classes a
join
(
select c.school_key,c.class_key,group_concat(c.filename order by c.rn desc) filenames
from
(
select c.*,
if(concat(c.school_key,c.class_key) <> #p, #rn:=1, #rn:=#rn+1) rn,
#p:=concat(c.school_key,c.class_key) p
from classes_albums c, (select #rn:=0, #ck:=0,#sk:=0) rn
order by c.school_key,c.class_key, c.dateadded desc
) c
where c.rn < 3
group by c.school_key,c.class_key
) b on b.school_key = a.school_key and b.class_key = a.class_key
+------+------------+-----------+-----------+
| id | school_key | class_key | filenames |
+------+------------+-----------+-----------+
| 1 | 1 | 1 | b,c |
+------+------------+-----------+-----------+
1 row in set (0.02 sec)
I am trying to update (reference) a column (oid) of one table with OID of another table's column with certain condition.
Example :
Customer Table :
------------------
CID name oid
-------------------
1 abc null
2 abc null
3 abc null
4 xyz null
--------------------
Order Table
--------------
OID name
--------------
10 abc
11 abc
12 abc
13 xyz
--------------
Ouput should be :
Customer Table :
------------------
CID name oid
-------------------
1 abc 10
2 abc 11
3 abc 12
4 xyz 13
--------------------
I have tried the following
UPDATE customer as c, order as o
SET c.oid = o.OID
WHERE c.name = o.name;
-----------------------------
update customer INNER JOIN order on customer.name=Order.name
SET customer.oid=Order.OID
where customer.oid IS null;
But the customer table is being updated as follows
Customer Table :
------------------
CID name oid
-------------------
1 abc 10
2 abc 10
3 abc 10
4 xyz 13
--------------------
The idea is to assign a row number to each of the entries in Customer table and Order table.
Thus when making an inner join between these two tables you have two conditions right now (whereas previously it was one i.e. only name).
One condition is name and another one is the row_number
You can go with this query:
UPDATE Customer CT
INNER JOIN (
SELECT
customerTable.CID,
orderTable.OID FROM
(
SELECT
*,
#rn1 := #rn1 + 1 AS row_number
FROM
Customer C
CROSS JOIN (SELECT #rn1 := 0) var
ORDER BY CID
) AS customerTable
INNER JOIN (
SELECT
*,
#rn2 := #rn2 + 1 AS row_number
FROM
`Order` O
CROSS JOIN (SELECT #rn2 := 0) var
ORDER BY OID
) AS orderTable ON customerTable. NAME = orderTable. NAME
AND customerTable.row_number = orderTable.row_number
) AS combinedTable ON CT.CID = combinedTable.CID
SET CT.oid = combinedTable.OID
Note: Since joining these two tables on matching name is not sufficient for what are you looking for. That's why besides matching name assign a row_number to each of the rows (both in Customer and Order table. Then make an inner join between these two tables on matching name and row number. Thus you are restricting one entry to be joined multiple times with other entries from another table.
TEST SCHEMA & DATA:
Couldn't add an sql fiddle
DROP TABLE IF EXISTS `customer`;
CREATE TABLE `customer` (
`CID` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`oid` int(11) DEFAULT NULL,
PRIMARY KEY (`CID`)
);
INSERT INTO `customer` VALUES ('1', 'abc', null);
INSERT INTO `customer` VALUES ('2', 'abc', null);
INSERT INTO `customer` VALUES ('3', 'abc', null);
INSERT INTO `customer` VALUES ('4', 'xyz', null);
DROP TABLE IF EXISTS `order`;
CREATE TABLE `order` (
`OID` int(11) NOT NULL,
`name` varchar(100) NOT NULL
);
INSERT INTO `order` VALUES ('10', 'abc');
INSERT INTO `order` VALUES ('11', 'abc');
INSERT INTO `order` VALUES ('12', 'abc');
INSERT INTO `order` VALUES ('13', 'xyz');
See now, how does the Customer table look like:
SELECT
*
FROM Customer;
Output:
CID name oid
1 abc 10
2 abc 11
3 abc 12
4 xyz 13
This is very complicated to do. You need to assign a counter variable to each value -- which is a bit painful in an update statement. But something like this should work:
update customer c join
(select c.*,
(#rn := if(#n = name, #rn + 1,
if(#n := name, 1, 1)
)
) as rn
from customer c cross join
(select #n := '', #rn := 0) params
order by name, cid
) cc
on c.cid = cc.cid join
(select o.*,
(#rno := if(#no = name, #rno + 1,
if(#no := name, 1, 1)
)
) as rn
from orders o cross join
(select #no := ', #rno := 0) params
) o
on c.name = o.name and c.rn = o.rn
set c.oid = o.oid;
I am trying to get the avg of an item so I am using a subquery.
Update: I should have been clearer initially, but i want the avg to be for the last 5 items only
First I started with
SELECT
y.id
FROM (
SELECT *
FROM (
SELECT *
FROM products
WHERE itemid=1
) x
ORDER BY id DESC
LIMIT 15
) y;
Which runs but is fairly useless as it just shows me the ids.
I then added in the below
SELECT
y.id,
(SELECT AVG(deposit) FROM (SELECT deposit FROM products WHERE id < y.id ORDER BY id DESC LIMIT 5)z) AVGDEPOSIT
FROM (
SELECT *
FROM (
SELECT *
FROM products
WHERE itemid=1
) x
ORDER BY id DESC
LIMIT 15
) y;
When I do this I get the error Unknown column 'y.id' in 'where clause', upon further reading here I believe this is because when the queries go down to the next level they need to be joined?
So I tried the below ** removed un needed suquery
SELECT
y.id,
(SELECT AVG(deposit) FROM (
SELECT deposit
FROM products
INNER JOIN y as yy ON products.id = yy.id
WHERE id < yy.id
ORDER BY id DESC
LIMIT 5)z
) AVGDEPOSIT
FROM (
SELECT *
FROM products
WHERE itemid=1
ORDER BY id DESC
LIMIT 15
) y;
But I get Table 'test.y' doesn't exist. Am I on the right track here? What do I need to change to get what I am after here?
The example can be found here in sqlfiddle.
CREATE TABLE products
(`id` int, `itemid` int, `deposit` int);
INSERT INTO products
(`id`, `itemid`, `deposit`)
VALUES
(1, 1, 50),
(2, 1, 75),
(3, 1, 90),
(4, 1, 80),
(5, 1, 100),
(6, 1, 75),
(7, 1, 75),
(8, 1, 90),
(9, 1, 90),
(10, 1, 100);
Given my data in this example, my expected result is below, where there is a column next to each ID that has the avg of the previous 5 deposits.
id | AVGDEPOSIT
10 | 86 (deposit value of (id9+id8+id7+id6+id5)/5) to get the AVG
9 | 84
8 | 84
7 | 84
6 | 79
5 | 73.75
I'm not an MySQL expert (in MS SQL it could be done easier), and your question looks a bit unclear for me, but it looks like you're trying to get average of previous 5 items.
If you have Id without gaps, it's easy:
select
p.id,
(
select avg(t.deposit)
from products as t
where t.itemid = 1 and t.id >= p.id - 5 and t.id < p.id
) as avgdeposit
from products as p
where p.itemid = 1
order by p.id desc
limit 15
If not, then I've tri tried to do this query like this
select
p.id,
(
select avg(t.deposit)
from (
select tt.deposit
from products as tt
where tt.itemid = 1 and tt.id < p.id
order by tt.id desc
limit 5
) as t
) as avgdeposit
from products as p
where p.itemid = 1
order by p.id desc
limit 15
But I've got exception Unknown column 'p.id' in 'where clause'. Looks like MySQL cannot handle 2 levels of nesting of subqueries.
But you can get 5 previous items with offset, like this:
select
p.id,
(
select avg(t.deposit)
from products as t
where t.itemid = 1 and t.id > coalesce(p.prev_id, -1) and t.id < p.id
) as avgdeposit
from
(
select
p.id,
(
select tt.id
from products as tt
where tt.itemid = 1 and tt.id <= p.id
order by tt.id desc
limit 1 offset 6
) as prev_id
from products as p
where p.itemid = 1
order by p.id desc
limit 15
) as p
sql fiddle demo
This is my solution. It is easy to understand how it works, but at the same time it can't be optimized much since I'm using some string functions, and it's far from standard SQL. If you only need to return a few records, it could be still fine.
This query will return, for every ID, a comma separated list of previous ID, ordered in ascending order:
SELECT p1.id, p1.itemid, GROUP_CONCAT(p2.id ORDER BY p2.id DESC) previous_ids
FROM
products p1 LEFT JOIN products p2
ON p1.itemid=p2.itemid AND p1.id>p2.id
GROUP BY
p1.id, p1.itemid
ORDER BY
p1.itemid ASC, p1.id DESC
and it will return something like this:
| ID | ITEMID | PREVIOUS_IDS |
|----|--------|-------------------|
| 10 | 1 | 9,8,7,6,5,4,3,2,1 |
| 9 | 1 | 8,7,6,5,4,3,2,1 |
| 8 | 1 | 7,6,5,4,3,2,1 |
| 7 | 1 | 6,5,4,3,2,1 |
| 6 | 1 | 5,4,3,2,1 |
| 5 | 1 | 4,3,2,1 |
| 4 | 1 | 3,2,1 |
| 3 | 1 | 2,1 |
| 2 | 1 | 1 |
| 1 | 1 | (null) |
then we can join the result of this query with the products table itself, and on the join condition we can use FIND_IN_SET(src, csvalues) that return the position of the src string inside the comma separated values:
ON FIND_IN_SET(id, previous_ids) BETWEEN 1 AND 5
and the final query looks like this:
SELECT
list_previous.id,
AVG(products.deposit)
FROM (
SELECT p1.id, p1.itemid, GROUP_CONCAT(p2.id ORDER BY p2.id DESC) previous_ids
FROM
products p1 INNER JOIN products p2
ON p1.itemid=p2.itemid AND p1.id>p2.id
GROUP BY
p1.id, p1.itemid
) list_previous LEFT JOIN products
ON list_previous.itemid=products.itemid
AND FIND_IN_SET(products.id, previous_ids) BETWEEN 1 AND 5
GROUP BY
list_previous.id
ORDER BY
id DESC
Please see fiddle here. I won't recommend using this trick for big tables, but for small sets of data it is fine.
This is maybe not the simplest solution, but it does do the job and is an interesting variation and in my opinion transparent. I simulate the analytical functions that I know from Oracle.
As we do not assume the id to be consecutive the counting of the rows is simulated by increasing #rn each row. Next products table including the rownum is joint with itself and only the rows 2-6 are used to build the average.
select p2id, avg(deposit), group_concat(p1id order by p1id desc), group_concat(deposit order by p1id desc)
from ( select p2.id p2id, p1.rn p1rn, p1.deposit, p2.rn p2rn, p1.id p1id
from (select p.*,#rn1:=#rn1+1 as rn from products p,(select #rn1 := 0) r) p1
, (select p.*,#rn2:=#rn2+1 as rn from products p,(select #rn2 := 0) r) p2 ) r
where p2rn-p1rn between 1 and 5
group by p2id
order by p2id desc
;
Result:
+------+--------------+---------------------------------------+------------------------------------------+
| p2id | avg(deposit) | group_concat(p1id order by p1id desc) | group_concat(deposit order by p1id desc) |
+------+--------------+---------------------------------------+------------------------------------------+
| 10 | 86.0000 | 9,8,7,6,5 | 90,90,75,75,100 |
| 9 | 84.0000 | 8,7,6,5,4 | 90,75,75,100,80 |
| 8 | 84.0000 | 7,6,5,4,3 | 75,75,100,80,90 |
| 7 | 84.0000 | 6,5,4,3,2 | 75,100,80,90,75 |
| 6 | 79.0000 | 5,4,3,2,1 | 100,80,90,75,50 |
| 5 | 73.7500 | 4,3,2,1 | 80,90,75,50 |
| 4 | 71.6667 | 3,2,1 | 90,75,50 |
| 3 | 62.5000 | 2,1 | 75,50 |
| 2 | 50.0000 | 1 | 50 |
+------+--------------+---------------------------------------+------------------------------------------+
SQL Fiddle Demo: http://sqlfiddle.com/#!2/c13bc/129
I want to thank this answer on how to simulate analytical functions in mysql: MySQL get row position in ORDER BY
It looks like you just want:
SELECT
id,
(SELECT AVG(deposit)
FROM (
SELECT deposit
FROM products
ORDER BY id DESC
LIMIT 5) last5
) avgdeposit
FROM products
The inner query gets the last 5 rows added to product, the query that wraps that gets the average for their deposits.
I'm going to simplify your query a bit so I can explain it.
SELECT
y.id,
(
SELECT AVG(deposit) FROM
(
SELECT deposit
FROM products
LIMIT 5
) z
) AVGDEPOSIT
FROM
(
SELECT *
FROM
(
SELECT *
FROM products
) x
LIMIT 15
) y;
My guess would be that you just need to insert some AS keywords in there. I'm sure someone else will come up with something more elegant, but for now you can try it out.
SELECT
y.id,
(
SELECT AVG(deposit) FROM
(
SELECT deposit
FROM products
LIMIT 5
) z
) AS AVGDEPOSIT
FROM
(
SELECT *
FROM
(
SELECT *
FROM products
) AS x
LIMIT 15
) y;
Here's one way to do it in MySQL:
SELECT p.id
, ( SELECT AVG(deposit)
FROM ( SELECT #rownum:=#rownum+1 rn, deposit, id
FROM ( SELECT #rownum:=0 ) r
, products
ORDER BY id ) t
WHERE rn BETWEEN p.rn-5 AND p.rn-1 ) avgdeposit
FROM ( SELECT #rownum1:=#rownum1+1 rn, id
FROM ( SELECT #rownum1:=0 ) r
, products
ORDER BY id ) p
WHERE p.rn >= 5
ORDER BY p.rn DESC;
It's a shame MySQL doesn't support the WITH clause or windowing functions. Having both would greatly simplify the query to the following:
WITH tbl AS (
SELECT id, deposit, ROW_NUMBER() OVER(ORDER BY id) rn
FROM products
)
SELECT id
, ( SELECT AVG(deposit)
FROM tbl
WHERE rn BETWEEN t.rn-5 AND t.rn-1 )
FROM tbl t
WHERE rn >= 5
ORDER BY rn DESC;
The latter query runs fine in Postgres.
2 possible solutions here
Firstly using user variables to add a sequence number. Do this twice, and join the second set to the first where the sequence number is between the id - 1 and the id - 5. Then just use AVG. No correlated sub queries.
SELECT Sub3.id, Sub3.itemid, Sub3.deposit, AVG(Sub4.deposit)
FROM
(
SELECT Sub1.id, Sub1.itemid, Sub1.deposit, #Seq:=#Seq+1 AS Sequence
FROM
(
SELECT id, itemid, deposit
FROM products
ORDER BY id DESC
) Sub1
CROSS JOIN
(
SELECT #Seq:=0
) Sub2
) Sub3
LEFT OUTER JOIN
(
SELECT Sub1.id, Sub1.itemid, Sub1.deposit, #Seq1:=#Seq1+1 AS Sequence
FROM
(
SELECT id, itemid, deposit
FROM products
ORDER BY id DESC
) Sub1
CROSS JOIN
(
SELECT #Seq1:=0
) Sub2
) Sub4
ON Sub4.Sequence BETWEEN Sub3.Sequence + 1 AND Sub3.Sequence + 5
GROUP BY Sub3.id, Sub3.itemid, Sub3.deposit
ORDER BY Sub3.id DESC
Second one is cruder, and uses a correlated sub query (which is likely to perform poorly as the amount of data increases). Does a normal select but for the last column it has a sub query that refers to the id in the main select.
SELECT id, itemid, deposit, (SELECT AVG(P2.deposit) FROM products P2 WHERE P2.id BETWEEN P1.id - 5 AND p1.id - 1 ORDER BY id DESC LIMIT 5)
FROM products P1
ORDER BY id DESC
Is this what you are after?
SELECT m.id
, AVG(d.deposit)
FROM products m
, products d
WHERE d.id < m.id
AND d.id >= m.id - 5
GROUP BY m.id
ORDER BY m.id DESC
;
But can't be that simple. Firstly, the table cannot just contain one itemid (hence your WHERE clause); Second, the id cannot be sequential/without gaps within an itemid. Thirdly, you probably want to produce something that runs across itemid and not one itemid at a time. So here it is.
SELECT itemid
, m_id as id
, AVG(d.deposit) as deposit
FROM (
SELECT itemid
, m_id
, d_id
, d.deposit
, #seq := (CASE WHEN m_id = d_id THEN 0 ELSE #seq + 1 END) seq
FROM (
SELECT m.itemid
, m.id m_id
, d.id d_id
, d.deposit
FROM products m
, products d
WHERE m.itemid = d.itemid
AND d.id <= m.id
ORDER BY m.id DESC
, d.id DESC) d
, (SELECT #seq := 0) s
) d
WHERE seq BETWEEN 1 AND 5
GROUP BY itemid
, m_id
ORDER BY itemid
, m_id DESC
;
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