SQL how to keep multiple entries satisfying a certain condition - sql

I have the following dataset:
ID Amount
1 50
2 6
3 7
3 50
4 60
4 30
5 40
5 20
I only want to keep IDs with multiple entries that have at least one amount greater than, or equal to, 50. The final result would look like this:
ID Amount
3 7
3 50
4 60
4 30
How would I go about doing this?

You can use exists:
select t.*
from t
where exists (select 1
from t t2
where t2.id = t.id and t2.amount >= 50
);

You can use windows function max as follows:
select * from
(select t.*, max(amount) over (partition by id) as max_
from your_table t) t
where max_ >= 50

you can try like below
select t1.* from table_name t1
where 50 >=( select max(amount) from table_name t2
where t1.id=t2.id)

Related

How can I select top 3 for each group based on another column in sqlite?

I'm trying to get top 3 most profitable UserIDs in each country in one table using sqlite. I'm not sure where to use LIMIT 3.
Here is the table I have:
Country | UserID | Profit
US 1 100
US 12 98
US 13 10
US 5 8
US 2 5
IR 9 95
IR 3 90
IR 8 70
IR 4 56
IR 15 40
the result should look like this:
Country | UserID | Profit
US 1 100
US 12 98
US 13 10
IR 9 95
IR 3 90
IR 8 70
One pretty simple method is:
select t.*
from t
where t.profit >= (select t2.profit
from t t2
where t2.country = t.country
order by t2.profit desc
limit 1 offset 2
);
This assumes at least three records for each country. You can get around that with coalesce():
select t.*
from t
where t.profit >= coalesce((select t2.profit
from t t2
where t2.country = t.country
order by t2.profit desc
limit 1 offset 2
), t.profit
);
Since SQLite doesn't support windows function, so you can write a subquery be a seqnum by Country, then get top 3
You can try this query.
select t.Country,t.UserID,t.Profit
from(
select t.*,
(select count(*)
from T t2
where t2.Country = t.Country and t2.Profit >= t.Profit
) as seqnum
from T t
)t
where t.seqnum <=3
sqlfiddle:https://www.db-fiddle.com/f/tmNhRLGG2oKqCKXJEDsjfe/0
LIMIT won't be usefull as it applies to a whole result set.
I would create an auxiliary column "CountryRank" like this:
SELECT *, (SELECT COUNT() FROM Data AS d WHERE d.Country=Data.Country AND d.Profit>Data.Country)+1 AS CountryRank
FROM Data;
And query on that result:
SELECT Country, UserID, Profit
FROM (
SELECT *, (SELECT COUNT() FROM Data AS d WHERE d.Country=Data.Country AND d.Profit>Data.Profit)+1 AS CountryRank FROM Data)
WHERE CountryRank<=3
ORDER BY Country, CountryRank;

SQL group numbers that are 'close' together using a threshold value

