Sql Query With GroupBys - Impossible Query - sql

I have the following:
Name Age When
Paul 21 01-Jan-10
Paul 54 01-Jan-11
Paul 65 01-Jan-12
I want to pull out all names and that age wherer the date is >= 01-Jan-11
I ve tried
SELECT NAME, AGE, MIN(When)
FROM ATABLE
WHERE When >= '01-Jan-11'
GROUP BY NAME, AGE
That did not work - I get the 01 Jan 2011 AND 01 Jan 2012 for Paul which is wrong - I just want the one
NOTE: This comment is the most correct so far but does not provide an answer :(
You're where clause will get 2 records and will keep them as 2 records since you're grouping by Name and Age. If Name and Age were the same, it'd be 1 record and not 2.

WHEN is a SQL Server reserved word - if that is really your column name it might be causing you problems. In any case, it would be helpful to post more information, such as any errors you were receiving.
Also for what its worth, normally I would use DATEDIFF for the date comparison:
SELECT NAME, AGE, MIN(When)
FROM ATABLE
WHERE DATEDIFF ( 'd', '2001-01-01', When ) > 0
GROUP BY NAME, AGE

In SQL Server:
SELECT a.*
FROM (
SELECT DISTINCT name
FROM atable
) ad
CROSS APPLY
(
SELECT TOP 1 *
FROM atable ai
WHERE ai.name = ad.name
AND When >= '01-Jan-11'
ORDER BY
When
) a

try
SELECT NAME, AGE, MIN(When) FROM A TABLE WHERE When >= '2011/01/01' GROUP BY NAME, AG
http://www.databasejournal.com/features/mssql/article.php/2209321/Working-with-SQL-Server-DateTime-Variables-Part-Three---Searching-for-Particular-Date-Values-and-Ranges.htm

thats easy...
SELECT NAME, AGE, MIN(When)
FROM ATABLE
WHERE
YEAR([When]) >= 2011
AND MONTH([When]) >= 1
AND DAY([When]) >= 1
GROUP BY NAME, AGE

SELECT k.Name, t.Age, k.MinWhen
FROM (
SELECT Name, MIN(When) MinWhen
FROM ATABLE
GROUP BY Name
) k
JOIN ATABLE t ON
t.Name = k.Name
AND t.When = k.MinWhen

Related

How to find frequency in SQL

I am having some issues with SQL code, specifically in finding the frequency of an ID.
My table looks like
Num ID
136 23
1427 45
1415 67
1416 23
7426 45
4727 12
4278 67
...
I would need to see the frequency of ID, when this has more or equal 2 same values.
For example: 23, 45 and 67 in the table above.
I have tried as follows:
Select distinct *, count(*)
From table_1
Group by 1,2
Having count(*) >2
But it is wrong.
I need distinct, as I do not want any duplicates in Num.
I think I should you a counter to reset when the value of the next rows is different from the previous one and report the frequency (1, 2, 3, and so on), then select values greater or equal to 2, but Indo not know how to do it in Sql.
Could you help me please?
Thanks
Use ID only in GROUP BY :
SELECT ID, COUNT(*) AS No_frequency
FROM table t
GROUP BY id
HAVING COUNT(*) >= 2;
Note : If you have duplicate num then use distinct :
HAVING COUNT(DISTINCT num) >= 2;
If I understand your question, you can try this:
SELECT ID, COUNT(1)
FROM table_1
GROUP BY ID
HAVING COUNT(1) >= 2
In this way you have the ID's with 2 or more occurences and the number of occurences
EDIT
I suppose you are using MySql but add your DBMS in your question, so, try this:
SELECT ID, COUNT(1) as FREQUENCY, GROUP_CONCAT(NUM)
FROM table_1
GROUP BY ID
HAVING COUNT(1) >= 2
This works for me
SELECT ID, COUNT(ID) AS Frq FROM MyTable
GROUP BY ID
HAVING COUNT(ID) > 2
ORDER BY COUNT(ID) DESC

Case statement to determine if I should union

I currently want to do some sort of conditional union. Given the following example:
SELECT age, name
FROM users
UNION
SELECT 25 AS age, 'Betty' AS name
Say I wanted to only union the second statement if the count of 'users' was >=2 , otherwise do not union the two.
In summary I want to append a table with a row if the table only has 2 or more values.
You could use an ugly hack something like this, but I think Tim's answer is better:
SELECT age, name
FROM users
UNION ALL
SELECT 25, 'Betty'
WHERE (SELECT COUNT(*) FROM users) > 1;
If it's in a stored-procedure you could use If...Else:
IF (SELECT COUNT(*) FROM users) < 2
BEGIN
SELECT age, name
FROM users
END
ELSE
SELECT age, name
FROM users
UNION ALL
SELECT 25 AS age, 'Betty' AS name
Otherwise you could try something like this:
SELECT age, name
FROM users
UNION ALL
SELECT TOP 1 25 AS age, 'Betty' AS name
FROM users
WHERE (SELECT COUNT(*) FROM users) >= 2
Note that i've used UNION ALL since it doesn't seem that you want to eliminate duplicates.
Played around here: http://sqlfiddle.com/#!6/a7540/2323/0
Edit: Instead of my second approach i prefer Zohar's. So if you can use If....Else prefer that otherwise WHERE (SELECT COUNT(*) FROM users) > 1 without a table.
Something like the following should work:
SELECT age, name
FROM users
UNION ALL
SELECT age, name
FROM (SELECT 25 AS age, 'Betty' AS name) x
CROSS APPLY (SELECT COUNT(*) FROM users) y(cnt)
WHERE y.cnt >= 2
Second part of UNION ALL will be NULL in case users table has less than 2 records.
SELECT age
, name
FROM users
UNION
SELECT 25 As age
, 'Betty' As name
WHERE EXISTS (
SELECT Count(*)
FROM users
HAVING Count(*) >= 2
)
;

SQL query for grouping monthly period ranges

I'm having some trouble building a query that will group my items into monthly ranges according to whenever they exist in a month or not. I'm using PostgreSQL.
For example I have a table with data as this:
Name Period(text)
Ana 2010/09
Ana 2010/10
Ana 2010/11
Ana 2010/12
Ana 2011/01
Ana 2011/02
Peter 2009/05
Peter 2009/06
Peter 2009/07
Peter 2009/08
Peter 2009/12
Peter 2010/01
Peter 2010/02
Peter 2010/03
John 2009/05
John 2009/06
John 2009/09
John 2009/11
John 2009/12
and I want the result query to be this:
Name Start End
Ana 2010/09 2011/02
Peter 2009/05 2009/08
Peter 2009/12 2010/03
John 2009/05 2009/06
John 2009/09 2009/09
John 2009/11 2009/12
Is there any way to achieve this?
This is an aggregation problem, but with a twist -- you need the define the groups of adjacent months for each name.
Assuming that the month never appears more than once for a given name, you can do this by assigning a "month" number to each period and subtracting a sequential number. The values will be a constant for months that are in a row.
select name, min(period), max(period)
from (select t.*,
(cast(left(period, 4) as int) * 12 + cast(right(period, 2) as int) -
row_number() over (partition by name order by period)
) as grp
from names t
) t
group by grp, name;
Here is a SQL Fiddle illustrating this.
Note: duplicates are not really a problem either. You would jsut use dense_rank() instead of row_number().
I don't know if there is an easier way (there probably is) but I can't think of one right now:
with parts as (
select name,
to_date(replace(period,'/',''), 'yyyymm') as period
from names
), flagged as (
select name,
period,
case
when lag(period,1, (period - interval '1' month)::date) over (partition by name order by period) = (period - interval '1' month)::date then null
else 1
end as group_flag
from parts
), grouped as (
select flagged.*,
coalesce(sum(group_flag) over (partition by name order by period),0) as group_nr
from flagged
)
select name, min(period), max(period)
from grouped
group by name, group_nr
order by name, min(period);
The first common table expression (parts) simple changes the period into a date so that it can be used in an arithmetic expression.
The second CTE (flagged) assigns a flag each time the gap (in months) between the current row and the previous is not one.
The third CTE then accumulates those flags to define a unique group number for each consecutive number of rows.
The final select then simply gets the start and end period for each group. I didn't bother to convert the period back to the original format though.
SQLFiddle example that also shows the intermediate result of the flagged CTE: http://sqlfiddle.com/#!15/8c0aa/2
Well one of the common ways to do this could be recursive SQL:
with recursive cte1 as (
select
"Name" as name,
("Period"||'/01')::date as period
from Table1
), cte2 as (
select
c.name, c.period as s, c.period as e
from cte1 as c
where not exists (select * from cte1 as t where t.name = c.name and t.period = c.period - interval '1 month')
union all
select
c.name, c.s as s, t.period
from cte2 as c
inner join cte1 as t on t.name = c.name and t.period = c.e + interval '1 month'
)
select
c.name, to_char(c.s, 'YYYY/MM') as "Start", to_char(max(c.e), 'YYYY/MM') as "End"
from cte2 as c
group by c.name, c.s
order by 1, 2
I'm not sure about performance of this one, you have to test it.
sql fiddle demo

SQL query to select most recent of duplicates

I have a table of values, with a date stored against each entry for example
Name
Age
PaymentAmount
Date
Can someone help me to write a query that would show the most recent payment only of any person within a certain age range.
E.g If I had 5 entries, and wanted the most recent payment of all people aged 20-25
Allan, 45, $1500, 1/1/2014
Tim, 22, $1500, 1/2/2001
John, 25, $2000, 2/3/2001
Tim, 22, $2500, 1/2/2010
John, 25, $3000, 2/3/2010
It would return the bottom 2 rows only
You didn't state your DBMS, so this is ANSI SQL
select *
from (
select name,
age,
PaymentAmount,
Date,
row_number() over (partition by name order by date desc) as rn
from the_table
where age between 22 and 25
) t
where rn = 1;
Another option is to use a co-related subquery:
select name,age,paymentamount,date
from the_table t1
where age between 22 and 25
and date = (select max(date)
from the_table t2
where t2.name = t1.name
and t2.age between 22 and 25)
order by name;
Usually the solution with a window function is faster than the co-related subquery as only a single access to the table is needed.
SQLFiddle: http://sqlfiddle.com/#!15/17e37/4
Btw: having a column named age is a bit suspicious because you need to update that every year. You should rather store the date of birth and then calculate the age when retrieving the data.
This query would give you all records of most recent payment of age 20 and 25. Limit it by using TOP 2 or LIMIT 2 or rownum <=2 as per your DB syntax
SELECT NAME,AGE,PAYMENTAMOUNT,DATE FROM MY_TABLE
WHERE AGE BETWEEN 20 AND 25
AND DATE IN
(
SELECT MAX(DATE)
FROM MY_TABLE
WHERE
AGE BETWEEN 20 AND 25
);
EDIT as per horse_with_no_name:
SELECT NAME,AGE,PAYMENTAMOUNT,DATE
FROM the_table
WHERE AGE BETWEEN 20 AND 25
AND DATE IN
(
SELECT (DATE)
FROM the_table
WHERE
AGE BETWEEN 20 AND 25 order by date desc limit 2
)
limit 2;
Fiddle reference : http://sqlfiddle.com/#!15/17e37/10
Simplest of all,Try this following query
select name,age,paymentamount,date from yourtablename where date in (select max(date) from yourtablename where age between 20 and 25 and group by name);
You should Create a Table with Identity Column to make your Life easier
ColumnPrimaryKey IDENTITY (1,1)
Name
Age
PaymentAmount
Date
SELECT TOP 2 * FROM [TableName] Where Age BETWEEN 20 AND 25 ORDER BY [PrimaryKey] DESC
The above query will return the top two row Inserted in table
You can use between like
select * from meta where title='$title' and (date between '$start_date' and '$end_date').
Okay, I know you said SQL-- here's for people with two layers.
VIA SQL:
Order your SQL results by date descending (should be newest to oldest...).
VIA YOUR "BACK END":
Create an empty final set.
As you are iterating through your results, if your result row person is not in your final set, add the data to the final set.
Boom, your final set has the latest of each person.

SQL selecting where all distinct values exist in another column

I have a table in which the first two rows are Company, Year. Each company will have some years, but not necessarily all of them:
ABC | 2010
ABC | 2011
ABC | 2012
BBC | 2011 //does not have all the years, don't want to select it
I'd like to select a list of companies which have ALL the years (not just some of them), but I'm having trouble writing a select query to do that. I imagine this is really easy but I can't figure it out for some reason.
try
select company
from your_table
group by company
having count(distinct year) = (select count(distinct year) from your_table)
Select * FROM Company Where CompanyId In(
select CompanyId From Company
group by CompanyId
having count(*) = (select count(distinct Year) from Company)
)
http://www.sqlfiddle.com/#!3/c2f81/11
Note that if you alread know how many years there should be, then obviously you would just say that number instead of doing a select distinct year.
SELECT Company
FROM Table
GROUP BY Company
HAVING COUNT(Distinct YEAR) = 3