Get previous distinct number - sql

I've got the following table:
id date status
1 2017-04-20 good
1 2017-04-19 bad
1 2017-04-18 bad
2 2017-04-20 ok
2 2017-04-19 ok
2 2017-04-17 ok
2 2017-04-16 bad
What I need is to get the previous distinct value in status column per id.
Assuming that today is 2017-04-20, the result would look like this:
id previous_status
1 bad
2 bad
Afterwards, I wanna use this information in the case statement:
Case when status = 'good' and previous_status = 'bad', ....

You could you this for your first requirement:
SELECT id, status AS previous_status
FROM table_name
WHERE (id, date_col) IN (
SELECT id, MAX(date_col)
FROM table_name
WHERE DATE(date_col) < "2017-04-20"
);
And this is for second requirement using CASE statement
SELECT t.id, t.status, t1.previous_status,
CASE
WHEN t.status = 'good' AND t1.previous_status = 'bad' THEN 'some_value'
ELSE 'other_value'
END AS case_col
FROM table_name t
INNER JOIN (
SELECT id, status AS previous_status
FROM table_name
WHERE (id, date_col) IN (
SELECT id, MAX(date_col)
FROM table_name
WHERE DATE(date_col) < "2017-04-20"
)
) t1
ON t.id = t1.id
WHERE DATE(t.date_col) = "2017-04-20";
This refer how_to_compare_date_google_big_query

Related

SQL to return 1 or 0 depending on values in a column's audit trail

