SQL selection of last period - mysql

I have a table like this:
<table border="1" cellspacing="0" cellpadding="5">
<tr><td>ID</td><td>status</td><td>start_date</td><td>end_date</td></tr>
<tr><td>1</td><td>A</td><td>2015-01-01</td><td>2015-12-31</td></tr>
<tr><td>1</td><td>B</td><td>2016-01-01</td><td>NULL</td></tr>
<tr><td>2</td><td>C</td><td>NULL</td><td>2016-12-31</td></tr>
<tr><td>3</td><td>D</td><td>NULL</td><td>NULL</td></tr>
</table>
I must do a trigger in MySQL to update a main table with the last status of a subject.
To do this I must select for every ID the last status based on the period between start_date and end_date that can be NULL if didn't known.
I write this SQL statement test:
SELECT *
FROM My_Table AS T
WHERE T.start_date Is Null AND
T.end_date In(SELECT MAX(end_date) FROM My_Table WHERE ID = T.ID)
OR
T.start_date In(SELECT MAX(start_date) FROM My_Table WHERE ID = T.ID) AND
T.end_date Is Null
OR
T.start_date In(SELECT MAX(start_date) FROM My_Table WHERE ID = T.ID) AND
T.end_date In(SELECT MAX(end_date) FROM My_Table WHERE ID = T.ID
);
Is there a shorten method to do this?
In practice I want a WHERE clause that catch each of these cases (a = start_date, b=end_date):
a = MAX AND b = MAX OR
a = NULL AND b = MAX OR
a = MAX AND b = NULL OR
a = NULL AND b = NULL

Here's a slightly simplified query which returns exactly the same result set as the query in your question. It uses only two subqueries instead of four (no duplicated subqueries):
SELECT *
FROM My_Table AS T
WHERE
(T.start_date Is Null OR T.start_date = (SELECT MAX(start_date) FROM My_Table WHERE ID = T.ID)) AND
(T.end_date Is Null OR T.end_date = (SELECT MAX(end_date) FROM My_Table WHERE ID = T.ID)) AND
(T.start_date Is NOT Null OR T.end_date Is NOT Null)
;
Note that it does NOT include the a = NULL AND b = NULL case. To include it as well, the last AND-clause must be removed, i.e.:
SELECT *
FROM My_Table AS T
WHERE
(T.start_date Is Null OR T.start_date = (SELECT MAX(start_date) FROM My_Table WHERE ID = T.ID)) AND
(T.end_date Is Null OR T.end_date = (SELECT MAX(end_date) FROM My_Table WHERE ID = T.ID))
;

Related

Getting ERROR 1093 (HY000) using subquery

I have the following table :
--------------------------------------------------------------------------------
| id | pack_id | user_id | start_date | end_date | runtime |
--------------------------------------------------------------------------------
| 1 | 52 | 9 | 2019-04-09 11:53:00 | 2019-04-09 11:54:00 | 60 |
| 2 | 52 | 9 | 2019-04-09 11:58:00 | NULL | NULL |
I would like to update the end_date and runtime of the last id of the same pack_id and user_id.
end_date have to take the current datetime.
Here is my query :
UPDATE
myTable
SET
end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
WHERE
id = (
SELECT
max(id)
FROM
myTable
WHERE
user_id = '9'
AND
pack_id = '52'
)
And I get the following error :
ERROR 1093 (HY000) at line 1: You can't specify target table
'myTable' for update in FROM clause
You can't do this in MySQL, so use a JOIN instead:
UPDATE myTable t JOIN
(SELECT max(id) as max_id
FROM myTable
WHERE user_id = '9' AND pack_id = '52'
) tt
ON t.id = tt.max_id
SET t.end_date = NOW(),
t.runtime = TIMESTAMPDIFF(SECOND, t.start_date, NOW()) ;
use subquery deeper into a from clause
UPDATE
myTable
SET
end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
WHERE
id = (
select id from (
SELECT
max(id) as id
FROM
myTable
WHERE
user_id = '9'
AND
pack_id = '52'
) a
)
You need a subquery for create a temp set of value and avoid the use of the same table for updated and select at same level.
You can do thi using a nested select eg:
UPDATE myTable
SET end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())
WHERE id = (
select max_id from (
SELECT
max(id) max_id
FROM myTable
WHERE user_id = '9'
AND pack_id = '52'
) t )
or an inner join on the subquery
UPDATE myTable m
INNER JOIN (
SELECT
max(id) max_id
FROM myTable
WHERE user_id = '9'
AND pack_id = '52'
) t ON t.max_id = m.id
SET end_date = NOW(),
runtime = TIMESTAMPDIFF(SECOND, start_date, NOW())

