Find players who have all "win", "draw" and "loss" results - sql

player1_id | score1 | score2 | player2_id
-----------+--------+--------+-----------
1 | 1 | 1 | 2
3 | 1 | 1 | 1
11 | 1 | 0 | 20
5 | 1 | 1 | 55
200 | 1 | 2 | 11
17 | 1 | 1 | 7
11 | 1 | 3 | 4
11 | 1 | 1 | 100
20 | 1 | 1 | 2
20 | 2 | 1 | 33
Player have "win", "draw" or "loss" results according to score1 and score2. I need find players, who have all "win", "draw" and "loss" results. In this case, players 11 and 20.
I am stuck here, any help greatly appreciated.

If I correctly understand, you need this:
select p from (
select player1_id as p, case when score1>score2 then 'W' when score1=score2 then 'D' when score1<score2 then 'L' end as res from your_table
union all
select player2_id as p, case when score1>score2 then 'L' when score1=score2 then 'D' when score1<score2 then 'W' end as res from your_table
) t
group by p
having count( distinct res ) = 3

You need to get all the players into one column, along with the scores or an indicator of the groups that you want:
select p1
from ((select player1_id as p1, player2_id as p2, score1 as s1, score2 as s2
from t
) union all
(select player2_id as p1, player1_id as p2, score2 as s1, score1 as s2
from t
)
) t
group by p1
having sum( (s1 > s2)::int) > 0 and
sum( (s1 = s2)::int) > 0 and
sum( (s1 < s2)::int) > 0;

Related

SQL Server dynamically sum rows based on other rows

