SQL multiple conditions for group of data - sql

I am trying to write a validation for the following set of data:
SSYS | Material_Number | Characteristic | Description
001 | 000000000001111 | SH_DESC | TEST
001 | 000000000001111 | DESIGN_TYPE | NULL
001 | 000000000001111 | VOLTAGE | NULL
001 | 000000000009999 | SH_DESC | TEST2
001 | 000000000009999 | OPER_METHOD | LIGHT
001 | 000000000009999 | FILTER_TYPE | Filter element,Air
001 | 000000000014560 | SH_DESC | Horn,Signal
001 | 000000000014560 | DIMENSION_SIZE | NULL
001 | 000000000014560 | FILTER_TYPE | NULL
I would like to group by the Material_Number and count as 1 (ie. true) if within the Material_Number group, the SH_DESC description is NOT NULL and all other characteristics' descriptions IS NULL. So, in this case my result would be:
SSYS | Material_Number | Characteristic | Description | COUNT
001 | 000000000001111 | SH_DESC | TEST | 1
001 | 000000000009999 | SH_DESC | TEST2 | 0
001 | 000000000014560 | SH_DESC | Horn,Signal | 1
My attempt:
Select COUNT (*), SSYS, Material_Number, Characteristic, Description
From myDB where (Characteristic = 'SH_DESC' AND DESCRIPTION IS NOT NULL) AND (Characteristic NOT IN ('SH_DESC') IS NULL)
GROUP BY SSYS, Material_Number, Characteristic, Description HAVING COUNT (*) < 2
Any help is much appreciated!

Try:
Select SSYS,
Material_Number,
'SH_DESC' Characteristic,
MAX(CASE WHEN Characteristic = 'SH_DESC' THEN Description END) Description,
CASE WHEN MAX(CASE WHEN Characteristic = 'SH_DESC' THEN Description END) IS NOT NULL AND
MAX(CASE WHEN Characteristic <>'SH_DESC' THEN Description END) IS NULL
THEN 1
ELSE 0
END COUNT
From myDB
GROUP BY SSYS, Material_Number

Try this:
select ssys, material_number, 'SH_DESC' as characteristic,
(case when sum(case when characteristic is not null and characteristic<> 'SH_DESC' and description is null then 1 else 0 end) = count(*) - 1
then 1
else 0
end) as count
from t
group by ssys, material_number
It groups by material and counts the number of rows that have non-null characterist where the description is null. It sets count accordingly.

An alternative to the GROUP BY and SUM(CASE WHEN) options...
SELECT
*,
CASE WHEN Description IS NULL THEN 0
WHEN EXISTS (SELECT *
FROM myDB as lookup
WHERE lookup.SSYS = myDB.SSYS
AND lookup.Material_Number = myDB.Material_Number
AND lookup.Characteristic <> 'SH_DESC'
AND lookup.Description IS NOT NULL) THEN 0
ELSE 1 END as myCount
FROM
myDB
WHERE
Characteristic = 'SH_DESC'

Try this -- Here I guess you cant get the description bcos there is nothiing to filter the specific description.
CREATE TABLE yourtable(SSYS varchar(10),Material_Number varchar(100),Characteristic varchar(100),Description varchar(100))
INSERT INTO yourtable
VALUES('001','000000000001111','SH_DESC','TEST'),
('001','000000000001111','DESIGN_TYPE','NULL'),
('001','000000000001111','VOLTAGE','NULL'),
('001','000000000009999','SH_DESC','TEST2'),
('001','000000000009999','SH_DESC','LIGHT'),
('001','000000000009999','FILTER_TYPE','Filter element,Air'),
('001','000000000014560','SH_DESC','Horn,Signal'),
('001','000000000014560','DIMENSION_SIZE','NULL'),
('001','000000000014560','FILTER_TYPE ','NULL')
select max(SSYS),
max(Material_Number),
'SH_DESC' as Characteristic,
CASE WHEN SUM(CASE WHEN Characteristic='SH_DESC' and Description is not null then 1 else 0 end) = 1 then 1 else 0 end as cnt
from yourtable
group by Material_Number

Related

Tableau/SQL Calculated Field With Grouping

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'.

PostgreSQL group by then count by value

I have the following data
id | sub_id |status |
---|--------|-------|
1 | 1 | new |
2 | 2 | old |
3 | 2 | new |
4 | 3 | old |
Which query should I use to get the following result?
I want to group the result by sub_id and then add new columns that store the number of statuses of the corresponding sub_id.
sub_id | new | old | total |
-------|------|------|-------|
1 | 1 | 0 | 1 |
2 | 1 | 1 | 2 |
3 | 0 | 1 | 1 |
I tried this and it did not work as expected.
SELECT
sub_id,
count(status='new') AS new,
count(status='old') AS old,
count(status) AS total
FROM table
GROUP BY sub_id;
status = 'new' is
true for all rows with status = 'new'
false for all rows with status <> 'new'
null for all rows with status is null.
COUNT( <expression> ) counts all non-null occurences of the expression. This means you count both 'new' and 'old', as neither true nor false is null, when you only want to count 'new'. Use a CASE expression instead:
count(case when status = 'new' then 1 end)
which is short for
count(case when status = 'new' then 1 else null end)
or the same with SUM:
sum(case when status = 'new' then 1 else 0 end)
Some DBMS (MySQL for instance) treat true as 1 and false as 0. There you can even use:
sum(status = 'new')
In PostgreSQL you can also use the filter() clause:
count(*) filter (where status = 'new')