SQL join table lower date and lower id

I have got the following two tables
START AND REPEAT
START
INSPECID=======SCORE
1--------------3
2--------------1
3--------------4
REPEAT
ID========INSPECID========SCORE========DATE
1---------1---------------9------------12/01/2016
2---------1---------------1------------11/01/2016
3---------2---------------2------------29/01/2016
4---------2---------------4------------01/01/2016
5---------2---------------3------------22/01/2016
6---------2---------------5------------02/01/2016
7---------2---------------1------------11/01/2016
8---------2---------------1------------01/01/2016
9---------3---------------1------------02/01/2016
10--------3---------------2------------09/01/2016
I am expecting as below
INCREASED------1
DECREASED------2
EQUAL----------0
Rules
1) Join tables by INSPECID
2) When more than 1 INSPECID is found in REPEAT table consider the score from the lower date.
3) when both INSPECID is matched and date is matched than consider the lower ID in the REPEAT table, so ID 4 and ID 8 has same date and same INPECTID but consider the ID 4 score which is 4.
Do a self join with REPEAT table to pick the oldest row
select s.*,a.*
from `START` s
join `REPEAT` a on s.INSPECID = a.INSPECID
left join `REPEAT` b on a.INSPECID = b.INSPECID
and case when a.DATE = b.DATE
then a.ID > b.ID
else a.DATE > b.DATE
end
where b.INSPECID is null
For conflict when INSPECID and DATE is same use CASE to choose row with lowest ID
Demo
Updated for desired result set
select t.result,count(t1.result) cnt
from (
select 'Increased' result
union
select 'Decreased' result
union
select 'Equal' result
) t
left join (
select s.score,a.id,a.DATE,
case when s.SCORE > a.SCORE
then 'Increased'
when s.SCORE < a.SCORE
then 'Decreased'
else 'Equal'
end result
from `START` s
join `REPEAT` a on s.INSPECID = a.INSPECID
left join `REPEAT` b on a.INSPECID = b.INSPECID
and case when a.DATE = b.DATE
then a.ID > b.ID
else a.DATE > b.DATE
end
where b.INSPECID is null
) t1 using(result)
group by t.result
Demo
This is a bit tricky. The following uses the group_concat() trick for calculating the first and last scores. It then puts these into the categories that you want:
select w.which, count(r.INSPECID)
from (select 'DECREASING' as which union all
select 'INCREASING' as which union all
select 'EQUAL' as which
) w left join
(select r.INSPECID,
(substring_index(group_concat(score order by date), ',', 1) + 0) as first_score,
(substring_index(group_concat(score order by date desc), ',', 1) + 1) as last_score
from repeat r
group by INSPECID
) r
ON (last_score > first_score and w.which = 'INCREASING') or
(last_score < first_score and w.which = 'DECREASING') or
(last_score = first_score and w.which = 'INCREASING')
group by w.which;
Note that the first table is not necessary.

Sql server errors in query

