Tableau/SQL Calculated Field With Grouping - sql

I have a table with the following structure
id, event_name, event_date
| 1 | a | 1.1.2020 |
| 2 | b | 3.2.2020 |
| 3 | b | 3.2.2020 |
| 3 | b | 5.2.2020|
| 1 | b | 31.12.2019 |
| 2 | a | 5.1.2020 |
My goal would be to perform a grouping on the id and then I'd have to check wheter the date of an event 'a' comes before an event 'b'. If so I'd like to output 'ok' and 'error' elsewise.
In this example this would result to
id, check
| 1 | error|
| 2 | ok |
| 3 | ok |
Would it be possible to perform the task with a calculated field in Tableau? SQL would be also be ok!

Try this
Select id, case when diff<0 then 'ok'
else 'error' end as status from
(
Select id,
max(case when event_name ='a' then event_date end) -
max(case when event_name='b' then event_date end)
As diff
From table group by id order by id)

You can use this query with UNION clause:
select id, 'Error' "check" from mydata md where event_name='a' and id in
(select id from mydata where id=md.id and md.event_name<>event_name
and md.event_date > event_date)
union
select id, 'Ok' "check" from mydata md where event_name='a' and id in
(select id from mydata where id=md.id and md.event_name<>event_name
and md.event_date < event_date);
Output should be :
| ID | 'ERROR' |
|----|---------|
| 1 | Error |
| 2 | Ok |
ID=3 doesn't appear, because event_name both are 'b'.

Related

SQL query - Beginner

I'm new to SQL. I need SQL query to achieve the mentioned output.
I have asked a similar query but that doesn't describe my problem well. Here is my detailed requirement.
I have a table with data as below
Table: boxes
+------------+----------+
| box_id | Status |
+------------+----------+
| 1 | created |
| 2 | created |
| 3 | opened |
| 4 | opened |
| 5 | closed |
| 6 | closed |
| 7 | closed |
| 8 | wrapped |
+------------+----------+
With this there is also a status names destroyed But for which there is no box destroyed.
I need an output like this
+--------------+-------+
| Status | Count |
+--------------+-------+
| created | 2 |
| opened | 2 |
| destroyed | 0 |
| other_status | 4 | # this includes status (closed and wrapped)
| total | 8 |
+--------------+-------+
How can this be achieved in SQL. Thanks in advance
If you are using MSSQL or MySQL8.0, you can use CTE as below to achieve your required output-
DEMO HERE
WITH CTE AS
(
SELECT 'created' Status UNION ALL
SELECT 'opened' UNION ALL
SELECT 'destroyed' UNION ALL
SELECT 'other_status'
)
,CTE2 AS
(
SELECT
CASE
WHEN Status IN ('created','opened','destroyed') THEN Status
ELSE 'other_status'
END Status,
SUM(1) Cnt
FROM your_table
GROUP BY
CASE
WHEN Status IN ('created','opened','destroyed') THEN Status
ELSE 'other_status'
END
)
SELECT CTE.Status,ISNULL(CTE2.Cnt, 0) Cnt
FROM CTE LEFT JOIN CTE2 ON CTE.Status = CTE2.Status
UNION ALL
SELECT 'Total' Status, SUM(CTE2.Cnt) FROM CTE2
you can try the following code.
select status, count(box_id)
from table
where status in ('created','opened', 'destroyed')
group by status
UNION ALL
select 'other_status' status, count(box_id)
from table
where status not in ('created','opened', 'destroyed')
UNION ALL
select 'total' status, count(box_id)
from table;

SQL select distinct when one column in and another column greater than