One SQL query with multiple conditions

I am running an Oracle database and have two tables below.
#account
+----------------------------------+
| acc_id | date | acc_type |
+--------+------------+------------+
| 1 | 11-07-2018 | customer |
| 2 | 01-11-2018 | customer |
| 3 | 02-09-2018 | employee |
| 4 | 01-09-2018 | customer |
+--------+------------+------------+
#credit_request
+-----------------------------------------------------------------+
| credit_id | date | credit_type | acc_id | credit_amount |
+------------+-------------+---------- +--------+
| 1112 | 01-08-2018 | failed | 1 | 2200 |
| 1214 | 02-12-2018 | success | 2 | 1500 |
| 1312 | 03-11-2018 | success | 4 | 8750 |
| 1468 | 01-12-2018 | failed | 2 | 3500 |
+------------+-------------+-------------+--------+---------------+
Want to have followings for each customer:
the last successful credit_request
sum of credit_amount of all failed credit_requests
Here is one method:
select a.acct_id, acr.num_fails,
acr.num_successes / nullif(acr.num_fails) as ratio, -- seems weird. Why not just the failure rate?
last_cr.credit_id, last_cr.date, last_cr.credit_amount
from account a left join
(select acc_id,
sum(case when credit_type = 'failed' then 1 else 0 end) as num_fails,
sum(case when credit_type = 'failed' then credit_amount else 0 end) as num_fails,
sum(case when credit_type = 'success' then 1 else 0 end) as num_successes
max(case when credit_type = 'success' then date else 0 end) as max_success_date
from credit_request
group by acct_id
) acr left join
credit_request last_cr
on last_cr.acct_id = acr.acct_id and last_cr.date = acr.date;
The following query should do the trick.
SELECT
acc_id,
MAX(CASE WHEN credit_type = 'success' AND rn = 1 THEN credit_id END) as last_successfull_credit_id,
MAX(CASE WHEN credit_type = 'success' AND rn = 1 THEN cdate END) as last_successfull_credit_date,
MAX(CASE WHEN credit_type = 'success' AND rn = 1 THEN credit_amount END) as last_successfull_credit_amount,
SUM(CASE WHEN credit_type = 'failed' THEN credit_amount ELSE 0 END) total_amount_of_failed_credit,
SUM(CASE WHEN credit_type = 'failed' THEN 1 ELSE 0 END) / COUNT(*) ratio_success_request
FROM (
SELECT
a.acc_id,
a.cdate adate,
a.acc_type,
c.credit_id,
c.cdate,
c.credit_type,
c.credit_amount,
ROW_NUMBER() OVER(PARTITION BY c.acc_id, c.credit_type ORDER BY c.cdate DESC) rn
FROM
account a
LEFT JOIN credit_request c ON c.acc_id = a.acc_id
) x
GROUP BY acc_id
ORDER BY acc_id
The subquery assigns a sequence to each record, within groups of accounts and credit types, using ROW_NUMBR(). The outer query does conditional aggrgation to compute the different computation you asked for.
This Db Fiddle demo with your test data returns :
ACC_ID | LAST_SUCCESSFULL_CREDIT_ID | LAST_SUCCESSFULL_CREDIT_DATE | LAST_SUCCESSFULL_CREDIT_AMOUNT | TOTAL_AMOUNT_OF_FAILED_CREDIT | RATIO_SUCCESS_REQUEST
-----: | -------------------------: | :--------------------------- | -----------------------------: | ----------------------------: | --------------------:
1 | null | null | null | 2200 | 1
2 | 1214 | 02-DEC-18 | 1500 | 3500 | .5
3 | null | null | null | 0 | 0
4 | 1312 | 03-NOV-18 | 8750 | 0 | 0
This might be what you are looking for... Since you did not show expected results, this might not be 100% accurate, feel free to adapt this.
I guess the below query is easy to understand and implement. Also, to avoid more and more terms in the CASE statements you can just make use of WITH clause and use it in the CASE statements to reduce the query size.
SELECT a.acc_id,
c.credit_type,
(distinct c.credit_id),
CASE WHEN
c.credit_type='success'
THEN max(date)
END CASE,
CASE WHEN
c.credit_type='failure'
THEN sum(credit_amount)
END CASE,
(CASE WHEN
c.credit_type='success'
THEN count(*)
END CASE )/
( CASE WHEN
c.credit_type='failure'
THEN count(*)
END CASE)
from accounts a LEFT JOIN
credit_request c on
a.acc_id=c.acc_id
where a.acc_type= 'customer'
group by c.credit_type