I am trying to collaborate 3 queries to perform arithmetic operation. The queries are shown in
(SELECT ITEM_ID,ISNULL(SUM(REC_GOOD_QTY),0)
FROM INVENTORY_ITEM
WHERE COMPANY_ID = 1
AND INVENTORY_ITEM.COMPANY_BRANCH_ID = 1
AND INVENTORY_ITEM.INV_ITEM_STATUS = 'Inward'
AND GRN_DATE < CAST('2017-01-10 00:00:00.0' AS DATETIME)
GROUP BY INVENTORY_ITEM.ITEM_ID) -
(SELECT ITEM_ID, SUM ( TOTAL_LITRE )
FROM STOCK_REQUISITION_ITEM B, STOCK_REQUISITION A
WHERE A.ID = B.REQUISITION_ID
AND A.COMPANY_ID = 1
AND A.REQ_FROM_BRANCH_ID = 1
AND A.REQUISITION_DATE < CAST('2017-01-10 00:00:00.0' AS DATETIME)
GROUP BY B.ITEM_ID) +
(SELECT ITEM_ID, SUM ( RETURN_QUANTITY )
FROM STOCK_RETURN_ITEM B, STOCK_RETURN A
WHERE A.ID = B.STOCK_RETURN_ID
AND A.COMPANY_ID = 1
AND A.COMPANY_BRANCH_ID = 1
AND A.RETURN_DATE <= CAST('2017-01-10 00:00:00.0' AS DATETIME)
GROUP BY B.ITEM_ID)
I am getting this error.
[Err] 42000 - [SQL Server]Incorrect syntax near '-'.
42000 - [SQL Server]Incorrect syntax near '+'
Not much to go on here for details. And we aren't actually sure if you are using mysql or sql server but pretty sure you are using sql server. I think you can accomplish what you are trying to do with something along these lines.
with Iventory as
(
SELECT i.ITEM_ID
, GoodQty = ISNULL(SUM(i.REC_GOOD_QTY), 0)
FROM INVENTORY_ITEM i
WHERE COMPANY_ID = 1
AND i.COMPANY_BRANCH_ID = 1
AND i.INV_ITEM_STATUS = 'Inward'
AND i.GRN_DATE < '2017-01-10'
GROUP BY i.ITEM_ID
)
, StockRequisition as
(
SELECT ITEM_ID
, TotalLitre = SUM(TOTAL_LITRE)
FROM STOCK_REQUISITION_ITEM B
JOIN STOCK_REQUISITION A ON A.ID = B.REQUISITION_ID
WHERE A.COMPANY_ID = 1
AND A.REQ_FROM_BRANCH_ID = 1
AND A.REQUISITION_DATE < '2017-01-10'
GROUP BY B.ITEM_ID
)
StockReturn as
(
SELECT ITEM_ID
, ReturnQuantity = SUM(RETURN_QUANTITY)
FROM STOCK_RETURN_ITEM B
JOIN STOCK_RETURN A ON A.ID = B.STOCK_RETURN_ID
WHERE A.COMPANY_ID = 1
AND A.COMPANY_BRANCH_ID = 1
AND A.RETURN_DATE <= '2017-01-10'
GROUP BY B.ITEM_ID
)
select i.ITEM_ID
, MyCalculation = i.GoodQty - isnull(Req.TotalLitre, 0) + isnull(sr.ReturnQuantity, 0)
from Inventory i
left join StockRequisition sr on sr.ITEM_ID = i.ITEM_ID
left join StockReturn Req on Req.ITEM_ID = i.ITEM_ID
In your queries you always returns two fields ITEM_ID and a numeric field.
To apply an arithmetical operation you must return one numeric field
The first query:
SELECT ITEM_ID,ISNULL(SUM(REC_GOOD_QTY),0)
becomes
SELECT ISNULL(SUM(REC_GOOD_QTY),0)
The second query:
SELECT ITEM_ID, SUM ( TOTAL_LITRE )
becomes
SELECT SUM ( TOTAL_LITRE )
The third query:
SELECT ITEM_ID, SUM ( RETURN_QUANTITY )
becomes
SELECT SUM ( RETURN_QUANTITY )
So the GROUP BY returns more than one row per query
UPDATE
i try to rewrite your query:
SELECT DISTINCT ii.item_id,
ISNULL(
(SELECT SUM(ii2.rec_good_qty)
FROM inventory_item ii2
WHERE ii2.item_id = ii.item_id
AND ii.company_id = 1
AND ii.company_branch_id = 1
AND ii.inv_item_status = 'Inward'
AND ii.grn_date < CAST('2017-01-10 00:00:00.0' AS DATETIME))
,0) -
ISNULL(
(SELECT SUM(total_litre)
FROM stock_requisition_item b
JOIN stock_requisition a
ON a.id = b.requisition_id
WHERE a.company_id = 1
AND a.req_from_branch_id = 1
AND a.requisition_date < CAST('2017-01-10 00:00:00.0' AS DATETIME))
,0) +
ISNULL(
(SELECT SUM(return_quantity)
FROM stock_return_item b
JOIN stock_return a
ON a.id = b.stock_return_id
WHERE a.company_id = 1
AND a.company_branch_id = 1
AND a.return_date <= CAST('2017-01-10 00:00:00.0' AS DATETIME))
,0) AS result
FROM inventory_item ii
WHERE ii.company_id = 1
AND ii.company_branch_id = 1
AND ii.inv_item_status = 'Inward'
AND ii.grn_date < CAST('2017-01-10 00:00:00.0' AS DATETIME)