Consider the table:
id value
1 2
2 4
3 6
4 9
5 10
6 12
7 19
8 20
9 22
I want to group them by a threshold value so that I can find values that are 'close' together.
To do this I want another column that groups these numbers together. For this example use 2 as the
threshold. The result should be like this. It does not matter what is used as the group label, just
as long as it makes it easy to query later.
id value group_label
1 2 A
2 4 A
3 6 A
4 9 B
5 10 B
6 12 B
7 19 C
8 20 C
9 22 C
I couldn't get the version using lag() to work but here's a mysql query using variables
select id, value,
(case
when (value - #value) > 2
then #groupLabel := #groupLabel + 1
else #groupLabel
end) groupLabel, #value := value
from data cross join (
select #value := -1, #groupLabel := 0
) t1
order by value
SQLFiddle
Update
Here's a query using lag
select t1.id, t1.value, count(t2.id)
from data t1 left join (
select id, value,
case when
(value - lag(value) over (order by value)) > 2
then 1 else 0
end groupLabel
from data
) t2 on t2.groupLabel = 1
and t2.id <= t1.id
group by t1.id, t1.value
order by t1.value
SQLFiddle

Accumulate a summarized column

I could need some help with a SQL statement. So I have the table "cont" which looks like that:
cont_id name weight
----------- ---------- -----------
1 1 10
2 1 20
3 2 40
4 2 15
5 2 20
6 3 15
7 3 40
8 4 60
9 5 10
10 6 5
I then summed up the weight column and grouped it by the name:
name wsum
---------- -----------
2 75
4 60
3 55
1 30
5 10
6 5
And the result should have a accumulated column and should look like that:
name wsum acc_wsum
---------- ----------- ------------
2 75 75
4 60 135
3 55 190
1 30 220
5 10 230
6 5 235
But I didn't manage to get the last statement working..
edit: this Statement did it (thanks Gordon)
select t.*,
(select sum(wsum) from (select name, SUM(weight) wsum
from cont
group by name)
t2 where t2.wsum > t.wsum or (t2.wsum = t.wsum and t2.name <= t.name)) as acc_wsum
from (select name, SUM(weight) wsum
from cont
group by name) t
order by wsum desc
So, the best way to do this is using cumulative sum:
select t.*,
sum(wsum) over (order by wsum desc) as acc_wsum
from (<your summarized query>) t
The order by clause makes this cumulative.
If you don't have that capability (in SQL Server 2012 and Oracle), a correlated subquery is an easy way to do it, assuming the summed weights are distinct values:
select t.*,
(select sum(wsum) from (<your summarized query>) t2 where t2.wsum >= t.wsum) as acc_wsum
from (<your summarized query>) t
This should work in all dialects of SQL. To work with situations where the accumulated weights might have duplicates:
select t.*,
(select sum(wsum) from (<your summarized query>) t2 where t2.wsum > t.wsum or (t2.wsum = t.wsum and t2.name <= t.name) as acc_wsum
from (<your summarized query>) t
try this
;WITH CTE
AS
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY wsum) rownum
FROM #table1
)
SELECT
c1.name,
c1.wsum,
acc_wsum= (SELECT SUM(c2.wsum)
FROM cte c2
WHERE c2.rownum <= c1.rownum)
FROM CTE c1;
or you can join instead of using subquery
;WITH CTE
AS
(
SELECT *,
ROW_NUMBER() OVER(ORDER BY usercount) rownum
FROM #table1
)
SELECT
c1.name,
c1.wsum,
acc_wsum= SUM(c2.wsum)
FROM CTE c1
INNER JOIN CTE c2 ON c2.rownum <= c1.rownum
GROUP BY c1.name, c1.wsum;

Get next minimum, greater than or equal to a given value for each group

