Aggregation for multiple SQL SELECT statements - sql

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]

Related

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)

Single query to split out data of one column, into two columns, from the same table based on different criteria [SQL]

I have the following data in a table, this is a single column shown from a table that has multiple columns, but only data from this column needs to be pulled into two column output using a query:
+----------------+--+
| DataText | |
| 1 DEC20 DDD | |
| 1 JUL20 DDD | |
| 1 JAN21 DDD | |
| 1 JUN20 DDD500 | |
| 1 JUN20 DDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUN20DDDD500 | |
| 1 JUL20 DDD800 | |
| 1 JUL20 DDD800 | |
| 1 JUL20DDDD800 | |
| 1 JUL20DDDD400 | |
| 1 JUL20DDDD400 | |
+----------------+--+
Required result: distinct values based on the first 13 characters of the data, split into two columns based on "long data", and "short data", BUT only giving the first 13 characters in output for both columns:
+-------------+-------------+
| ShortData | LongData |
| 1 DEC20 DDD | 1 JUN20 DDD |
| 1 JUL20 DDD | 1 JUN20DDDD |
| 1 JAN21 DDD | 1 JUL20 DDD |
| | 1 JUL20DDDD |
+-------------+-------------+
Something like:
Select
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)=13) As ShortData
,
(Select DISTINCT LEFT(DataText,13)
From myTable)
Where LEN(DataText)>13) As LongData
I would also like to query/"scan" the table only once if possible. I can't get any of the SO examples modified to make such a query work.
This is quite ugly, but doable. As a starter, you need a column that defines the order of the rows - I assumed that you have such a column, and that is called id.
Then you can select the distinct texts, put them in separate groups depending on their length, and finally pivot:
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by id) rn
from (
select
id,
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from (select min(id) id, dataText from mytable group by dataText) t
) t
) t
group by rn
If you are content with ordering the records by the string column itself, it is a bit simpler (and, for your sample data, it produces the same results):
select
max(case when grp = 0 then dataText end) shortData,
max(case when grp = 1 then dataText end) longData
from (
select
dataText,
grp,
row_number() over(partition by grp order by dataText) rn
from (
select distinct
case when len(dataText) <= 13 then 0 else 1 end grp,
substring(dataText, 1, 13) dataText
from mytable
) t
) t
group by rn
Demo on DB Fiddle:
shortData | longData
:---------- | :------------
1 DEC20 DDD | 1 JUL20 DDD80
1 JAN21 DDD | 1 JUL20DDDD40
1 JUL20 DDD | 1 JUL20DDDD80
null | 1 JUN20 DDD50
null | 1 JUN20DDDD50

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

How to make 2 columns from one in one select in sqlite?

I've got one database with two columns (id and value). There are two types of values and each id has both of this values. How can I make a select to this database to have three columns in result (id, value1 and value2)
I've tried CASE and GROUP BY, but it shows only one result of each id
Example of a db:
| id | value |
| 0 | a |
| 0 | b |
| 1 | a |
| 1 | b |
Example of the result I am looking for is:
| id | value_a | value_b |
| 0 | a | b |
| 1 | a | b |
UPDATE:
As it was noted in comments, there is too simple data in the example.
The problem is more complicated
An example that would better describe it:
DB:
| id | value | value2 | value3 |
| 0 | a | a2 | a3 |
| 0 | b | b2 | b3 |
| 1 | a | c2 | c3 |
| 1 | b | d2 | d3 |
RESULT:
| id | value_a | value_b | value2_a | value2_b | value3_a | value3_b |
| 0 | a | b | a2 | b2 | a3 | b3 |
| 1 | a | b | c2 | d2 | c3 | d3 |
The output should be sorted by id an have all info from the both rows of each id.
If there are always two values per ID, you can try an aggregation using min() and max().
SELECT id,
min(value) value_a,
max(value) value_b
FROM elbat
GROUP BY id;
select t0.id,t0.Value as Value_A, t1.Value as Value_B
from test t0
inner join test t1 on t0.id = t1.id
where t0.Value = 'a' and t1.value = 'b';
I have used this method to turn "rows" into "columns". Depending on the number of unique values that exist in the table, you may or may not want to use this :)
SELECT id, SUM(CASE WHEN value = "a" then 1 else 0 END) value_a,
SUM(CASE WHEN value = "b" then 1 else 0 END) value_b,
SUM(CASE WHEN value = "c" then 1 else 0 END) value_c,
SUM(CASE WHEN value ="a2" then 1 else 0 END) value_a2,
.
.
.
FROM table
GROUP BY id;
Thanks all for the answers! This is the way how I did this:
WITH a_table AS
(
SELECT id, value, value2, value3 FROM table1 WHERE table1.value = 0
),
b_table AS
(
SELECT id, value, value2, value3 FROM table1 WHERE table1.value = 1
)
SELECT DISTINCT
a_table.id AS id,
a_table.value AS value_a,
a_table.value2 AS value2_a,
a_table.value3 AS value3_a,
b_table.value AS value_b,
b_table.value2 AS value2_b,
b_table.value3 AS value3_b
FROM a_table
JOIN b_table ON a_table.id = b_table.id
GROUP BY id;

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.