SQL - Select rows where all values in a column are the same - sql

I have an SQL question I've been struggling with and hope someone can help. I have the following data:
TEAM | USERID | STEP1 | STEP2 | STEP3 | STEP4 | STEP5
001 | 000001 | Y | Y | N | N | Y
001 | 000002 | Y | N | N | Y | Y
002 | 000003 | N | Y | Y | N | N
002 | 000004 | N | Y | Y | Y | Y
003 | 000005 | Y | N | N | Y | N
003 | 000006 | Y | Y | Y | N | Y
What I need to do is return the value of the TEAM where all values of any STEPx are 'N'. So in the example above I would need TEAM 001 and 002 to be returned because in TEAM 001 all values of STEP3 are 'N', and in TEAM 002 all values of STEP1 are 'N'.
Any help would be much appreciated.

select team
from table
group by team
having
sum(case step1 when 'N' then 0 else 1 end) = 0
or sum(case step2 when 'N' then 0 else 1 end) = 0
or sum(case step3 when 'N' then 0 else 1 end) = 0
or sum(case step4 when 'N' then 0 else 1 end) = 0
or sum(case step5 when 'N' then 0 else 1 end) = 0
Here's a fiddle: http://sqlfiddle.com/#!6/ecbff/3
It may be better to normalize your data so that you have an int column named STEP and one row per team/user/step. This would make your query much less awkward.

I went the other way, but Blorgbeard was much faster than me.
select TEAM
from TEAMS
group by TEAM
having
count(case STEP1 when 'Y' then 1 end) = 0
or count(case STEP2 when 'Y' then 1 end) = 0
or count(case STEP3 when 'Y' then 1 end) = 0
or count(case STEP4 when 'Y' then 1 end) = 0
or count(case STEP5 when 'Y' then 1 end) = 0

There is no pure sql-based solution, but each conrete database may have its own way to iterate over all columns.
How ever, this approach is bad.
You should normalize your database.
|TEAM|USER_ID|STEP_ID|VALUE|
|1 | 1 | 1 | Y |
After it, it it will be easy to join team with this tables and filter all teams with "Y"

Related

How to know arithmetic mean of two count values

I have table answers where I store information.
| EMPLOYEE | QUESTION_ID | QUESTION_TEXT | SELECTED_OPTION_ID | SELECTED_OPTION_TEXT |
|----------|-------------|------------------------|--------------------|----------------------|
| Mark | 1 | Do you like soup? | 1 | Yes |
| Kate | 1 | Do you like soup? | 1 | Yes |
| Jone | 1 | Do you like soup? | 2 | No |
| Kim | 1 | Do you like soup? | 3 | I don't know |
| Alex | 1 | Do you like soup? | 2 | No |
| Bond | 1 | Do you like soup? | 1 | Yes |
| Ford | 1 | Do you like soup? | 3 | I don't know |
| Mark | 2 | Do you like ice cream? | 2 | No |
| Kate | 2 | Do you like ice cream? | 1 | Yes |
| Jone | 2 | Do you like ice cream? | 1 | Yes |
| Kim | 2 | Do you like ice cream? | 1 | Yes |
| Alex | 2 | Do you like ice cream? | 2 | No |
| Bond | 2 | Do you like ice cream? | 1 | Yes |
| Ford | 2 | Do you like ice cream? | 3 | I don't know |
Formulas:
value_1 = (Number of users who answered "No" or "I don't know" to the first question) / (The total number of people who answered to the first question)
value_2 = (Number of users who answered "No" or "I don't know" to the second question) / (The total number of people who answered to the first question)
I can separately find the values according to the above formulas. For example value_1:
select
count(*)
from
answers
where
question_id = 1
and (
selected_option_id in (2, 3)
or
selected_option_text in ('No', 'I don\'t know')
)
My question is how to arithmetic mean of these 2 values correctly by one sql query?
In other words I need to find average value:
You could use a condition sum
select (sum( case when QUESTION_ID = 1 AND
SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 1 then 1 else 0 end)::float )*100 first_question_rate,
(sum( case when QUESTION_ID = 2 AND
SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 2 then 1 else 0 end)::float)*100 second_question_rate,
(( sum( case when QUESTION_ID = 1 AND SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 1 then 1 else 0 end)::float +
sum( case when QUESTION_ID = 2 AND SELECTED_OPTION_ID in ( 2,3) THEN 1 else 0 end )::float /
sum( case when QUESTION_ID = 2 then 1 else 0 end) ::float)/2)*100 avg
from answer
Are you looking for something like below-
SELECT
SUM(CASE WHEN QUESTION_ID = 1 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)
/
SUM(CASE WHEN QUESTION_ID = 1 THEN 1 ELSE 0 END) value_1 ,
SUM(CASE WHEN QUESTION_ID = 2 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)
/
SUM(CASE WHEN QUESTION_ID = 2 THEN 1 ELSE 0 END) value_2
FROM answers
For getting average, please use the below script-
SELECT (A.value_1+A.value_2)/2.0
FROM
(
SELECT
SUM(CASE WHEN QUESTION_ID = 1 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)*1.0
/
SUM(CASE WHEN QUESTION_ID = 1 THEN 1 ELSE 0 END)*1.0 value_1 ,
SUM(CASE WHEN QUESTION_ID = 2 AND SELECTED_OPTION_TEXT <> 'Yes' THEN 1 ELSE 0 END)*1.0
/
SUM(CASE WHEN QUESTION_ID = 2 THEN 1 ELSE 0 END)*1.0 value_2
FROM answers
)A
I'm pretty sure you want conditional aggregation. I suspect you want:
select question_id,
count(*) filter (where selected_option_id in (2, 3)) as num_2_3,
avg( selected_option_id in (2, 3)::int ) as ratio_2_3
from answers
group by question_id;
For each question, this provides the number of answers that are 2 or 3 and the ratio of those answers to all answers.

