Get range (min - max) of values concatenated in a single row - sql

given the following table
+-----------------------------+
| id | type | price | item_id |
|-----------------------------|
| 1 | 1 | 20 | 22 |
|-----------------------------|
| 2 | 1 | 22 | 22 |
|-----------------------------|
| 3 | 2 | 19 | 22 |
|-----------------------------|
| 4 | 2 | 11 | 22 |
|-----------------------------|
| 5 | 1 | 08 | 22 |
|-----------------------------|
| 6 | 2 | 25 | 22 |
+-----------------------------+
I am trying to select the data to create a view as follows in a single row
+-------------------------------------+
| type1_range | type2_range | item_id |
|-------------------------------------|
| 08 - 22 | 11 - 25 | 22 |
+-------------------------------------+
type1_range and type2_range are the minimum and maximum price for each types.
I can get the data in couple of rows using
SELECT type, MAX (price) , MIN (price)
FROM table
where item_id=22 GROUP BY type;
+----------------------------+
| type | max | min | item_id |
|----------------------------|
| 1 | 22 | 08 | 22 |
|----------------------------|
| 2 | 25 | 11 | 22 |
+----------------------------+
But I am trying to concat the rows like this:
+-------------------------------------+
| type1_range | type2_range | item_id |
|-------------------------------------|
| 08 - 22 | 11 - 25 | 22 |
+-------------------------------------+
What would be sql required for this?

