Query Different Condition With 1 Column - sql

I have table like :
+-------+--------+----------+------------+-------+
| cd_hs | cd_cnt | name_cnt | dates | value |
+-------+--------+----------+------------+-------+
| 1 | 1 | aaa | 2018-06-01 | 50 |
| 1 | 2 | bbb | 2018-07-01 | 150 |
| 1 | 3 | ccc | 2018-08-01 | 20 |
| 1 | 1 | aaa | 2018-06-02 | 40 |
| 1 | 2 | bbb | 2018-07-02 | 70 |
| 1 | 3 | ccc | 2018-08-02 | 80 |
+-------+--------+----------+------------+-------+
Actually I have more data but I am just show the sample and what I want to do is
I want to group by cd_hs, name_cnt and based on year in dates column and do sum(value) but I have the 2 condition. First is to show value with condition cd_cnt with 1 and 2 and second condition cd_cnt without 1 and 2 so meaning I have much value other than 1 and 2 and do aliasing as other in one column
Expected Result :
+-------+------+----------+-------------+
| cd_hs | year | name_cnt | total_value |
+-------+------+----------+-------------+
| 1 | 2018 | aaa | 90 |
| 1 | 2018 | bbb | 220 |
| 1 | 2018 | other | 100 |
+-------+------+----------+-------------+
how can I do that? I am new in query and don't know what to do..

Your question is a bit confusing considering your spec doesn't seem to exactly line up with what you requested.
If the sample result you've provided is actually what you're looking for, a simple SUM and GROUP BY should do the trick here:
SELECT cd_hs, EXTRACT(YEAR from dates) as year, name_cnt, SUM(value_)
FROM foo
GROUP BY cd_hs, EXTRACT(YEAR from dates), name_cnt
Result:
| cd_hs | year | name_cnt | sum |
|-------|------|----------|-----|
| 1 | 2018 | aaa | 90 |
| 1 | 2018 | bbb | 220 |
| 1 | 2018 | ccc | 100 |
SQLFiddle
Since you mentioned you wanted two different totals with two separate conditions, you could use JOIN in conjunction with some well-crafted subqueries:
SELECT a.cd_hs, EXTRACT(YEAR FROM a.dates), a.name_cnt, COALESCE(b.total_a, 0) as "Total A", COALESCE(c.total_b, 0) as "Total B"
FROM foo a
LEFT JOIN (
SELECT b.cd_hs, b.name_cnt, EXTRACT(YEAR FROM b.dates), SUM(value_) as total_a
FROM foo b
WHERE b.cd_cnt NOT IN (1, 2)
GROUP BY b.cd_hs, b.name_cnt, EXTRACT(YEAR from b.dates)
) b ON a.cd_hs = b.cd_hs AND a.name_cnt = b.name_cnt
LEFT JOIN (
SELECT c.cd_hs, c.name_cnt, EXTRACT(YEAR FROM c.dates), SUM(value_) as total_b
FROM foo c
WHERE c.cd_cnt IN (1, 2)
GROUP BY c.cd_hs, c.name_cnt, EXTRACT(YEAR from c.dates)
) c ON a.cd_hs = c.cd_hs AND a.name_cnt = c.name_cnt
This particular solution is readable and will get you to the correct end result but will most likely not be scalable in its current form.
Result:
| cd_hs | date_part | name_cnt | Total A | Total B |
|-------|-----------|----------|---------|---------|
| 1 | 2018 | aaa | 0 | 90 |
| 1 | 2018 | bbb | 0 | 220 |
| 1 | 2018 | ccc | 100 | 0 |
| 1 | 2018 | aaa | 0 | 90 |
| 1 | 2018 | bbb | 0 | 220 |
| 1 | 2018 | ccc | 100 | 0 |
SQLFiddle

Related

How to minus by period in another column