Here's the situation: I need a way to total sales of a certain class of item every month. Easy enough, right?
Except sometimes, the item will be suppressed (with 0 price) and a special item will be put on the order with the price. I solved this by looking for suppressed lines and using LAG to pull the price from the special item on the line below it:
CASE
WHEN olu.supress_print = 'Y'
THEN LAG(shrv.sales_price_home, 1, 0) OVER (ORDER BY shrv.order_no, pvol.line_seq_no DESC)
ELSE shrv.sales_price_home
END AS total_sales
However, I recently discovered that sometimes they will split the suppressed item into multiple "special" lines. I'm trying to dynamically sum rows of certain trigger items until the row below the trigger item contains a non-special item. I'll illustrate with a table:
item_id
qty_ordered
tot_price
line_seq
suppress_print
A
10
150
1
N
B
10
0
2
Y
SPECIAL
4
140
3
N
SPECIAL
6
90
4
N
SPECIAL
8
70
8
N
SPECIAL
6
80
9
N
So in this example, I'd like the prices for lines 2, 3, and 4 summed and rolled into one line. I really only need the total price and ideally to be able to preserve item id "B".
I'm trying to think of a way to solve this using exclusively SQL. I know I could write a script to do it, but I'd like to limit this to just SQL if possible.
Edit - unfiltered table (imagine 2 is the item class I want the sum of sales for):
item_id
qty_ordered
tot_price
line_seq
suppress_print
class
A
10
150
1
N
2
B
10
0
2
Y
2
SPECIAL
4
140
3
N
NULL
SPECIAL
6
90
4
N
NULL
C
5
80
5
N
NULL
D
3
50
6
N
NULL
D
14
0
7
N
NULL
SPECIAL
8
70
8
N
NULL
SPECIAL
6
80
9
N
NULL
Edit 2 - expected results:
item_id
qty_ordered
tot_price
line_seq
suppress_print
class
A
10
150
1
N
2
B
10
230
2
Y
2
C
5
80
5
N
NULL
D
3
50
6
N
NULL
D
14
0
7
N
NULL
SPECIAL
8
70
8
N
NULL
SPECIAL
6
80
9
N
NULL
Here's something based on your unfiltered table.
I didn't attempt to limit the logic to a specific class.
But that could be added easily, at the end, or as needed.
I also didn't really need the suppress_print column in the logic.
We could also easily exclude the 'D' items from the SPECIAL logic. Based on the summed qty values and the 0 tot_price, I guessed we should treat them specially too. That's easily adjusted.
We handle this much like an edges case, creating groups in the first groups CTE term.
Then, in the sums CTE term, use these groups to combine / SUM the SPECIAL rows within their groups / partitions. The rows associated with non-SPECIAL cases are in their own group, so can be summed as well.
The final query expression just takes the edge rows, which causes the SPECIAL rows to be hidden and the leading item_id shown only, as requested.
Here's the SQL Server test case:
Working Test Case (Updated)
and the corresponding solution:
WITH groups AS (
SELECT t.*
, SUM(CASE WHEN item_id <> 'SPECIAL' THEN 1 END) OVER (ORDER BY line_seq) AS seq
, CASE WHEN item_id <> 'SPECIAL' THEN 1 END AS edge
FROM unfiltered AS t
)
, sums AS (
SELECT item_id, qty_ordered
, line_seq, suppress_print, class
, SUM(tot_price) OVER (PARTITION BY seq) AS tot_price
, edge
FROM groups
)
SELECT item_id, qty_ordered, tot_price
, line_seq, suppress_print, class
FROM sums
WHERE edge = 1
;
Result:
+---------+-------------+-----------+----------+----------------+-------+
| item_id | qty_ordered | tot_price | line_seq | suppress_print | class |
+---------+-------------+-----------+----------+----------------+-------+
| A | 10 | 150 | 1 | N | 2 |
| B | 10 | 230 | 2 | Y | 2 |
| C | 5 | 80 | 5 | N | NULL |
| D | 3 | 50 | 6 | N | NULL |
| D | 14 | 150 | 7 | N | NULL |
+---------+-------------+-----------+----------+----------------+-------+
Both 'B' and the second 'D' item are summed as described in the question description.
The data in the unfiltered table:
+---------+-------------+-----------+----------+----------------+-------+
| item_id | qty_ordered | tot_price | line_seq | suppress_print | class |
+---------+-------------+-----------+----------+----------------+-------+
| A | 10 | 150 | 1 | N | 2 |
| B | 10 | 0 | 2 | Y | 2 |
| SPECIAL | 4 | 140 | 3 | N | NULL |
| SPECIAL | 6 | 90 | 4 | N | NULL |
| C | 5 | 80 | 5 | N | NULL |
| D | 3 | 50 | 6 | N | NULL |
| D | 14 | 0 | 7 | N | NULL |
| SPECIAL | 8 | 70 | 8 | N | NULL |
| SPECIAL | 6 | 80 | 9 | N | NULL |
+---------+-------------+-----------+----------+----------------+-------+
and the following actually produces the explicit requested result.
I haven't tried to reduce this. The requirement to restrict the behavior to a specific class added work. There were a couple of places I could have re-stated expressions to avoid additional CTE terms. Feel free to collapse them.
I also regenerated the groups (seq) a second time, once the main class logic was handled.
WITH groups AS (
SELECT t.*
, SUM(CASE WHEN item_id <> 'SPECIAL' THEN 1 END) OVER (ORDER BY line_seq) AS seq
, CASE WHEN item_id <> 'SPECIAL' THEN 1 END AS edge
FROM unfiltered AS t
)
, classes AS (
SELECT item_id, qty_ordered, tot_price
, line_seq, suppress_print
, edge, seq
, MAX(class) OVER (PARTITION BY seq) AS class
FROM groups
)
, edges AS (
SELECT item_id, qty_ordered, tot_price
, line_seq, suppress_print
, class
, CASE WHEN edge = 1 OR class IS NULL THEN 1 END AS edge
, SUM(CASE WHEN edge = 1 OR class IS NULL THEN 1 END) OVER (ORDER BY line_seq) AS seq
FROM classes
)
, sums AS (
SELECT item_id, qty_ordered
, line_seq, suppress_print, class
, SUM(tot_price) OVER (PARTITION BY seq) AS tot_price
, edge
FROM edges
)
SELECT item_id, qty_ordered, tot_price
, line_seq, suppress_print, class
FROM sums
WHERE edge = 1
;
Result:
+---------+-------------+-----------+----------+----------------+-------+
| item_id | qty_ordered | tot_price | line_seq | suppress_print | class |
+---------+-------------+-----------+----------+----------------+-------+
| A | 10 | 150 | 1 | N | 2 |
| B | 10 | 230 | 2 | Y | 2 |
| C | 5 | 80 | 5 | N | NULL |
| D | 3 | 50 | 6 | N | NULL |
| D | 14 | 0 | 7 | N | NULL |
| SPECIAL | 8 | 70 | 8 | N | NULL |
| SPECIAL | 6 | 80 | 9 | N | NULL |
+---------+-------------+-----------+----------+----------------+-------+
Using APPLY to get parent info for 'SPECIAL's of item with suppress_print = 'Y'
WITH grp AS (
SELECT -- all but tot_price from parent
coalesce(parent.item_id, itm.item_id) item_id,
coalesce(parent.qty_ordered, itm.qty_ordered) qty_ordered,
itm.tot_price,
coalesce(parent.line_seq, itm.line_seq) line_seq,
coalesce(parent.suppress_print, itm.suppress_print) suppress_print,
coalesce(parent.class, itm.class) class
FROM myTbl itm
OUTER APPLY (
SELECT t3.*
FROM (
SELECT top(1) t2.*
FROM myTbl t2
WHERE itm.item_id = 'SPECIAL' AND t2.line_seq < itm.line_seq AND t2.item_id != 'SPECIAL'
ORDER BY line_seq DESC
) t3
WHERE t3.suppress_print = 'Y'
) parent
)
select item_id, qty_ordered, sum(tot_price) tot_price, line_seq, suppress_print, class
from grp
group by item_id, qty_ordered, line_seq, suppress_print, class
order by line_seq

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.

