How to SUM column 1 and select column 2 by condition? - sql

I've stuck with how to sum column A and select column B with a condition if column B >= 50 select this row id.
Example Table Like this
+----+-----------+---------+
| ID | PRICE | PERCENT |
+----+-----------+---------+
| 1 | 5 | 5 |
| 2 | 18 | 20 |
| 3 | 7 | 50 |
| 4 | 16 | 56 |
| 5 | 50 | 87 |
| 6 | 17 | 95 |
| 7 | 40 | 107 |
+----+-----------+---------+
SELECT ID, SUM(PRICE) AS PRICE, PERCENT FROM Table
Column ID and PERCENT, I want to select from a row with PERCENT >= 50
The result should be
Any suggestions?

Try below query:
declare #tbl table(ID int, PRICE int, [PERCENT] int);
insert into #tbl values
(1, 5, 5),
(2, 18, 20),
(3, 7, 50),
(4, 16, 56),
(5, 50, 87),
(6, 17, 95),
(7, 40, 107);
select top 1 ID,
(select sum(PRICE) from #tbl) PRICE,
[PERCENT]
from #tbl
where [PERCENT] > 50

You could include the total in a subquery in the SELECT clause of your query like this:
SELECT
[ID],
(SELECT SUM([PRICE]) FROM T) AS [PRICE],
[PERCENT]
FROM
T
WHERE
[PRICE] >= 50
However, it remains unclear which of the five valid records should be picked. You indicated it should be the record where PERCENT has value 56, but IMHO value 50 would be possible too, just like 87, 95, and 107 (?). It is unclear why you pick value 56 as the correct one. If it doesn't matter, you could use TOP (1) in the SELECT clause, but if it does matter, you should extend the WHERE clause with appropriate conditions/filters.
Mixing aggregate data from groups back with individual elements/records like this is often fuzzy. I consider it to be a "code smell" and here in your question on StackOverflow, it might indicate an XY-problem. Anyway, these query results might get misinterpreted quite easily if you are not careful. Always remember that such aggregated data in the result (in this case the PRICE field) has practically nothing to do with the detail data in the result (in this case the ID and PERCENT fields). Unless you want to combine your aggregate data with your detail data (in a calculation for example), but you do not indicate you want anything like that in your question...

you can do this Trick to have a result of 2 queries in 1 query:
select ID as ID,T.[PERCENT] AS B, 0 as sumA
from Table_1 as T
where T.[PERCENT]>=50
union All
select 0 as ID,0 AS B, sum(t.[PRICE]) as sumA
from Table_1 as T

Am not sure why you need this but certainly, You can Archive Above Output using below query
Sample Data
declare #data table
(Id int, Price int, [Percent] int)
insert #data
VALUES (1,5,5),
(2,18,20),
(3,7,50),
(4,16,56),
(5,50,87),
(6,17,95),
(7,40,107)
Query
select top 1 ID, (select sum(price) from #data) as Price, [Percent ]
from #data
where [Percent ] >50

You can try the following code:
SELECT TOP (1) [ID], SUM(PRICE) OVER (), [PERCENT]
FROM #tbl
ORDER BY CASE WHEN [PERCENT] > 50 THEN 0 ELSE 1 END, [ID];
I am using OVER clause in order to extract/read data from the table only once - one table scan.

Related

SQL Server query for multiple conditions on the same column

Here's the schema and data that i am working with
CREATE TABLE tbl (
name varchar(20) not null,
groups int NOT NULL
);
insert into tbl values('a', 35);
insert into tbl values('a', 36);
insert into tbl values('b', 35);
insert into tbl values('c', 36);
insert into tbl values('d', 37);
| name | groups|
|------|-------|
| a | 35 |
| a | 36 |
| b | 35 |
| c | 36 |
| d | 37 |
now i need names of only those that are having group greater than or equal to 35
but also an additional is that i can only include a row for which group=35 when a corresponding groups=36 is also present
| name | groups|
|------|-------|
| a | 35 |
| a | 36 |
second condition is that it CAN include those names that are having groups greater than or equal to 36 without having a groups=35
| name | groups|
|------|-------|
| c | 36 |
| d | 37 |
the only case it should leave out is where a record has only groups=35 present without a corresponding groups=36
| name | groups|
|------|-------|
| b | 35 |
i have tried the following
select name from tbl
where groups>=35
group by name
having count(distinct(groups))>=2
or groups>=36;
this is the error i am facing Column 'tbl.groups' is invalid in the HAVING clause because it is not contained in either an aggregate function or the GROUP BY clause.
Try this:
DECLARE #tbl table ( [name] varchar(20) not null, groups int NOT NULL );
INSERT INTO #tbl VALUES
('a', 35), ('a', 36), ('b', 35), ('c', 36), ('d', 37);
DECLARE #group int = 35;
; WITH cte AS (
SELECT
[name]
, COUNT ( DISTINCT groups ) AS distinct_group_count
FROM #tbl
WHERE
groups >= #group
GROUP BY
[name]
)
SELECT t.* FROM #tbl AS t
INNER JOIN cte
ON t.[name] = cte.[name]
WHERE
cte.distinct_group_count > 1
OR t.groups > #group;
RETURNS
+------+--------+
| name | groups |
+------+--------+
| a | 35 |
| a | 36 |
| c | 36 |
| d | 37 |
+------+--------+
Basically, this restricts the name results to groups with a value >= 35 with more than one distinct group associated, or any name with a group value greater than 35. Several assumptions were made in regard to your data, but I believe the logic still applies.
So, as far as i can tell you just want to limit where groups 35 is by itself. I thought, lets try and isolate those names where they only have groups=35 and then not exists from there. Is this the correct output youre after?
Also, using complicated OR's in the where clause will often lead to your query not being SARGable. Better to UNION or some how building the query so that each part can use indexes (if they can).
if object_id('tempdb..#tbl') is not null drop table #tbl;
CREATE TABLE #tbl (
name varchar(20) not null,
groups int NOT NULL
);
insert into #tbl values('a', 35), ('a', 36), ('b', 35), ('c', 36), ('d', 37);
select *
from #tbl tbl
WHERE NOT EXISTS
(
SELECT COUNT(groups), name
FROM #tbl t
WHERE EXISTS
(
SELECT name
FROM #tbl tb
WHERE groups = 35
and tb.name=t.name
)
AND t.name = tbl.name
GROUP BY name
HAVING COUNT(groups)=1
)
;
It looks like you need an exists() condition. Try:
select *
from tbl t
where t.groups >= 35
and (
t.groups > 35
or exists(select * from tbl t2 where t2.name = t.name and t2.groups = 36)
)
There are other ways to arrange the where clause to achieve the same effect. Having the t.groups >= 35 condition up front should give the query optimizer the ability to leverage an index on groups.
You can use a windowed count for this
This avoids joining the table multiple times
SELECT
name,
groups
FROM (
SELECT *,
Count36 = COUNT(CASE WHEN groups = 36 THEN 1 END) OVER (PARTITION BY name)
FROM tbl
WHERE groups >= 35
) tbl
WHERE groups >= 36 OR Count36 > 0;
db<>fiddle

Calculate difference between rows and keep the first row always 0?

I have to calculate the difference between row values in Table X (SQL Server)
Table X
ID A
1 100
2 200
3 300
4 400
So I wrote the following SQL query
SELECT ID,
A
A - COALESCE (lag(A) OVER (ORDER BY date), 0) AS Difference
FROM Table X
And the result is
ID A Difference
1 100 100
2 200 -100
3 300 -100
4 400 -100
What I want is to keep the first-row Difference always as 0
ID A Difference
1 100 0
2 200 -100
3 300 -100
4 400 -100
But I have no idea how to do it.
You may try to pass а value for the default parameter of the LAG() window function. As is explained in the documentation, the default parameter is the value to return when offset is beyond the scope of the partition (and for the first row, the previous row is beyond that scope).
Table:
CREATE TABLE Data (ID int, A int, [Date] date)
INSERT INTO Data (ID, A, [Date])
VALUES
(1, 100, '20200701'),
(2, 200, '20200702'),
(3, 300, '20200703'),
(4, 400, '20200704')
Statment:
SELECT
ID,
A,
LAG(A, 1, A) OVER (ORDER BY [Date]) - A AS Difference
FROM Data
Result:
ID A Difference
------------------
1 100 0
2 200 -100
3 300 -100
4 400 -100
Thanks to #zhorov for the table schema, data
You can use ISNULL or COALESCE to arrive at the difference.
DECLARE #Data table(ID int, A int, [Date] date)
INSERT INTO #Data (ID, A, [Date])
VALUES
(1, 100, '20200701'),
(2, 200, '20200702'),
(3, 300, '20200703'),
(4, 400, '20200704')
SELECT ID,A, ISNULL(LAG(A,1) OVER(ORDER BY DATE),A) AS difference FROM #Data
--or you can use COALESCE
SELECT ID,A, COALESCE(LAG(A,1) OVER(ORDER BY DATE),A) AS difference FROM #Data
+----+-----+------------+
| ID | A | difference |
+----+-----+------------+
| 1 | 100 | 100 |
| 2 | 200 | 100 |
| 3 | 300 | 200 |
| 4 | 400 | 300 |
+----+-----+------------+
You can try the following query.
For this type of query order by clause is important based on the column and the applied order by clause ascending or descending the result can be different.
create table Test(ID int,
A int)
insert into Test values
(1, 100),
(2, 200),
(3, 300),
(4, 400)
SELECT ID
,A
,Difference
FROM (
SELECT ID
,A
,isnull(A - LAG(A) OVER (
ORDER BY A DESC
), 0) Difference
FROM test
) t
Live Demo

How can i duplicate records with T-SQL and keep track of the progressive number?

How can I duplicate the records of table1 and store them in table2 along with the progressive number calculated from startnum and endnum?
Thanks
the first row must be duplicated in 4 records i.e num: 80,81,82,83
Startnum | Endnum | Data
---------+-------------+----------
80 | 83 | A
10 | 11 | C
14 | 16 | D
Result:
StartEndNum | Data
------------+-----------
80 | A
81 | A
82 | A
83 | A
10 | C
11 | C
14 | D
15 | D
16 | D
A simple method uses a recursive CTE:
with cte as
select startnum, endnum, data
from t
union all
select startnum + 1, endnum, data
from cte
where startnum < endnum
)
select startnum, data
from cte;
If you have ranges that exceed 100, you need option (maxrecursion 0).
Note: There are other solutions as well, using numbers tables (either built-in or generated). I like this solution as a gentle introduction to recursive CTEs.
Without recursion:
declare #t table(Startnum int, Endnum int, Data varchar(20))
insert into #t values
(80, 83, 'A'),
(10, 11, 'C'),
(14, 16, 'D');
select a.StartEndNum, t.Data
from #t t cross apply (select top (t.Endnum - t.Startnum + 1)
t.Startnum + row_number() over(order by getdate()) - 1 as StartEndNum
from sys.all_columns) a;
You can use any other table with enough rows instead of sys.all_columns