I need results in minus column like:
For example, we take first result by A = 23(1)
and we 34(2) - 23(1) = 11, then 23(3) - 23(1)...
And so on. For each category.
+--------+----------+--------+-------+
| Period | Category | Result | Minus |
+--------+----------+--------+-------+
| 1 | A | 23 | n/a |
| 1 | B | 24 | n/a |
| 1 | C | 25 | n/a |
| 2 | A | 34 | 11 |
| 2 | B | 23 | -1 |
| 2 | C | 1 | -24 |
| 3 | A | 23 | 0 |
| 3 | B | 90 | 66 |
| 3 | C | 21 | -4 |
+--------+----------+--------+-------+
Could you help me?
Could we use partitions or lead here?
SELECT
*,
Result - FIRST_VALUE(Result) OVER (PARTITION BY Category ORDER BY Period) AS Minus
FROM
yourTable
This doesn't create the hello values, but returns 0 instead. I'm not sure returning arbitrary string in an integer column makes sense, so I didn't do it.
If you really need to avoid the 0 you could just use a CASE statement...
CASE WHEN 1 = Period
THEN NULL
ELSE Result - FIRST_VALUE(Result) OVER (PARTITION BY Category ORDER BY Period)
END
Or, even more robustly...
CASE WHEN 1 = ROW_NUMBER() OVER (PARTITION BY Category ORDER BY Period)
THEN NULL
ELSE Result - FIRST_VALUE(Result) OVER (PARTITION BY Category ORDER BY Period)
END
(Apologies for any typos, etc, I'm on my phone.)
You can do:
select b.*, b.result - a.result as "minus"
from t a
join t b on b.category = a.category and a.period = 1
Result:
period category result minus
------- --------- ------- -----
1 A 23 0
1 B 24 0
1 C 25 0
2 A 34 11
2 B 23 -1
2 C 1 -24
3 A 23 0
3 B 90 66
3 C 21 -4
See running example at DB Fiddle.
Ok, just not to duplicate my question
How to do it?
If
For each new Sub period we should repeat first_value logic
+------------+----------+------------+----------+---------+
| Sub period | Period | Category | Result | Minus |
+------------+----------+------------+----------+---------+
| SA | 1 | A | 23 | n/a |
| SA | 2 | A | 34 | 11 |
| SA | 3 | A | 35 | 12 |
| SA | 4 | A | 36 | 13 |
| KS | 1 | A | 23 | n/a |
| KS | 2 | A | 21 | -2 |
| KS | 3 | A | 23 | 0 |
| KS | 4 | A | 21 | -2 |
+------------+----------+------------+----------+---------+

Get all records from Table A and all entries that don't already exist from Table B

I have an Oracle DB with 2 tables, Table A and Table B.
Table A has better data quality but only for a limited set of entries. Table A has also multiple entries (because of history) and I only need the last one per number.
So I need to get all entries from Table A and then the rest of the entries which are not in Table A from Table B.
I also need to get some data from Table B into the result of Table A because the info does not exist in Table A (val1, val2, val3).
So probably some sort of JOIN + GROUP BY?
Table A:
number | valid_from | valid_to | pos1 | pos2 | pos3 | factor | loc
100 | 2020-03-01 | 2020-03-10 | 7 | 80 | 18 | 19 | 1
100 | 2020-03-10 | 2020-03-13 | 7 | 80 | 18 | 19 | 1
100 | 2020-03-13 | 2020-03-16 | 8 | 80 | 18 | 20 | 1
200 | 2020-03-02 | 2020-03-03 | 6 | 90 | 19 | 30 | 1
200 | 2020-03-03 | 2020-03-04 | 6 | 90 | 19 | 29 | 1
200 | 2020-03-04 | 2020-03-10 | 9 | 90 | 19 | 30 | 1
300 | 2020-03-10 | 2020-03-12 | 13 | 100 | 10 | 41 | 2
300 | 2020-03-12 | 2020-03-14 | 13 | 100 | 10 | 40 | 2
300 | 2020-03-14 | 2020-03-20 | 10 | 100 | 10 | 40 | 2
Table B:
number | pos1 | pos2 | pos3 | val1 | val2 | val3 | top
100 | 7 | 70 | 18 | a | aa | aaa | 3
200 | 6 | 60 | 19 | b | bb | bbb | 4
300 | 5 | 50 | 10 | c | cc | ccc | 5
400 | 2 | 20 | 2 | d | dd | ddd | 16
500 | 3 | 30 | 3 | e | ee | eee | 28
End result should be:
number | pos1 | pos2 | pos3 | factor | loc | val1 | val2 | val3 | top
100 | 8 | 80 | 18 | 20 | 1 | a | aa | aaa | 3
200 | 9 | 90 | 19 | 30 | 1 | b | bb | bbb | 4
300 | 10 | 100 | 10 | 40 | 2 | c | cc | ccc | 5
400 | 2 | 20 | 2 | NULL | NULL | d | dd | ddd | 16
500 | 3 | 30 | 3 | NULL | NULL | e | ee | eee | 28
How can I achieve this? Do I need a FULL LEFT JOIN and GROUP BY by number? Not sure what to take or how to get the latest entries from Table A.
select b.number, b.pos1, b.pos2, b.pos3,
a.factor, a.loc, b.val1, b.val2, b.val3, b.top
from tableb b
left outer join tablea a
on b.number = a.number
you can also use NVL(b.pos1, a.pos1) which means if b.pos1 is null take a.pos1
I would write this as a left join with filtering:
select b.number, b.pos1, b.pos2, b.pos3,
a.factor, a.loc,
b.val1, b.val2, b.val3, b.top
from b
left join (
select a.*, row_number() over(partition by number order by valid_from desc) rn
from a
) a on a.number = b.number and a.rn = 1

Need query for JOIN four tables with some conditions?

I have the following four tables:
1) mls_user
2) mls_category
3) bonus_point
4) mls_entry
In mls_user table values are like below:
*-------------------------*
| id | store_id | name |
*-------------------------*
| 1 | 101 | sandeep |
| 2 | 101 | gagan |
| 3 | 102 | santosh |
| 4 | 103 | manu |
| 5 | 101 | jagveer |
*-------------------------*
In mls_category table values are like below:
*---------------------------------*
| cat_no | store_id | cat_value |
*---------------------------------*
| 20 | 101 | 1 |
| 21 | 101 | 4 |
| 30 | 102 | 1 |
| 31 | 102 | 2 |
| 40 | 103 | 1 |
| 41 | 103 | 1 |
*---------------------------------*
In bonus_point table values are like below:
*-----------------------------------*
| user_id | store_id | bonus_point |
| 1 | 101 | 10 |
| 4 | 101 | 5 |
*-----------------------------------*
In mls_entry table values are like below:
*-------------------------------------------------------*
| user_id | store_id | category | distance | status |
*-------------------------------------------------------*
| 1 | 101 | 20 | 10 | Approved |
| 1 | 101 | 21 | 40 | Approved |
| 1 | 101 | 20 | 10 | Approved |
| 2 | 101 | 20 | 5 | Approved |
| 3 | 102 | 30 | 10 | Approved |
| 3 | 102 | 31 | 80 | Approved |
| 4 | 101 | 20 | 15 | Approved |
*-------------------------------------------------------*
And I want below output:
*--------------------------------------------------*
| user name | Points | bonus Point | Total Point |
*--------------------------------------------------*
| Sandeep | 30 | 10 | 40 |
| Santosh | 30 | 0 | 30 |
| Manu | 15 | 5 | 20 |
| Gagan | 5 | 0 | 5 |
| Jagveer | 0 | 0 | 0 |
*--------------------------------------------------*
I tell the calculation of how the points will come for user Sandeep.
Points = ((10+10)/1 + 40/4)=30
Here 1 and 4 is cat value which comes from mls_category.
I am using below code for a particular user but when i
SELECT sum(t1.totald/c.cat_value) as total_distance
FROM mls_category c
join (
select sum(distance) totald, user_id, category
FROM mls_entry
WHERE user_id=1 AND store_id='101' AND status='approved'
group by user_id, category) t1 on c.cat_no = t1.category
I have created tables in online for checking
DEMO
Computing the points (other than the bonus points) requires a separate join between the mls_entry and mls_category tables. I would do this in a separate subquery, and then join this to the larger query.
Here is one approach:
SELECT
u.name,
COALESCE(t1.points, 0) AS points,
COALESCE(b.bonus_point, 0) AS bonus_points,
COALESCE(t1.points, 0) + COALESCE(b.bonus_point, 0) AS total_points
FROM mls_user u
LEFT JOIN
(
SELECT e.user_id, SUM(e.distance / c.cat_value) AS points
FROM mls_entry e
INNER JOIN mls_category c
ON e.store_id = c.store_id AND e.category = c.cat_no
GROUP BY e.user_id
) t1
ON u.id = t1.user_id
LEFT JOIN bonus_point b
ON u.id = b.user_id
ORDER BY
total_points DESC;
This is the output I am getting from the above query in the demo you setup:
The output does not match exactly, because you have (perhaps) a typo in Santosh's data in your question, or otherwise the expected output in your question has a typo.