Select specific columns by their alias, and later order by it

My table are the following
+----+----------+--------+
| id | priority | User |
+----+----------+--------+
| 1 | 2 | [null] |
| 2 | 1 | [null] |
| 3 | 3 | Tony |
| 4 | 2 | John |
| 5 | 2 | Andy |
| 6 | 1 | Mike |
+----+----------+--------+
My goal is to extract them, and order by the following combined conditions:
priority = 1
User is null
+----+----------+--------+-----------+
| id | priority | User | peak_rows |
+----+----------+--------+-----------+
| 1 | 2 | [null] | 1 |
| 2 | 1 | [null] | 1 |
| 6 | 1 | Mike | 0 |
| 3 | 3 | Tony | 1 |
| 4 | 2 | John | 0 |
| 5 | 2 | Andy | 0 |
+----+----------+--------+-----------+
This is what I guess I can do
select
id,
CASE WHEN priority = 1 THEN 1 ELSE 0 END as c1,
CASE WHEN User is NULL THEN 1 ELSE 0 END as c2,
c1 + c2 AS peak_rows
FROM mytable
ORDER BY peak_rows DESC
but it cause an error:
ERROR: column "c1" does not exist
LINE 5: c1+ c2as pp
^
SQL state: 42703
Character: 129
I don't know why I make 2 columns(c1 and c2), but I can not use it later.
Any good idea to do that?
You are not making two columns and using them later, you are making them and want to use them at the same time. You could use a subquery.
SELECT a.id, a.priority, a.User, a.c1 + a.c2 AS peak_rows
FROM
(SELECT id,
priority,
User,
CASE WHEN priority = 1 THEN 1 ELSE 0 END as c1,
CASE WHEN User IS NULL THEN 1 ELSE 0 END as c2,
FROM mytable) a
ORDER BY peak_rows DESC;
select
id,
CASE WHEN priority = 1 THEN 1 ELSE 0 END as c1,
CASE WHEN User is NULL THEN 1 ELSE 0 END as c2,
(CASE WHEN priority = 1 THEN 1 ELSE 0) + ( CASE WHEN User is NULL THEN 1 ELSE 0 END) AS peak_rows
FROM mytable
ORDER BY peak_rows DESC
I suppose your aim is to order by those c1 and c2, so you can directly use in the order by clause. You just need to interchange 0 and 1 in the case..when statements. And depending on your priority=1 criteria id=2 must stay at the top.
with mytable( id, priority, "User" ) as
(
select 1 , 2, null union all
select 2, 1, null union all
select 3, 3, 'Tony' union all
select 4, 2, 'John' union all
select 5, 2, 'Andy' union all
select 6, 1, 'Mike'
)
select *
from mytable
order by ( case when priority = 1 then 0 else 1 end ) +
( case when "User" is null then 0 else 1 end );
id priority User
-- -------- -------
2 1 [null]
1 2 [null]
6 1 Mike
3 3 Tony
4 2 John
5 2 Andy
Demo

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