Aggregation for multiple SQL SELECT statements

I've got a table TABLE1 like this:
|--------------|--------------|--------------|
| POS | TYPE | VOLUME |
|--------------|--------------|--------------|
| 1 | A | 34 |
| 2 | A | 2 |
| 1 | A | 12 |
| 3 | B | 200 |
| 4 | C | 1 |
|--------------|--------------|--------------|
I want to get something like this (TABLE2):
|--------------|--------------|--------------|--------------|--------------|
| POS | Amount_A | Amount_B | Amount_C | Sum_Volume |
|--------------|--------------|--------------|--------------|--------------|
| 1 | 2 | 0 | 0 | 46 |
| 2 | 1 | 0 | 0 | 2 |
| 3 | 0 | 1 | 0 | 200 |
| 4 | 0 | 0 | 1 | 1 |
|--------------|--------------|--------------|--------------|--------------|
My Code so far is:
SELECT
(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'A') AS [Amount_A]
,(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'B') AS [Amount_B]
,(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'C') AS [Amount_C]
,(SELECT SUM(VOLUME)
FROM TABLE AS [Sum_Volume]
INTO [TABLE2]
Now two Questions:
How can I include the distinction concerning POS?
Is there any better way to count each TYPE?
I am using MSSQLServer.
What you're looking for is to use GROUP BY, along with your Aggregate functions. So, this results in:
USE Sandbox;
GO
CREATE TABLE Table1 (Pos tinyint, [Type] char(1), Volume smallint);
INSERT INTO Table1
VALUES (1,'A',34 ),
(2,'A',2 ),
(1,'A',12 ),
(3,'B',200),
(4,'C',1 );
GO
SELECT Pos,
COUNT(CASE WHEN [Type] = 'A' THEN [Type] END) AS Amount_A,
COUNT(CASE WHEN [Type] = 'B' THEN [Type] END) AS Amount_B,
COUNT(CASE WHEN [Type] = 'C' THEN [Type] END) AS Amount_C,
SUM(Volume) As Sum_Volume
FROM Table1 T1
GROUP BY Pos;
DROP TABLE Table1;
GO
if you have a variable, and undefined, number of values for [Type], then you're most likely going to need to use Dynamic SQL.
your first column should be POS, and you'll GROUP BY POS.
This will give you one row for each POS value, and aggregate (COUNT and SUM) accordingly.
You can also use CASE statements instead of subselects. For instance, instead of:
(SELECT COUNT(TYPE)
FROM TABLE1
WHERE TYPE = 'A') AS [Amount_A]
use:
COUNT(CASE WHEN TYPE = 'A' then 1 else NULL END) AS [Amount_A]

Combine multiple lines onto one line

I have the following query
SELECT orderno,
CASE WHEN param_id = 'variant' THEN param_val END AS 'variant',
CASE WHEN param_id = 'period_from' THEN param_val END AS'period_from',
CASE WHEN param_id = 'period_to' THEN param_val END AS'period_to',
CASE WHEN param_id = 'division' THEN param_val END AS 'division',
CASE WHEN param_id = 'show_div' THEN param_val END AS 'show_div',
CASE WHEN param_id = 'group_div' THEN param_val END AS 'group_div',
FROM orderreport
order by orderno
This returns a grid similar to the below (there are another number of columns which I have removed for the purpose of the question) There is also an infinite number of order nos
orderno | variant | period_from | period_to | division | show_div | group_div
3 | AH003 | NULL | NULL | NULL | NULL | NULL
3 | NULL | 201300 | NULL | NULL | NULL | NULL
3 | NULL | NULL | 201304 | NULL | NULL | NULL
3 | NULL | NULL | NULL | SALES | NULL | NULL
3 | NULL | NULL | NULL | NULL | Y | NULL
3 | NULL | NULL | NULL | NULL | NULL | Y
My desired output is as the below, no matter what i have tried I am stumped.
orderno | variant | period_from | period_to | division | show_div | group_div
3| AH003 | 201300 | 201304 | SALES | Y | Y
Your query is very close, I would add an aggregate function around your CASE expressions and then a GROUP BY:
SELECT orderno,
max(CASE WHEN param_id = 'variant' THEN param_val END) AS variant,
max(CASE WHEN param_id = 'period_from' THEN param_val END) AS period_from,
max(CASE WHEN param_id = 'period_to' THEN param_val END) AS period_to,
max(CASE WHEN param_id = 'division' THEN param_val END) AS division,
max(CASE WHEN param_id = 'show_div' THEN param_val END) AS show_div,
max(CASE WHEN param_id = 'group_div' THEN param_val END) AS group_div
FROM orderreport
group by orderno
order by orderno