Something like this:
SELECT
CONCAT(
MIN(CASE WHEN type = 1 THEN price END),
' - ',
MAX(CASE WHEN type = 1 THEN price END)
) as type1range,
CONCAT(
MIN(CASE WHEN type = 2 THEN price END),
' - ',
MAX(CASE WHEN type = 2 THEN price END)
) as type2range.
item_id
FROM table
WHERE item_id = 22
GROUP BY item_id
You've tagged two different database systems (please avoid doing this) but I believe they do both support CONCAT() for string concatenation
If you want to omit the item_id from the select list (you already know it's item 22) you can remove the GROUP BY. Alternatively if you remove the WHERE and leave the group by you'll get a row for each item_id
To get more of an idea as to how it works, remove the concat and the min/max - you'll see that the case when causes the price to show up only if the type is 1 (in the type 1 range column) otherwise it's null. It's the. Trivial for the min and max to work on just type 1 or just type 2 data for each column. It's actually a form of pivot query if you want to read up on them more

A straight forward approach would be having type1_range and type2_range as two sub-queries and join with the distinct id's like shown below,
SELECT t.item_id,type1_range,type2_range
FROM (Select distinct item_id from table) t
LEFT join
(SELECT item_id,type, concat(MIN(price),'-' ,MAX(price) ) as type1_range
FROM table
where type=1
GROUP BY item_id,type)type1 on type1.item_id=t.item_id
LEFT join
(SELECT item_id,type, concat(MIN(price),'-' ,MAX(price) ) as type2_range
FROM table
where type=2
GROUP BY item_id,type)type2 on type2.item_id=t.item_id

Related

Pivot the row with column's names into a column using SQL

I have one table with 25 columns and 1 single row and I want to pivot it
and obtain a column with the names of the columns and a column with the corresponding values using SQL. But I do not know how to do it.
I have the following table:
+-------+-------+-------+-----+--------+
| cnt_0 | cnt_1 | cnt_2 | ... | cnt_25 |
+-------|-------|-------+-----|--------+
| 34. | 26 | 15 | ... | 5 |
+-------+-------+-------+-----+--------+
And I want to pivot the table and transform the row of the column names into. a column and obtain this:
+--------+--------+
| counts | amount |
+--------+--------+
| cnt_0 | 34. |
| cnt_1 | 26 |
| cnt_2 | 15 |
| ... | ... |
| cnt_25 | 5 |
+--------+--------+
Verbose, but you could use UNION ALL:
SELECT counts, amount
FROM
(
SELECT 'cnt_0' AS counts, cnt_0 AS amount, 0 AS pos UNION ALL
SELECT 'cnt_1', cnt_1, 1 UNION ALL
SELECT 'cnt_2', cnt_2, 2 UNION ALL
...
SELECT 'cnt_25', cnt_25, 25
) t
ORDER BY pos;

Make a query making groups on the same result row

I have two tables. Like this.
select * from extrafieldvalues;
+----------------------------+
| id | value | type | idItem |
+----------------------------+
| 1 | 100 | 1 | 10 |
| 2 | 150 | 2 | 10 |
| 3 | 101 | 1 | 11 |
| 4 | 90 | 2 | 11 |
+----------------------------+
select * from items
+------------+
| id | name |
+------------+
| 10 | foo |
| 11 | bar |
+------------+
I need to make a query and get something like this:
+--------------------------------------+
| idItem | valtype1 | valtype2 | name |
+--------------------------------------+
| 10 | 100 | 150 | foo |
| 11 | 101 | 90 | bar |
+--------------------------------------+
The quantity of types of extra field values is variable, but every item ALWAYS uses every extra field.
If you have only two fields, then left join is an option for this:
select i.*, efv1.value as value_1, efv2.value as value_2
from items i left join
extrafieldvalues efv1
on efv1.iditem = i.id and
efv1.type = 1 left join
extrafieldvalues efv2
on efv1.iditem = i.id and
efv1.type = 2 ;
In terms of performance, two joins are probably faster than an aggregation -- and it makes it easier to bring in more columns from items. One the other hand, conditional aggregation generalizes more easily and the performance changes by little as more columns from extrafieldvalues are added to the select.
Use conditional aggregation
select iditem,
max(case when type=1 then value end) as valtype1,
max(case when type=2 then value end) as valtype2,name
from extrafieldvalues a inner join items b on a.iditem=b.id
group by iditem,name

SQL select with preference on column values

I am new to SQL and I would like to ask about how to select entries based on preferences and grouping.
+----------+----------+------+
| ENTRY_ID | ROUTE_ID | TYPE |
+----------+----------+------+
| 1 | 15 | 0 |
| 1 | 26 | 1 |
| 1 | 39 | 1 |
| 2 | 22 | 1 |
| 2 | 15 | 1 |
| 3 | 30 | 1 |
| 3 | 35 | 0 |
| 3 | 40 | 1 |
+----------+----------+------+
With the table above, I would like to select 1 entry for each ENTRY_ID with the following preference for the returned ROUTE_ID:
IF TYPE = 0 is available
for any one of the entries with the same ENTRY_ID, return the minimum ROUTE_ID for all entries with TYPE = 0
IF for the same ENTRY_ID only TYPE = 1 is available, return the minimum ROUTE_ID
The expected outcome for the query will be the following:
+----------+----------+------+
| ENTRY_ID | ROUTE_ID | TYPE |
+----------+----------+------+
| 1 | 15 | 0 |
| 2 | 15 | 1 |
| 3 | 35 | 0 |
+----------+----------+------+
Thank you for your help!
You can group by both TYPE and ENTRY_ID, and then use the HAVING clause to filter out those where TYPE is not the minimal value for that record.
SELECT ENTRY_ID, MIN(ROUTE_ID), TYPE
FROM MyTable
GROUP BY ENTRY_ID, TYPE
HAVING TYPE = (SELECT MIN(s.TYPE) FROM MyTable s WHERE s.ENTRY_ID = MyTable.ENTRY_ID)
This relies on type only being able to be 0 or 1. If there are more possible values, it will only return the lowest type.
If you want complete rows, use a correlated subquery:
select t.*
from t
where t.route_id = (select top 1 t2.route_id
from t as t2
where t2.entry_id = t.entry_id
order by iif(t2.type = 0, 1, 2), -- put type 0 first
t2.route_id asc -- then the first route_id
);
This has the advantage that it can return more than just the three columns you show in the question.

calculating sum of rows with identical id

Let's imagine a table with two columns ex:
| Value | ID |
+-------+----+
| 2 | 1 |
| 3 | 1 |
| 4 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 2 |
What I am trying to do is to calculate the sum of those with similar id and display them in different table like:
| Sum | ID |
+-----+----+
| 9 | 1 |
| 5 | 2 |
and so on.
I could find a sum of a known id by
SELECT SUM(VALUE) FROM MYTABLE WHERE ID = 1;
However not sure on how to find sum of different id's separately, could you give an idea on how to proceed?
Select SUM(VALUE),ID FROM MYTABLE GROUP BY ID
Use GROUP BY clause:
SELECT SUM(VALUE) Sum, ID FROM MYTABLE GROUP BY ID;
SELECT SUM(VALUE),ID FROM MYTABLE Group By ID

Using lag() for dynamic rows postgresql

I have a table named 'quantity' as below
Table 1 :
----------------------------------------------------
Price | A_Quantity | B_Quantity | Remaining_quantity
----------------------------------------------------
30 | 17 | 2 | 0
32 | 17 | 4 | 0
33 | 17 | 4 | 0
----------------------------------------------------
I want the field - Remaining_quantity to be filled as below
Table 2 :
----------------------------------------------------
Price | A_Quantity | B_Quantity | Remaining_quantity
----------------------------------------------------
30 | 17 | 2 | 15
32 | 17 | 4 | 11
33 | 17 | 4 | 7
----------------------------------------------------
I have used the below query, but it doesn't give the expected result.
update quantity set remaining_quantity = remaining_quantity -
(lag(B_Quantity) OVER (ORDER BY price))
This is 1 way to do it.
Note: By looking at data, looks like there is no specific primary key, but combination of price and a_quantity looks unique, hence I used these 2 columns in join. If this is just your example data and in actual you do have a primary key, I suggest you to change the join condition to that primary key.
REXTESTER DEMO
update Table1
set remaining_quantity = upd_tbl.cum
from
(
select t.*
,a_quantity - sum(b_quantity) over (partition by a_quantity order by price) as cum
from table1 t
) upd_tbl
where table1.price=upd_tbl.price
and table1.a_quantity=upd_tbl.a_quantity;
Explanation:
sum(b_quantity) over (partition by a_quantity order by price) will give you cumulative sum for a partition (2,6,10..). Now you can subtract it from a_quantity to get your output.