Checking using nvl2 with multiple group by

I have a table like
------------------------
S.No Name Amount Imp_Num
1 A 10 12345
2 B 20
3 A 30
4 C 40 4555
5 B 50
--------------------------
and I want something like
---------------------------------------
Name Total_Amount Imp_Num Imp_Num_Present
A 40 12345 Y
B 70 null N
C 40 4555 Y
---------------------------------------
The important_number_present column should be Y if the important number is present for the particular name at least once and the important number should be captured. The important number for a particular name is assumed to be the same.If different the latest one should be displayed as imp_numb. (But this is of secondary priority).
I tried something like
Select sum(amount) as total_amount, imp_num, nvl2(imp_num,'Y','N') from sampletable group by imp_num;
But name can't be retrieved and the data doesn't make sense without the name. I might be doing something totally wrong. Can a feasible solution be done in SQL rather than in pl/sql.
Group by with name is returning the name with a null entry and imp_num entry.
I am cracking my head on this. Would be of great help, if someone solves it.
Thanks in advance
You could use a (fake) aggregation function on imp_num and group by name
Select Name, sum(amount) as total_amount, max(imp_num), nvl2( max(imp_num),'Y','N')
from sampletable
group by Name;
EDIT: Another solution with COUNT function. DEMO
SELECT name
,SUM(amount) AS total_amount
,MAX(imp_num) AS Imp_Num
,CASE
WHEN Count(imp_num) > 0
THEN 'Y'
ELSE 'N'
END AS Imp_Num_Present
FROM yourtable
GROUP BY name
You may also use a MAX( CASE ) block
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE yourtable
(S_No int, Name varchar2(1), Amount int, Imp_Num varchar2(5))
;
INSERT ALL
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (1, 'A', 10, '12345')
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (2, 'B', 20, NULL)
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (3, 'A', 30, NULL)
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (4, 'C', 40, '4555')
INTO yourtable (S_No, Name, Amount, Imp_Num)
VALUES (5, 'B', 50, NULL)
SELECT * FROM dual
;
Query 1:
SELECT Name,
SUM (amount) AS total_amount,
MAX (imp_num) AS Imp_Num,
CASE
WHEN MAX (CASE WHEN imp_num IS NOT NULL THEN 1 ELSE 0 END) = 1
THEN
'Y'
ELSE
'N'
END
AS Imp_Num_Present
FROM yourtable
GROUP BY Name
Results:
| NAME | TOTAL_AMOUNT | IMP_NUM | IMP_NUM_PRESENT |
|------|--------------|---------|-----------------|
| A | 40 | 12345 | Y |
| B | 70 | (null) | N |
| C | 40 | 4555 | Y |