given the following Table1:
RefID intVal SomeVal
----------------------
1 10 val01
1 20 val02
1 30 val03
1 40 val04
1 50 val05
2 10 val06
2 20 val07
2 30 val08
2 40 val09
2 50 val10
3 12 val11
3 14 val12
4 10 val13
5 100 val14
5 150 val15
5 1000 val16
and Table2 containing some RefIDs and intVals like
RefID intVal
-------------
1 11
1 28
2 9
2 50
2 51
4 11
5 1
5 150
5 151
need an SQL Statement to get the next greater intValue for each RefID and NULL if not found in Table1
following is the expected result
RefID intVal nextGt SomeVal
------------------------------
1 11 20 val01
1 28 30 val03
2 9 10 val06
2 50 50 val10
2 51 NULL NULL
4 11 NULL NULL
5 1 100 val14
5 150 150 val15
5 151 1000 val16
help would be appreciated !
Derived table a retrieves minimal values from table1 given refid and intVal from table2; outer query retrieves someValue only.
select a.refid, a.intVal, a.nextGt, table1.SomeVal
from
(
select table2.refid, table2.intval, min (table1.intVal) nextGt
from table2
left join table1
on table2.refid = table1.refid
and table2.intVal <= table1.intVal
group by table2.refid, table2.intval
) a
-- table1 is joined again to retrieve SomeVal
left join table1
on a.refid = table1.refid
and a.nextGt = table1.intVal
Here is Sql Fiddle with live test.
You can solve this using the ROW_NUMBER() function:
SELECT
RefID,
intVal,
NextGt,
SomeVal,
FROM
(
SELECT
t2.RefID,
t2.intVal,
t1.intVal AS NextGt,
t1.SomeVal,
ROW_NUMBER() OVER (PARTITION BY t2.RefID, t2.intVal ORDER BY t1.intVal) AS rn
FROM
dbo.Table2 AS t2
LEFT JOIN dbo.Table1 AS t1 ON t1.RefID = t2.RefID AND t1.intVal >= t2.intVal
) s
WHERE
rn = 1
;
The derived table matches each Table2 row with all Table1 rows that have the same RefID and an intVal that is greater than or equal to Table2.intVal. Each subset of matches is ranked and the first row is returned by the main query.
The nested query uses an outer join, so that those Table2 rows that have no Table1 matches are still returned (with nulls substituted for the Table1 columns).
Alternatively you can use OUTER APPLY:
SELECT
t2.RefID,
t2.intVal,
t1.intVal AS NextGt,
t1.SomeVal
FROM
dbo.Table2 AS t2
OUTER APPLY
(
SELECT TOP (1)
t1.intVal
FROM
dbo.Table1 AS t1
WHERE
t1.RefID = t2.RefID
AND t1.intVal >= t2.intVal
ORDER BY
t1.intVal ASC
) AS t1
;
This method is arguably more straightforward: for each Table2 row, get all matches from Table1 based on the same set of conditions, sort the matches in the ascending order of Table1.intVal and take the topmost intVal.
This can be done with a join, group by, and a case statement, and a trick:
select t1.refid, t2.intval,
min(case when t1.intval > t2.intval then t1.intval end) as min_greater_than_ref,
substring(min(case when t1.intval > t2.intval
then right('00000000'+cast(t1.intval as varchar(255)), 8)+t1.SomeVal)
end)), 9, 1000)
from table1 t1 left join
table2 t2
on t1.refid = t2.refid
group by t1.refid, t2.intval
SO, the trick is to prepend the integer value to SomeValue, zero-padding the integer value (in this case to 8 characters). You get something like: "00000020val01". The minimum on this column is based on the minimum of the integer. The final step is to extract the value.
For this example, I used SQL Server syntax for the concatenation. In other databases you might use CONCAT() or ||.

How to take column value count

Using SQL Server 2000
Table1
Column1
20
30
40
20
40
30
30
I want take a count like this
20 - 2
30 - 3
40 - 2
In case if the column value 20 or 30 or 40 is not available, it should display 20 - 0 or 30 - 0 or 40 - 0.
For example
Column1
20
30
20
30
30
Expected output
20 - 2
30 - 3
40 - 0
I will get only 20, 30. 40. No more value will come.
How to make a query
Need help
select item,count (item) from table group by item
EDIT : ( after your edit)
CREATE TABLE #table1 ( numbers int )
insert into #table1 (numbers) select 20
insert into #table1 (numbers) select 30
insert into #table1 (numbers) select 40
SELECT [num]
FROM [DavidCard].[dbo].[sssssss]
select numbers,count (num) from #table1 LEFT JOIN [sssssss] ON #table1.numbers = [sssssss].num group by numbers
SQL Query 101:
SELECT Column1, COUNT(*)
FROM dbo.YourTable
GROUP BY Column1
ORDER BY Column1
Update: if you want to get a list of possible values, and their potential count (or 0) in another table, you need two tables, basically - one with all the possible values, one with the actual values - and a LEFT OUTER JOIN to put them together - something like:
SELECT
p.Column1, ISNULL(COUNT(t.Column1), 0)
FROM
(SELECT 20 AS 'Column1'
UNION
SELECT 30
UNION
SELECT 40) AS p
LEFT OUTER JOIN
dbo.YourTable t ON t.Column1 = p.Column1
GROUP BY
p.Column1
ORDER BY
p.Column1