Multiple not exists in MYSQL to check count of rows

I am trying to make this query work, here is my logic , I am trying to select record thats there is not at LEAST NOT one of these options:
1). status = Approved AND end_date >now
2). status =Approved AND end_date is NULL
3). status = Pending And end_date is NULL & COUNT(id) =1
4). status =Pending AND end_date >now & COUNT(id) =1
5). status =Pending AND end_date <now & COUNT(id) =1
how to accomplish this , this is what I have so far I am not sure how to check for COUNT(id) = 1 in conditions 3, 4, and 5?
SELECT
*
FROM
outreach
WHERE
NOT EXISTS (
SELECT
*
FROM
outreach_links
WHERE
outreach_links.outreach_id = outreach.id
AND STATUS = "Approved"
AND end_date > now()
)
AND NOT EXISTS (
SELECT
*
FROM
outreach_links
WHERE
outreach_links.outreach_id = outreach.id
AND STATUS = "Approved"
AND end_date IS NULL
)
AND NOT EXISTS (
SELECT
*
FROM
outreach_links
WHERE
outreach_links.outreach_id = outreach.id
AND STATUS = "Pending"
AND end_date > now()
)
AND NOT EXISTS (
SELECT
*
FROM
outreach_links
WHERE
outreach_links.outreach_id = outreach.id
AND STATUS = "Pending"
AND end_date IS NULL
)
AND NOT EXISTS (
SELECT
*
FROM
outreach_links
WHERE
outreach_links.outreach_id = outreach.id
AND STATUS = "Pending"
AND end_date < now()
)
select
o.*,
SUM(if(ol.status = "Approved" and (ol.end_date > now() or end_date is null), 1, 0)) as cond1,
SUM(if(ol.status = "Pending" and (ol.end_date != now() or end_date is null), 1, 0)) as cond2
from
outreach o
left join
outreach_links ol on ol.outreach_id = o.id
group by
o.id
having
cond1 = 0 and cond2 != 1
;
Not sure if that's you are looking for, but you can try it.
cond2 != 1 makes your COUNT(id) = 1 condition (if there are more than one linked id in outreach_links cond2 will be greater than 1)

Sum of two counts from different table with different conditions

Basically I need to merge these into one single query:
SELECT COUNT( DISTINCT id ) AS totalRows1
FROM other_events WHERE status = "approved"
AND Location = 1
SELECT COUNT( DISTINCT Id ) AS totalRows2
FROM core_events WHERE Status = "Active"
AND Location_id = 1
When I do it like below, if there is no event with Location_id = 1 query returns 0. In that condition I need it to return the count of the first table only.
SELECT COUNT( DISTINCT t1.id ) + COUNT( DISTINCT t2.Id ) AS total
FROM other_events AS t1, core_events AS t2
WHERE t1.status = "approved"
AND t1.Location = 1
AND t2.Location_id = 1
AND t2.Status = 'Active'
ps. column names are exactly like above
Use a UNION statement to merge the result like this:
SELECT SUM(total) FROM (
SELECT COUNT(DISTINCT id) AS total
FROM other_events
WHERE (status = "approved" AND Location = 1)
UNION ALL
SELECT COUNT(DISTINCT Id) AS total
FROM core_events
WHERE (Location_id = 1 AND Status = 'Active')
) union_result