Consider the following dataset:
+---------------------+
| ID | NAME | VALUE |
+---------------------+
| 1 | a | 0.2 |
| 1 | b | 8 |
| 1 | c | 3.5 |
| 1 | d | 2.2 |
| 2 | b | 4 |
| 2 | c | 0.5 |
| 2 | d | 6 |
| 3 | a | 2 |
| 3 | b | 4 |
| 3 | c | 3.6 |
| 3 | d | 0.2 |
+---------------------+
I'm tying to develop a sql select statement that returns the top or distinct ID where NAME 'a' and 'b' both exist and both of the corresponding VALUE's are >= '1'. Thus, the desired output would be:
+---------------------+
| ID | NAME | VALUE |
+---------------------+
| 3 | a | 2 |
+----+-------+--------+
Appreciate any assistance anyone can provide.
You can try to use MIN window function and some condition to make it.
SELECT * FROM (
SELECT *,
MIN(CASE WHEN NAME = 'a' THEN [value] end) OVER(PARTITION BY ID) aVal,
MIN(CASE WHEN NAME = 'b' THEN [value] end) OVER(PARTITION BY ID) bVal
FROM T
) t1
WHERE aVal >1 and bVal >1 and aVal = [Value]
sqlfiddle
This seems like a group by and having query:
select id
from t
where name in ('a', 'b')
having count(*) = 2 and
min(value) >= 1;
No subqueries or joins are necessary.
The where clause filters the data to only look at the "a" and "b" records. The count(*) = 2 checks that both exist. If you can have duplicates, then use count(distinct name) = 2.
Then, you want the minimum value to be 1, so that is the final condition.
I am not sure why your desired results have the "a" row, but if you really want it, you can change the select to:
select id, 'a' as name,
max(case when name = 'a' then value end) as value
you can use in and sub-query
select top 1 * from t
where t.id in
(
select id from t
where name in ('a','b')
group by id
having sum(case when value>1 then 1 else 0)>=2
)
order by id

Provide status of an Activity based on date column in a view in SQL Server

In the view have to provide Status based on date.
The condition is:
If one of the activities doesn't have a date for the person, then Status is Active
If all the activities have a date for the person, then Status is Not Active
How do I get STATUS for a person?
For example:
|PersonId|Name|Activity | Date |
+--------+----+---------+-----------+
| 1 |John| a | 01/01/2015|
| 1 |John| b | 03/12/2016|
| 1 |John| c | 02/13/2017|
| 2 |Sam | d | 06/01/2014|
| 2 |Sam | e | 05/18/2016|
| 2 |Sam | f | NULL |
Output in the view:
|PersonId|Name|Status |
+--------+----+----------+
| 1 |John|Not Active|
| 2 |Sam | Active |
You can do this with aggregation:
select personid, name,
(case when count(*) = count(date) then 'Not Active' else 'Active' end) as status
from t
group by personid, name;
I think Gordon's answer is better, but since I started typing this out, I figured i'd finish it anyway.
something like
declare #maxDate date = datefromparts(9999, 12, 31)
select case when max(isnull(Date, #MaxDate)) = #maxDate) then 'Active' else 'inactive'
from ...

Get ID if table has one or more row exist for a condition

Suppose I have a table as below:
ID | Account| Status
---+--------+-------
1 | acct1 | A
1 | acct2 | S
1 | acct3 | C
2 | acct4 | C
2 | acct5 | C
3 | acct6 | A
3 | acct7 | C
4 | acct8 | C
4 | acct9 | C
4 | acct10 | C
Condition: return ID if accounts do not have any 'A' and 'S' status.
For this case, I only want ID '2' and '4' to be returned.
You could use HAVING and conditional SUM:
SELECT ID
FROM tab
GROUP BY ID
HAVING SUM(CASE WHEN Status IN ('A', 'S') THEN 1 ELSE 0 END) = 0
First select id which record don't have 'A' and 'S'. Then get distinct record:
Select distinct(ID) as ID
from table_name where id not in
(
select ID from table_name where status in('A', 'S')
)

2 listagg in one SQL Select in Oracle

I have a table in the form of :
| ID | COURSE | PASS |
---------------------------
| 1 | 1 | 1 |
| 1 | 2 | 1 |
| 1 | 3 | 1 |
| 1 | 4 | 0 |
| 1 | 5 | 0 |
and I want row in the form:
| ID | FAILED | PASSED |
---------------------------
| 1 | 4,5 | 1,2,3 |
the only i figured is something like this:
select NVL(passed.id, failed.id), passed.test, failed.test from
(select id, listagg(course, ',') within group (order by course) test from table1 where pass = 1 group by id ) passed
full outer join
(select id, listagg(course, ',') within group (order by course) test from table1 where pass = 0 group by id ) failed
on passed.id = failed.id
is there a way to do it in a single query ?
Try
select id,
listagg(case when pass = 1 then course end, ',') within group (order by course) passed,
listagg(case when pass = 0 then course end, ',') within group (order by course) failed
from table1
group by id
Here is a sqlfiddle demo