SQL to group by 2 IDs - sql

So I have a table that's laid out like this:
table1:
ID | metric1 | metric2
A | 1 | 1
A | 1 | 1
B | 2 | 3
C | 3 | 2
And another table that may have an alternate ID an item may have (note that the new ID will also be in the table above). Example:
conversions:
old_ID | new_ID
A | C
So I'm trying to create a query that aggregates both on the new ID and the old-ID, but also preserves the old-ID if available. So basically the results I want look like this:
ID | potential_old_ID | metric1 | metric2
C | A | 5 | 4
B | NULL | 2 | 3
So far with my current strategy I've been able to get close with a query like this:
select
(CASE WHEN new_ID is null then ID else new_ID END) as ID,
(CASE WHEN new_ID is null then null else ID END) as potential_old_ID,
SUM(metric1),
SUM(metric2)
from table1
left join conversions on ID = old_ID
group by ID, new_ID
Which get's me close, but it still separates C and A in separate rows, which doesn't work for my use case:
ID | potential_old_ID | metric1 | metric2
C | A | 2 | 2
B | NULL | 2 | 3
C | NULL | 3 | 2
If I remove the new_ID from the group by I get an error on the query. Anyway I can get the results I'm looking for that I'm missing?

You need to make sure that the rows that have C already also have the same potential old ID as the ones that are A. Something like
SELECT ID, potential_old_ID, SUM(metric1), SUM(metric2)
FROM
( select
(CASE WHEN c1.new_ID is null then ID else c1.new_ID END) as ID,
COALESCE(c1.old_ID, c2.old_ID) as potential_old_ID,
metric1,
metric2
from table1
left join conversions c1 on ID = c1.old_ID
left join conversions c2 on ID = c2.new_ID
) AS data
GROUP BY ID, potential_old_ID

Hmmmm . . . I think this does what you want:
select coalesce(new_id, id) as id,
SUM(metric1),
SUM(metric2)
from table1 left join
conversions
on ID = old_ID
group by coalesce(new_id, id);

Related

Count IDs in Column A that are not repeated in Column B - SQL

I have been with this query for two days and read many posts, but still can't figure out how to handle this situation.
My table is like this:
+------+------+
| Type | ID |
+------+------+
| A | 1339 |
| A | 1156 |
| B | 1156 |
| A | 1192 |
| B | 1214 |
| B | 1202 |
| C | 1202 |
| A | 1207 |
| B | 1207 |
| C | 1207 |
| B | 1241 |
+------+------+
I need to count how many IDs of B there are, but that ID is not repeated in A.
In detail, two criteria should be reflected:
Criterion 1: How many IDs does B have ONLY in B?
Criterion 2: How many IDs does B have in A and B?
C does not matter in this situation.
My expected result should be something like this:
+---------------+-----------+
| Ds in A and B | IDs in B |
+---------------------------+
| 2 | 4 |
+---------------+-----------+
It seems that it can be something like this:
select Count(Id) -- Or if we want distinct Ids: Count(Distinct Id)
from MyTable
where Type = 'A' -- Id has Type 'A'
and Id not in (select b.Id -- Not appears among of type 'B'
from MyTable b
where b.Type = 'B')
Here we get all Id which are have type A, but not B; to find Ids which have A type only:
select Count(Id) -- Or if we want distinct Ids: Count(Distinct Id)
from MyTable
where Type = 'A'
and Id not in (select b.Id
from MyTable b
where b.Type <> 'A')
To get Ids that have both type A and B just change not in into in (or do self join):
select Count(Id) -- Or if we want distinct Ids: Count(Distinct Id)
from MyTable
where Type = 'A'
and Id in (select b.Id
from MyTable b
where b.Type = 'B')
Try using COUNT and DISTINCT. But do not forget a WHERE condition to select B type rows. This is the type of query you get end up with :
SELECT COUNT(DISTINCT ID) FROM table WHERE Type = "B"
how IDs in B equal 4? There are five IDs in the B and tree id in (B and not in A)
select COUNT(DISTINCT ID) as AandB from tTable where Type='B' and ID
in(select id from tTable where Type='A')
select COUNT(DISTINCT ID) as B from tTable where Type='B'
select COUNT(DISTINCT ID) as Bnot_inA from tTable where Type='B' and ID not
in(select id from tTable where Type='A')