How to select an attribute based on string value within a group

Table name: Copies
+------------------------------------+
| group_id | my_id | stuff |
+------------------------------------+
| 900 | 1 | Y |
| 900 | 2 | N |
| 901 | 3 | Y |
| 901 | 4 | Y |
| 902 | 5 | N |
| 902 | 6 | N |
| 903 | 7 | N |
| 903 | 8 | Y |
---------------------------------------
The output should be:
+------------------------------------+
| group_id | my_id | stuff |
+------------------------------------+
| 900 | 1 | Y |
| 903 | 8 | Y |
--------------------------------------
Hello, I have a table where I have to discern a 'good' record within a group_id based on a positive (Y) value within the stuff field. I need the full record where only one value fits this criteria. If both stuff values are Y or both are N, then they shouldn't be selected. It seems like this should be simple, but I am not sure how to proceed.
One option here is to use conditional aggregation over each group_id and retain a group if it has a mixture of yes and no answers.
WITH cte AS (
SELECT group_id
FROM Copies
GROUP BY group_id
HAVING SUM(CASE WHEN stuff = 'Y' THEN 1 ELSE 0 END) > 0 AND
SUM(CASE WHEN stuff = 'N' THEN 1 ELSE 0 END) > 0
)
SELECT c1.*
FROM Copies c1
INNER JOIN cte c2
ON c1.group_id = c2.group_id
WHERE c1.stuff = 'Y'
One advantage of this solution is that it will show all columns of matching records.
select group_id,
min(my_id)
keep (dense_rank first order by case stuff when 'Y' then 0 end) as my_id,
'Y' as stuff
from table_1
group by group_id
having min(stuff) != max(stuff)
with rows as(
select group_id, my_id, sum(case when stuff = 'Y' then 1 else 0 end) c
from copies
group by group_id, my_id)
select c.*
from copies c inner join rows r on (c.group_id = r.group_id and c.my_id = r.my_id)
where r.c = 1;
Try this:
SELECT C.*
FROM COPIES C,
COPIES C2
WHERE C.STUFF='Y'
AND C2.STUFF='N'
AND C.GROUP_ID=C2.GROUP_ID
Try this:
SELECT t1.*
FROM copies t1
JOIN (
SELECT group_id
FROM copies
GROUP BY group_id
HAVING COUNT(CASE WHEN stuff = 'Y' THEN 1 END) = 1 AND
COUNT(CASE WHEN stuff = 'N' THEN 1 END) = 1
) t2 ON t1.group_id = t2.group_id
WHERE t1.stuff = 'Y'
This works as long as group_id values appear in couples.

SQL Pivot using count

I have a table which has the following entries
ID | column | value
------------------------
1 | status | DONE
2 | status | FAILED
1 | progress | Green
2 | progress | Red
i want the output as
ID | DONE | FAILED | GREEN | RED
1 | 1 | 0 | 1 | 0
2 | 0 | 1 | 0 | 1
Please let me know the query. I have tried pivot but not getting the results.
Here is a standard pivot query solution which does not use SQL Server's built in PIVOT capability:
SELECT ID,
SUM(CASE WHEN value = 'DONE' THEN 1 ELSE 0 END) AS DONE,
SUM(CASE WHEN value = 'FAILED' THEN 1 ELSE 0 END) AS FAILED,
SUM(CASE WHEN value = 'Green' THEN 1 ELSE 0 END) AS GREEN,
SUM(CASE WHEN value = 'Red' THEN 1 ELSE 0 END) AS RED
FROM yourTable
GROUP BY ID
SELECT *
FROM atable
PIVOT (
COUNT(column)
FOR value in ([DONE], [FAILED], [GREEN], [RED])
) p

Transforming multiple rows into columns w. single row