Return the row with the value of the previous row within the same group (Oracle Sql)

I have a tabel that looks like this:
|--------+------+---------|------|
| Head | ID | Amount | Rank |
|--------+------+---------|------|
| 1 | 10 | 1000 | 1 |
| 1 | 11 | 1200 | 2 |
| 1 | 12 | 1500 | 3 |
| 2 | 20 | 3400 | 1 |
| 2 | 21 | 3600 | 2 |
| 2 | 22 | 4200 | 3 |
| 2 | 23 | 1700 | 4 |
|--------+------+---------|------|
I want a new column (New_column) that does the following:
|--------+------+---------|------|------------|
| Head | ID | Amount | Rank | New_column |
|--------+------+---------|------|------------|
| 1 | 10 | 1000 | 1 | 1000 |
| 1 | 11 | 1200 | 2 | 1000 |
| 1 | 12 | 1500 | 3 | 1200 |
| 2 | 20 | 3400 | 1 | 3400 |
| 2 | 21 | 3600 | 2 | 3400 |
| 2 | 22 | 4200 | 3 | 3600 |
| 2 | 23 | 1700 | 4 | 4200 |
|--------+------+---------|------|------------|
Within each Head number, if rank is not 1, takes the amount of row within the Head number with Rank number before it (Rank 2 takes the amount of Rank 1 within the same Head and Rank 3 takes the amount of Rank 2 within the same Head and so on...)
I know how to fix it with a For loop in other programming languages but Don't know how to do it with SQL.
I think you basically want lag():
select t.*,
lag(amount, 1, amount) over (partition by head order by rank) as new_column
from t;
The three-argument form of lag() allows you to provide a default value.
You can join the same table(subquery) on rank-1 of derived table.
select t1.*,case when t1.rank=1 then amount else t2.amount new_amount
from your_table t1 left join (select Head,ID,Amount,Rank from your_table) t2
on t1.head=t2.head and t1.rank=t2.rank-1
You can use this update:
UPDATE your_table b
SET New_column = CASE WHEN rank = 1 then Amount
ELSE (select a.Amount FROM your_table a where a.ID = b.ID and a.rank = b.rank-1) END

Ask about query in sql server

i have table like this:
| ID | id_number | a | b |
| 1 | 1 | 0 | 215 |
| 2 | 2 | 28 | 8952 |
| 3 | 3 | 10 | 2000 |
| 4 | 1 | 0 | 215 |
| 5 | 1 | 0 |10000 |
| 6 | 3 | 10 | 5000 |
| 7 | 2 | 3 |90933 |
I want to sum a*b where id_number is same, what the query to get all value for every id_number? for example the result is like this :
| ID | id_number | result |
| 1 | 1 | 0 |
| 2 | 2 | 523455 |
| 3 | 3 | 70000 |
This is a simple aggregation query:
select id_number, sum(a*b)
from t
group by id_number
I'm not sure what the first column is for.