MySQL result Group by two values

I need some help for a query to group some rows, I'm trying the whole day and find no solution and I'm sure it's easy. Maybe some one can bring me light in the dark:
My Table:
id | Bid | Cid | value
4 | 22 | 11 | 33
5 | 24 | 11 | 33
6 | 25 | 11 | 33
7 | 24 | 11 | 100
8 | 25 | 16 | 150
I want only the result Bid=25 if I have Cid 11, 16 and value 33, 150
I tried
SELECT id, Bid
FROM `table`
WHERE
Cid IN (11, 16) AND
value IN ('33','150')
GROUP BY Bid;
But in this case I get all possible Cid's ...
It seems I'm on a wood way.
Your query is tricky because you are looking for the presence of pairs of column values in a given group. One way to go here is to aggregate by Bid, Cid, and value, first, with the restriction that each record has a matching pair. Then subquery this by Bid and check that the count be 2, indicating that both pairs were present.
SELECT Bid
FROM
(
SELECT Bid, Cid, value
FROM yourTable
WHERE (Cid, value) IN ((11, 33), (16, 150))
GROUP BY Bid, Cid, value
) t
GROUP BY Bid
HAVING COUNT(*) = 2;
Demo
Since you are using SQL Server we can slightly refactor the above query to this:
SELECT Bid
FROM
(
SELECT Bid, Cid, value
FROM yourTable
WHERE (Cid = 11 AND value = 33) OR (Cid = 16 AND value = 150)
GROUP BY Bid, Cid, value
) t
GROUP BY Bid
HAVING COUNT(*) = 2;
You can find your answer when you use GROUP BY in combination with some SUM
SELECT
Bid
FROM
Table1
GROUP BY
Bid
HAVING
SUM(Cid = 11) AND SUM(Cid = 16)
AND
SUM(value = 33) AND SUM(value = 150)
Result
| Bid |
|-----|
| 25 |
demo http://www.sqlfiddle.com/#!9/ce56e97/2