I need some help figuring out how best to transform an an array into a row-vector. My array looks like this:
+-----+-------+----------+
| ID | Grade | Quantity |
+-----+-------+----------+
| Ape | Water | Y |
| Ape | Juice | Y |
| Ape | Milk | Y |
+-----+-------+----------+
Each ID can have up to 4 rows distinguished only by grade (Water, Juice, Beer, Milk); the list of possible values is static.
My desired output is this:
+-----+----------+-------+-------+------+------+
| ID | Quantity | Water | Juice | Beer | Milk |
+-----+----------+-------+-------+------+------+
| Ape | Y | 1 | 1 | 0 | 1 |
+-----+----------+-------+-------+------+------+
My own efforts have carried me as far as the PIVOT-operator, which transforms Grade-values into columns, but it doesn't group the rows by ID, leaving me with an equal number of rows post-transformation.
SELECT ID, Quantity, Water, Juice, Beer, Milk
FROM
(SELECT ID, Grade, Quantity FROM Feeding WHERE ID = 'Ape') src
PIVOT(
COUNT(Quantity) FOR [Grade] IN (ID, Quantity, Water, Juice, Beer, Milk)
)AS TransformData
Output:
+-----+----------+-------+-------+------+------+
| ID | Quantity | Water | Juice | Beer | Milk |
+-----+----------+-------+-------+------+------+
| Ape | Y | 1 | 0 | 0 | 0 |
| Ape | Y | 0 | 1 | 0 | 0 |
| Ape | Y | 0 | 0 | 0 | 1 |
+-----+----------+-------+-------+------+------+
Any suggestions?
How about;
;WITH Feeding(id,grade,quantity) as (
select 'Ape','Water','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Juice','Y' union all
select 'Ape','Milk', 'N'
)
SELECT * FROM
(SELECT ID, Grade, Quantity agg, Quantity FROM Feeding WHERE ID = 'Ape') src
PIVOT ( COUNT(agg) FOR [Grade] IN (Water, Juice, Beer, Milk) ) AS TransformData
--
ID Quantity Water Juice Beer Milk
Ape N 0 0 0 1
Ape Y 1 4 0 0
you can try following query:-
SELECT ID, Quantity, CASE WHEN Grade = 'WATER' THEN 1 ELSE 0 END AS WATER,
CASE WHEN Grade = 'JUICE' THEN 1 ELSE 0 END AS JUICE,
CASE WHEN Grade = 'BEER' THEN 1 ELSE 0 END AS BEER,
CASE WHEN Grade = 'MILK' THEN 1 ELSE 0 END AS MILK
FROM YOUR_TABLE;
select id, quantity,
case when grade = 'Water' then 1 else 0 end as Water,
when grade = 'Juice' then 1 else 0 end as Juice,
when grade = 'Milk' then 1 else 0 end as Milk,
when grade = 'Beer' then 1 else 0 end as Beer
from feeding
As the value list is static, this is a way to do it.
Try this
SELECT id
,quantity
,CASE
WHEN grade = 'Water'
THEN 1
ELSE 0
END AS Water
,when grade = 'Juice' then 1 ELSE 0 END AS Juice
,when grade = 'Milk' then 1 ELSE 0 END AS Milk
,when grade = 'Beer' then 1 ELSE 0 END AS Beer
FROM feeding
Try this to get single row result
SELECT Id,Quantity,
SUM(CASE WHEN Grade='Water' THEN 1 ELSE 0 END) AS Water,
SUM(CASE WHEN Grade='Juice ' THEN 1 ELSE 0 END) AS Juice ,
SUM(CASE WHEN Grade='Beer' THEN 1 ELSE 0 END) AS Beer,
SUM(CASE WHEN Grade='Milk' THEN 1 ELSE 0 END) AS Milk
FROM Feed F
GROUP BY Id,Quantity
SQL Fiddle Demo

SQL query handling three tables

I want to compile the data for reporting purpose, using php communicating with postgres,
I have three tables
product_status:
status_code | value
------------|------
1 | a
2 | b
product_child:
code| ID | status_code
----|----|------------
X | 1 | 1
X | 2 | 1
X | 3 | 2
Y | 1 | 2
Y | 2 | 2
Z | 1 | 1
product_master:
code|description(and some other columns not relevent)
X | la
Y | alb
Z | lab
In the end I want basically a table like this which i'll display
| total child | status a | status b
bla | 3 | 2 | 1
alb | 2 | 0 | 2
lab | 1 | 1 | 0
I have tried
SELECT s.value, count(s.value)
FROM product_child p, product_status s
WHERE
p.product_status = s.status_code
and p.product_code = get_product_code('Sofa (1-seater) J805')
group by
s.value
it gives we grouping for particular code but I want this flipped and appended in front of distinct product_codes
do you mean like this?
select pm.description,
count(pc.code) total,
count(case when ps.value = 'A' then ps.value else null end) statusA,
count(case when ps.value = 'B' then ps.value else null end) statusB
from product_master pm join product_child pc on pm.code = pc.code
join product_status ps on pm.status_code = ps.status_code
group by pm.description