Get rows with multiple conditions

Below is my Postgres table:
Table:
+------+-----------------+---------+
| sku | properties | value |
|------+-----------------+---------|
| 1 | Family_ID | 21 |
| 1 | Class_ID | 21 |
| 2 | Family_ID | 20 |
| 2 | Class_ID | 21 |
| 3 | Family_ID | 21 |
| 3 | Class_ID | 21 |
+------+-----------------+---------+
How to query if I want to fetch data where the Family_ID and Class_ID is 21.
The expected return value:
+------+-----------------+---------+
| sku | properties | value |
|------+-----------------+---------|
| 1 | Family_ID | 21 |
| 1 | Class_ID | 21 |
| 3 | Family_ID | 21 |
| 3 | Class_ID | 21 |
+------+-----------------+---------+
How to query if I want to fetch data where the Family_ID is 20 and Class_ID is 21.
The expected return value:
+------+-----------------+---------+
| sku | properties | value |
|------+-----------------+---------|
| 2 | Family_ID | 20 |
| 2 | Class_ID | 21 |
+------+-----------------+---------+
This query:
select sku
from tablename
group by sku
having
max(case when properties = 'Family_ID' then value end) = 21
and
max(case when properties = 'Class_ID' then value end) = 21
returns all the skus that satisfy your conditions and you can use it with the operator IN like this:
select * from tablename
where sku in (
select sku
from tablename
group by sku
having
max(case when properties = 'Family_ID' then value end) = 21
and
max(case when properties = 'Class_ID' then value end) = 21
)
You could also use MAX() window function:
select t.sku, t.properties, t.value
from (
select *,
max(case when properties = 'Family_ID' then value end) over (partition by sku) family_id,
max(case when properties = 'Class_ID' then value end) over (partition by sku) class_id
from tablename
) t
where t.family_id = 21 and t.class_id = 21
See the demo.
Results:
> sku | properties | value
> --: | :--------- | ----:
> 1 | Family_ID | 21
> 1 | Class_ID | 21
> 3 | Family_ID | 21
> 3 | Class_ID | 21
To operate across rows you need to group, but the easiest thing here (given that you seem like you will want ever more variations of this theme) is probably to pivot your data:
WITH x as(
SELECT f.sku, c.value as class_value, f.value as family_value
FROM
(select sku, value FROM table WHERE properties = 'family_id') f
INNER JOIN
(select sku, value FROM table WHERE properties = 'class_id') c
ON f.sku = c.sku
)
You can now use a WHERE clause like normal:
SELECT * FROM x WHERE family_value = 20 and class_value = 21
If you need the data back in a column format, you can unpivot it again:
SELECT
sku,
'family_id' as properties,
family_value as value
FROM
x
UNION ALL
SELECT
sku,
'class_id' as properties,
class_value as value
FROM
x
But it might be easier to just work with the data in its pivoted form.
I wouldn't necessarily do this myself, but its a lot easier to understand this form of pivoting if you're used to regular joining, union and other similar "typical" database operations, so I'm recommending this as you may well find it easier to maintain and extend. Conditional aggregation to do a pivot is potentially more efficient but more complex to maintain:
WITH X as (
SELECT
sku,
MAX(CASE WHEN properties = 'Family_ID' THEN value END) as family_value,
MAX(CASE WHEN properties = 'Class_ID' THEN value END) as class_value
FROM
table
GROUP BY sku
)
The CASE WHENs spread the values across columns according to the properties value. The group by/max then collapse the rows removing the nulls leaving you with a unique sku and the values in named columns according to what kind of value they are
--case 1
select *
from tbl t
where exists (select *
from tbl t1
where t1.sku=t.sku
and t1.properties='Family_ID'
and t1.value=21)
and exists (select *
from tbl t1
where t1.sku=t.sku
and t1.properties='Class_ID'
and t1.value=21)
--case 2
select *
from tbl t
where exists (select *
from tbl t1
where t1.sku=t.sku
and t1.properties='Family_ID'
and t1.value=20)
and exists (select *
from tbl t1
where t1.sku=t.sku
and t1.properties='Class_ID'
and t1.value=21)