If I were to have a table such as the one below:
id_
last_updated_by
1
robot
1
human
1
robot
2
robot
3
robot
3
human
Using SQL, how could I group by the ID and create a new column to indicate whether a human has ever updated the record like this:
id_
last_updated_by
updated_by_human
1
robot
1
2
robot
0
3
robot
1
UPDATE
I'm currently doing the following, though I'm not sure how efficient this is. Selecting the latest record and then merging it with my calculated column via a sub-select.
SELECT MAIN.TRANSACTION_ID,
MAIN.CREATED_DATE
MAIN.CREATED_BY_USER_ID,
MAIN.OWNER_USER_ID,
STP.TOUCHED_BY_HUMAN
FROM (
SELECT TRANSACTION_ID,
CREATED_DATE
CREATED_BY_USER_ID_
OWNER_USER_ID_
FROM TABLE_NAME
WHERE CREATED_DATE >= CAST('{start_date} 00:00:00' AS TIMESTAMP)
AND CREATED_DATE <= CAST('{end_date} 23:59:59' AS TIMESTAMP)
QUALIFY row_number() OVER (partition by TRANSACTION_ID order by End_Dt desc) = 1
) MAIN
LEFT JOIN (
SELECT TRANSACTION_ID,
CASE
WHEN CREATED_BY_USER_ID IN ('ROBOT', 'MACHINE') OR
CREATED_BY_USER_ID LIKE 'N%' OR
CREATED_BY_USER_ID IS NULL
THEN 0
ELSE 1 END AS CREATED_BY_HUMAN,
CASE
WHEN OWNER_USER_ID IN ('ROBOT', 'MACHINE') OR
OWNER_USER_ID LIKE 'N%' OR
OWNER_USER_ID IS NULL
THEN 0
ELSE 1 END AS OWNED_BY_HUMAN,
CASE
WHEN CREATED_BY_HUMAN = 0 AND
OWNED_BY_HUMAN = 0
THEN 0
ELSE 1 END AS TOUCHED_BY_HUMAN_
FROM TABLE_NAME
WHERE CREATED_DATE >= CAST('{start_date} 00:00:00' AS TIMESTAMP)
AND CREATED_DATE <= CAST('{end_date} 23:59:59' AS TIMESTAMP)
QUALIFY row_number() OVER (partition by TRANSACTION_ID order by TOUCHED_BY_HUMAN_ desc) = 1
) STP
ON MAIN.TRANSACTION_ID = STP.TRANSACTION_ID
If I'm following your problem, then something like this should work.
SELECT
t.*
,CASE WHEN a.id IS NOT NULL THEN 1 ELSE 0 END AS updated_by_human
FROM table t
LEFT JOIN (SELECT DISTINCT id FROM table WHERE last_updated_by = 'human') a ON t.id = a.id
That takes care of the updated_by_human field, but if you also need to reduce the records in table (only keeping a subset) then you need more information to do that.
Exists clauses are usually not that performant but if your data isn't big this should work.
select id_,
IF (EXISTS (SELECT 1 FROM table_name t2 WHERE t2.last_updated_by = 'human' and t2.id_ = t1.id_), 1, 0) AS updated_by_human
from table_name t1;
here is another way
SELECT *
FROM table_name t1
GROUP BY ti.id_
HAVING COUNT(*) > 0
AND MAX(CASE t1.last_updated_by WHEN 'human' THEN 1 ELSE 0 END) = 1;
Since you didn't specified which column is used to determine this record is the newest record added by a given id, I assume that there will be a column to track the insert/modify timestamp (which is pretty standard table design), let's put it is last_updated_timestamp (if you don't have any, then I still insist you to have one as an auditing trail without timestamp does not make sense)
Given your table name is updating_trail
SELECT updating_trail.*, last_update_trail.modified_by_human
FROM updating_trail
INNER JOIN (
-- determine the id_, the lastest modified_timestamp, and a flag check to determine if there is any record with last_update_by is 'human' -> if yes then give 1
SELECT updating_trail.id_, MAX(last_update_timestamp) AS most_recent_update_ts, MAX(CASE WHEN updating_trail.last_updated_by = 'human' THEN 1 ELSE 0 END) AS modified_by_human
FROM updating_trail
GROUP BY updating_trail.id_
) last_update_trail
ON updating_trail.id_ = last_update_trail.id_ AND updating_trail.last_update_timestamp = last_update_trail.most_recent_update_ts;
Give
id_
last_updated_by
last_update_timestamp
modified_by_human
1
robot
2021-10-19T20:00:00.000Z
1
2
robot
2021-10-19T17:00:00.000Z
0
3
robot
2021-10-19T16:00:00.000Z
1
Check out this sample db fiddle I created for you
This is a 1:1 translation of your query to conditional aggregation:
SELECT TRANSACTION_ID,
CREATED_DATE,
CREATED_BY_USER_ID,
OWNER_USER_ID,
Max(CASE
WHEN CREATED_BY_USER_ID IN ('ROBOT', 'MACHINE') OR
CREATED_BY_USER_ID LIKE 'N%' OR
CREATED_BY_USER_ID IS NULL
THEN 0
ELSE 1
END) Over (PARTITION BY TRANSACTION_ID) AS CREATED_BY_HUMAN
FROM Table_Name
WHERE CREATED_DATE >= Cast('{start_date} 00:00:00' AS TIMESTAMP)
AND CREATED_DATE <= Cast('{end_date} 23:59:59' AS TIMESTAMP)
QUALIFY Row_Number() Over (PARTITION BY TRANSACTION_ID ORDER BY End_Dt DESC) = 1

Oracle SQL: Joining same table and getting desired output

I have a table like this
FILEID | FILENAME | STATUS
100 |Employee_06102016.txt |PASS
100 |Employee_06092016.txt |FAIL
100 |Employee_06092016.txt |MISS
101 |ABC_06092016.txt |PASS
I am reading a filename from file and passing to SQL. Lets say, i have only the file name 'Emplyee_06102016.txt' which is with PASS staus. With this, i need to join the same table and take the count of PASS and FAIL filenames which have same file id and should exclude the MISS status.
I am trying something like this below but gives count as 3 including all. I should get only 2.
SELECT COUNT (T.FILEID) FROM TABLE_NAME T, TABLE_NAME S
WHERE T.FILEID=S.FILEID
AND T.FILENAME = 'Employee_06102016.txt' AND T.STATUS IN ('PASS', 'FAIL');
Oracle Setup:
CREATE TABLE table_name ( FILEID, FILENAME, STATUS ) AS
SELECT 100, 'Employee_06102016.txt', 'PASS' FROM DUAL UNION ALL
SELECT 100, 'Employee_06092016.txt', 'FAIL' FROM DUAL UNION ALL
SELECT 100, 'Employee_06092016.txt', 'MISS' FROM DUAL UNION ALL
SELECT 101, 'ABC_06092016.txt', 'PASS' FROM DUAL;
Query:
SELECT *
FROM (
SELECT t.*,
COUNT(1) OVER ( PARTITION BY FileID ) AS num_pass_fail
FROM table_name t
WHERE status IN ( 'PASS', 'FAIL' )
)
WHERE filename = 'Employee_06102016.txt';
Output:
FILEID FILENAME STATUS NUM_PASS_FAIL
---------- --------------------- ------ -------------
100 Employee_06102016.txt PASS 2
SELECT
FILENAME,
SUM(CASE status WHEN 'PASS' THEN 1 ELSE 0 END) as "Pass Count",
SUM(CASE status WHEN 'FAIL' THEN 1 ELSE 0 END) as "Fail Count",
SUM(CASE status WHEN 'MISS' THEN 1 ELSE 0 END) as "Miss Count"
FROM
TableName
WHERE
FILENAME = 'Employee_06102016.txt'
It seems that you simply need:
SELECT COUNT (1)
FROM TABLE_NAME
WHERE FILENAME = 'Employee_06102016.txt'
AND STATUS IN ('PASS', 'FAIL');
Try this one
select cnt from (
select count(*) as cnt,
listagg(filename, ',') within group(order by filename) as filename_list from table_name
where status in ('PASS', 'FAIL') group by fileid
) where instr(filename_list, 'Employee_06102016.txt')>0;

SQL Server case when or enum

I have a table something like:
stuff type price
first_stuff 1 43
second_stuff 2 46
third_stuff 3 24
fourth_stuff 2 12
fifth_stuff NULL 90
And for every type of stuff is assigned a description which is not stored in DB
1 = Bad
2 = Good
3 = Excellent
NULL = Not_Assigned
All I want is to return a table which count each type separately, something like:
Description Count
Bad 1
Good 2
Excellent 1
Not_Assigned 1
DECLARE #t TABLE ([type] INT)
INSERT INTO #t ([type])
VALUES (1),(2),(3),(2),(NULL)
SELECT
[Description] =
CASE t.[type]
WHEN 1 THEN 'Bad'
WHEN 2 THEN 'Good'
WHEN 3 THEN 'Excellent'
ELSE 'Not_Assigned'
END, t.[Count]
FROM (
SELECT [type], [Count] = COUNT(*)
FROM #t
GROUP BY [type]
) t
ORDER BY ISNULL(t.[type], 999)
output -
Description Count
------------ -----------
Bad 1
Good 2
Excellent 1
Not_Assigned 1
;WITH CTE_TYPE
AS (SELECT DESCRIPTION,
VALUE
FROM (VALUES ('BAD',
1),
('GOOD',
2),
('EXCELLENT',
3))V( DESCRIPTION, VALUE )),
CTE_COUNT
AS (SELECT C.DESCRIPTION,
Count(T.TYPE) TYPE_COUNT
FROM YOUR_TABLE T
JOIN CTE_TYPE C
ON T.TYPE = C.VALUE
GROUP BY TYPE,
DESCRIPTION
UNION ALL
SELECT 'NOT_ASSIGNED' AS DESCRIPTION,
Count(*) TYPE_COUNT
FROM YOUR_TABLE
WHERE TYPE IS NULL)
SELECT *
FROM CTE_COUNT
Hope, this helps.
SELECT ISNULL(D.descr, 'Not_Assigned'),
T2.qty
FROM
(SELECT T.type,
COUNT(*) as qty
FROM Table AS T
GROUP BY type) AS T2
LEFT JOIN (SELECT 1 as type, 'Bad' AS descr
UNION ALL
SELECT 2, 'Good'
UNION ALL
SELECT 3, 'Excellent') AS D ON D.type = T2.type
If you are using Sql server 2012+ use this
SELECT
[Description] = coalesce(choose (t.[type],'Bad','Good' ,'Excellent'), 'Not_Assigned'),
t.[Count]
FROM (
SELECT [type], [Count] = COUNT(*)
FROM yourtable
GROUP BY [type]
) t

sql query sum count

I have a table
id date state
1 10.01.01 reg
2 08.01.01 reg
3 05.01.01 check
4 02.01.01 check
5 01.01.01 reg
and want to show result like this
count reg
5 1
e.g sum of "reg" statuses should be counted only if the previous status was "check".
Please, help me or give the right direction to solve it
In SQL Server 2012 and above you could use LAG to access the previous row value:
SQL SERVER 2012
;WITH MyCTE AS
(
select id, date,state,lag(state,1) over(order by id) as prevstate
from Table1
)
SELECT COUNT(*),SUM(CASE WHEN state = 'reg' AND prevstate = 'check' THEN 1 ELSE 0 END)
FROM MyCTE
Fiddler Demo
ORACLE
With T AS
(
select "id", "date", "state", lag("state",1) over(order by "id") as "prevstate"
from Table1
)
SELECT COUNT(*),SUM(CASE WHEN "state" = 'reg' AND "prevstate" = 'check' THEN 1 ELSE 0 END)
FROM T
Fiddler Demo
MySQL
SELECT COUNT(*),SUM(CASE WHEN state = 'reg' AND prevstate = 'check' THEN 1 ELSE 0 END)
FROM
(
SELECT T1.ID,T1.Date,T1.state,T2.state AS prevstate
FROM Table1 T1
LEFT JOIN Table1 T2
ON T1.ID - 1 = T2.ID
) AS T
Fiddler Demo
Actually the third case would work in prety much everything.

sql query group

SQL query question
I have a query like
select proposal_id, service_id,account_type
from table1
The result is like this:
proposal_id service_id account_type
1 1001 INTERVAL
1 1002 INTERVAL
2 1003 NON INTERVAL
2 1004 NON INTERVAL
3 1005 NON INTERVAL
3 1006 INTERVAL
I want to write a query: for each proposal_id, if all the service have INTERVAL then get 'INTERVAL', if all NON-INTERVAL get 'NON-INTERVAL', if both, get 'Both'
For the example above, it should return
proposal_id account_type
1 INTERVAL
2 NON-INTERVAL
3 BOTH
Data:
declare #table table (id int, sid int, acc nvarchar(20))
insert #table VALUES (1,1001,'INTERVAL'),(1,1002,'INTERVAL'),(2,1003,'NON INTERVAL'),(2,1004,'NON INTERVAL'),
(3,1005,'NON INTERVAL'),(3,1006,'INTERVAL')
Query:
select x.Id
, CASE counter
WHEN 1 THEN x.Account_Type
ELSE 'BOTH'
END AS Account_Type
from (
select Id, Count(DISTINCT(acc)) AS counter, MAX(acc) As Account_Type
from #table
GROUP BY Id
) x
Results
Id Account_Type
----------- --------------------
1 INTERVAL
2 NON INTERVAL
3 BOTH
SELECT
b.proposal_id
,CASE
WHEN s1.proposal_id IS NOT NULL AND s2.proposal_id IS NOT NULL THEN 'BOTH'
WHEN s1.proposal_id IS NOT NULL THEN 'INTERVAL'
WHEN s2.proposal_id IS NOT NULL THEN 'NON-INTERVAL'
ELSE 'UNKNOWN'
END [account_type]
FROM table1 b
LEFT JOIN(
SELECT proposal_id,account_type FROM table1 WHERE account_type = 'INTERVAL'
) s1
ON b.proposal_id = s1.proposal_id
LEFT JOIN (
SELECT proposal_id,account_type FROM table1 WHERE account_type = 'NON-INTERVAL'
)s2
ON b.proposal_id = s2.proposal_id
You could use count distinct to determinate if it is both then use CASE to determinate what to display
SELECT DISTINCT proposal.proposal_id,
CASE cou
WHEN 1 THEN type ELSE 'Both' END as TYPE
FROM proposal
INNER JOIN (SELECT proposal_id, count(distinct type) cou
FROM proposal GROUP BY proposal_id) inn
ON proposal.id = inn.id
select proposal_id,
case when count(distinct account_type) > 1 then 'BOTH'
else max(account_type)
end
from table1
group by proposal_id
You have the fiddler here.