select column1 from table A based on unique value of another column2 in table B

I have table A and table B and need to select a column1 from table A based on unique value of another column in table B
table A
id | product |
1 | A |
1 | B |
1 | A |
2 | A |
3 | B |
4 | A |
table B
id | product | date
1 | A | 1/01/2017
1 | B | 1/02/2017
1 | A | 1/01/2017
2 | A | 1/01/2017
3 | B | 1/02/2017
4 | A | 1/01/2017
I want the output to be : 2,3,4
i.e. all the 'id's' which have a unique value in 'date' column of table B
Depending upon the actual restrictions in your tables, there are a couple of options.
Option 1 - assuming that for example ID=1, Product=A, date=1/01/2017 and ID=1, Product=B, date=1/01/2017 means that ID=1 IS NOT included in your final result as it has 2 entries for the date = 1/01/2017 even though they are for different Products
SELECT a.ID
FROM
(
SELECT ID, COUNT(*)
FROM TableB
GROUP BY ID
HAVING COUNT(*) = 1
) a
Option 2 - assuming that for example ID=1, Product=A, date=1/01/2017 and ID=1, Product=B, date=1/01/2017 means that ID=1 IS included in your final result as it only has a single date for each ID/Product combination
SELECT DISTINCT ID
FROM
(
SELECT ID, Product, COUNT(*)
FROM TableB
GROUP BY ID, Product
HAVING COUNT(*) = 1
) a

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

Using CASE for a specific situation - How TO

I'm trying to find the proper SQL for the following situation:
Supposed we have two tables:
TABLE A
ID int,
TEXT varchar(200)
TABLE B
ID int,
A_NO int,
B_NO int
Fields named "ID" on both tables can be join to link tables.
The following SQL:
SELECT
A.ID,
B.A_NO,
B.B_NO
FROM
A
LEFT JOIN
B
ON A.ID = B.ID
ORDER BY A.ID, B.A_NO, B.B_NO
gives the following results:
Now, the problem.
What is asked for is to have in the column B_NO a value = 1 for the MIN value of column A_NO and a value = 0 for all the others row with the same A_NO value.
The results below are expected:
Please note that, in this example, we can find two rows for each B_NO value but it is possible to have more than 2 rows.
I have tried to reproduce these results by using a CASE but with no success.
Thanks for you help in advance,
Bouzouki.
Try this using CTE and ROW_NUMBER(); (DEMO)
Please note: I have considered myT as your joined query of A and B tables for demo purpose. So replace myT with as yours A LEFT JOIN B ON A.ID = B.ID.
;with cte as (
select id, a_no, b_no,
row_number() over(partition by id,b_no order by a_no) rn
from myT
)
select id,a_no, case when rn=1 then b_no else 0 end b_no
from cte
order by a_no
--RESULTS FROM DEMO TABLE
| ID | A_NO | B_NO |
-------------------------
| 1031014 | 1 | 1 |
| 1031014 | 2 | 0 |
| 1031014 | 3 | 2 |
| 1031014 | 4 | 0 |
| 1031014 | 5 | 3 |
| 1031014 | 6 | 0 |
| 1031014 | 7 | 4 |
| 1031014 | 8 | 0 |
| 1031014 | 9 | 5 |
| 1031014 | 10 | 0 |
something like
select ID, a_no, b_no,
case when a_no = min_a_no then b_no else 0 end as new_b_no
from
a left join b on a.id = b.id left join
(Select ID, B_no, min(a_no) as min_a_no
from a left join b on a.id = b.id
group by id, b_no) m on a.id = m.id and b.b_no = m.b_no
ORDER BY A.ID, B.A_NO