Selecting multiple unique matches from one column that match another column - sql

I have a list of codes (101, 102, 103, 104) and I want to pick out the people in the following table that have two or more different codes from the list occurring within a year of each other.
Name Code1 Code1date
John 101 01/01/2016
John 102 01/02/2013
Chris 101 01/01/2015
Chris 101 01/05/2014
Chris 102 01/10/2015
Mark 101 01/11/2011
Mark 101 01/01/2011
Mark 107 01/07/2012
So in this sample only Chris would be selected because he has a 101 code and a 102 code within a year of each other.
Thanks!

Try with the below query if you are using SQL Server.
SELECT name
FROM yourtable
WHERE code1 in (101, 102, 103, 104)
GROUP BY name, year(code1date)
HAVING COUNT(distinct code1) > 1

Version 1: Same year: happened in the same year
You need to make a group for each name and year and only show those distinct names that have more than 1 unique code:
select distinct name
from sample_table
where code1 between 101 and 104
group by name, extract(year from code1date)
having count(distinct code1) > 1
This will result in Chris only being presented in output.
EXTRACT function is ANSI-SQL compliant, but it will work assuming that code1date is of date type
In case it is of text data type, you could get 4 characters from the right, so for example right(code1date, 4)
Version 2: Same year: scan back- and onwards for one year difference
If by one year you mean not the same year, but scanning backwards and onwards from a date for 1 year difference, then here's the solution for Postgres:
SELECT
a.name
FROM sample_table a
JOIN sample_table b ON
a.name = b.name
AND a.code1 <> b.code1
AND b.code1date BETWEEN a.code1date - interval '1 year' AND a.code1date + interval '1 year'
WHERE a.code1 BETWEEN 101 AND 104
GROUP BY a.name
HAVING COUNT(DISTINCT a.code1) > 1
Above also assumes that your code1date is of date type. If that's not the case then you should think about converting it to a proper format. If that's beyond your reach, then you could always get the last character from your column, cast to Integer, increment it and append it back to the substring without the last char thus replacing the value of year :-)

SELECT
t1.Name
FROM
TableName t1
INNER JOIN TableName t2
ON t1.Name = t2.Name
AND t2.Code1Date BETWEEN DATEADD(year,-1,t1.Code1Date) AND DATEADD(year,1,t1.Code1Date)
AND t1.Code1 <> t2.Code1
AND t2.Code1 IN (101,102,103,104)
WHERE
t1.Code1 IN (101,102,103,104)
GROUP BY
t1.Name
HAVING
COUNT(DISTINCT t1.Code1) > 1
So this is for sql-server but just change up the BETWEEN statement to reflect the date functions of what ever rdbms you are working with.

Related

Sql Query: How to Base on the row name to display

I have the table data as listed on below:
name | score
andy | 1
leon | 2
aaron | 3
I want to list out as below, even no jacky's data, but list his name and score set to 0
aaron 3
andy 2
jacky 0
leon 2
You didn't specify your DBMS, but the following is 100% standard ANSI SQL:
select v.name, coalesce(t.score, 0) as score
from (
values ('andy'),('leon'),('aaron'),('jacky')
) as v(name)
left join your_table t on t.name = v.name;
The values clause builds up a "virtual table" that contains the names you are interested in. Then this is used in a left join so that all names from the virtual table are returned plus the existing scores from your (unnamed table). For non-existing scores, NULL is returned which is turned to 0 using coalesce()
If you only want to specify the missing names, you can use a UNION in the virtual table:
select v.name, coalesce(t.score, 0) as score
from (
select t1.name
from your_table t1
union
select *
from ( values ('jacky')) as x
) as v(name)
left join your_table t on t.name = v.name;
fixed the query, could list out the data, but still missing jacky, only could list out as shown on below, the DBMS. In SQL is SQL2008.
data
name score scoredate
andy 1 2021-08-10 01:23:16
leon 2 2021-08-10 03:25:16
aaron 3 2021-08-10 06:25:16
andy 4 2021-08-10 11:25:16
leon 5 2021-08-10 13:25:16
result set
name | score
aaron | 1
andy | 5
leon | 7
select v.name as Name,
coalesce(sum(t.score),0) as Score
from (
values ('aaron'), ('andy'), ('jacky'), ('leon')
) as v(name)
left join Score t on t.name=v.name
where scoredate>='2021-08-10 00:00:00'
and scoredate<='2021-08-10 23:59:59'
group by v.name
order by v.name asc
Your question lacks a bunch of information, such as where "Jacky"s name comes from. If you have a list of names that you know are not in the table, just use union all:
select name, score
from t
union all
select 'Jacky', 0;

Extract non-existent values based on previous months?

I HAVE tb1
code Name sal_month
==== ===== ========
101 john 02/2017
102 mathe 02/2017
103 yara 02/2017
104 sara 02/2017
101 john 03/2017
102 mathe 03/2017
103 yara 03/2017
104 sara 03/2017
101 john 04/2017
103 yara 04/2017
In February all of them received salaries as well as March
How do I extract non-existent values based on previous months?
the result should be come
code sal_month
==== =======
102 04/2017
104 04/2017
Thank in advance
First I created this table:
create table #T(code int, sal_month varchar(10))
insert into #T values(101,'2/2017'),(102,'2/2017'),(103,'2/2017'),(104,'2/2017'),
(101,'3/2017'),(102,'3/2017'),(104,'3/2017'),(101,'4/2017'),(103,'4/2017')
Second, I executed this query:
SELECT code, Max(sal_Month)
From #T
Where code not in (select code from #T where sal_Month = (select Max(sal_Month) from #T))
Group by code
Then I got the following results:
Note: I am using SQL SERVER 2012
I think you can count salary_month grouped by id, something like this, and select the rows that shows less than 3 times.
select code, count (sal_month) from tb1
group by code
having count (sal_month) < 3
After that you join with initial table (just to filter the full rows which you need) on code.
So the final query will look like his:
select code, sal_month
from tb1 a
join (select code, count (sal_month) from tb1
group by code
having code < 3) X on a.code = X.code
Something like this:
DECLARE #DataSource TABLE
(
[code] INT
,[sal_month] VARCHAR(12)
);
INSERT #DataSource ([code], [sal_month])
VALUES (101, '2/2017')
,(102, '2/2017')
,(103, '2/2017')
,(104, '2/2017')
,(101, '3/2017')
,(102, '3/2017')
,(104, '3/2017')
,(101, '4/2017')
,(103, '4/2017');
WITH DataSource AS
(
SELECT *
,DENSE_RANK() OVER (ORDER BY [sal_month]) AS [MonthID]
,MAX([sal_month]) OVER () AS [MaxMonth]
FROM #DataSource DS1
)
SELECT DS1.[code]
,DS1.[sal_month]
FROM DataSource DS1
LEFT JOIN DataSource DS2
ON DS1.[code] = DS2.[code]
AND DS1.[MonthID] = DS2.[MonthID] - 1
LEFT JOIN DataSource DS3
ON DS1.[code] = DS3.[code]
AND DS1.[MonthID] = DS3.[MonthID] + 1
WHERE DS2.[code] IS NULL
AND DS3.[code] IS NOT NULL
AND DS1.[sal_month] <> DS1.[MaxMonth];
Some notes:
we need a way to sort the months and it is not easy as you are storing them in very unpractical way; you are not using a date/datetime column and your string is not a valid date; also, the string you are using is not good at all, because if you have [sal_month] from different years, we will not be able to sort them; you should think about this - one alternative is to use this format:
201512
201701
201702
201711
In this way we can sort by string.
in the core I am using ROW_NUMBER and sorting months as strings;
the idea is to look for all records, that have not exists in the next month, but have a record in the previous; at the same time, excluded records which are from the last month, as it's not possible for them to have record in the next month;
Try this:
select tb2.code, tb2.sal_month from tb
right join (
select code, sal_month, datepart(month, sal_month) + 1 as next_sal_month from tb) as tb2
on (tb.code = tb2.code and datepart(month, tb.sal_month) = tb2.next_sal_month)
where tb2.next_sal_month < 5 and tb.sal_month is null
In the result set there's one additional record: code 103 didn't receive salary in March, but did so in February, so it is included as well.
Here's SQL fiddle, to try :)
In the absence of more facts about your tables, create a cartesian product of the 2 axes of month & code, then left join the stored data. Then it is easy to identify missing items when no stored data exists when compared to every possible combination.
You might already have master tables of sal_month and/or code to use, if you do use those, but if not you can dynamically create them using select distinct as seen below.
create table tbl1 (code int, sal_month varchar(10))
insert into tbl1 values(101,'2/2017'),(102,'2/2017'),(103,'2/2017'),(104,'2/2017'),
(101,'3/2017'),(102,'3/2017'),(104,'3/2017'),(101,'4/2017'),(103,'4/2017')
select c.code, m.sal_month
from ( select distinct sal_month from tbl1 ) m
cross join ( select distinct code from tbl1 ) c
Left join tbl1 t on m.sal_month = t.sal_month and c.code = t.code
Where t.sal_month IS NULL
code | sal_month
---: | :--------
103 | 3/2017
102 | 4/2017
104 | 4/2017
dbfiddle here

How to make a single line query include multiple lines in Oracle

I would like to take a set of data and expand it by adding date rows based an existing field. For instance, If I have the following table (TABLE1):
ID NAME YEAR
1 John 2001
2 Jim 2012
3 Sally 2005
I want to take this data and put it into another table but expand it to include a set of months (and from there I can add monthly information). If I just look at the first record (John) my result would be:
ID NAME YEAR MONTH
1 John 2001 01-JAN-2001
1 John 2001 01-FEB-2001
1 John 2001 01-MAR-2001
...
1 John 2001 01-DEC-2001
I have the mechanism to derive my monthly dates but how do I extract the data from TABLE1 to make TABLE2. Here is just a quick query but, of course, I get the ORA-01427 single-row subquery returns more than one row as expect. Just not sure how to organize the query to put these two pieces together:
select id,
name,
year,
book_cd,
(SELECT ADD_MONTHS('01-JAN-'|| year, LEVEL - 1)
FROM DUAL CONNECT BY LEVEL <= 12) month
from table1 ;
I realize I cant do this but I'm not sure how to put the two pieces together. I plan to bulk process records so it wont be one ID at a time Thanks for the help.
You can use a cross join:
select t.id,
t.name,
t.year,
t.book_cd,
ADD_MONTHS(to_date(t.year || '-01-01', 'YYYY-MM-DD'), m.rn) as mnth
from table1 t
cross join (select rownum - 1 as rn
from dual
connect by rownum <= 12) m

Sum values in one column and add to another table

My Table(BOB) is look like this:
Year Month Value
2010 1 100
2010 2 100
2010 3 100
2010 4 100
2010 5 100
I would like to add YTD values to another table (BOB2)
more exactly I want to see BOB 2 table like
Year Month Value
2010 1 100
2010 2 200
2010 3 300
2010 4 400
2010 5 500
See the answer below. I have simplified the query.
select
concat(cast(t1.year as char), cast(t1.month as char)) period_current,
sum(t1.amount) amount
from bob t1
left join bob t2 on
(t2.year + t2.month) <= (t1.year + t1.month)
group by
(t1.year + t1.month);
What the query is doing is using t1 as the base table and joining on the period (year + month) then you want to sum the amounts prior to that including the current amount. I haven't added in all the edge cases, but this gives you something to start from. If you are restricting your query to a single year, this should be enough.
Well, I think I understand what you are trying to do.. but if not, please re-phrase your question... You can accomplish what you have asked by using the following SQL.
--INSERT INTO BOB2 (Year, ID, Value)
SELECT a.Year, a.ID, (SELECT SUM(b.Value)
FROM BOB b
WHERE b.ID <= a.ID) as RunningTotalValue
FROM BOB a
ORDER BY a.Value;
Here is a SQLFiddle for you to look at.
EDIT: Change the ID column to "Month" after seeing the edit to your post.

oracle11g sql to find difference between rows from the same table

I have a table SAMPLE having unique column(sal_id, gal_id) and amount, tax, date and some more columns.
SAMPLE table
actual_id, sal_id, gal_id, processed_flag, amount, tax date
1 101 201 Y 10 1 25-Aug-12
2 101 201 Y 20 3 27-Aug-12
3 101 201 N 15 2 29-Aug-12
Now I need to find the difference between unprocessed (process_flag='N') and proceed data (process_flag='Y') having maximum date. I need to calculate difference between amount and tax for (sal_id + gal_id) combination.
So the query return should be like this:
In this example, Since for (101, 201), we are having 2 processed row and (actual_id 2 having maximum date. So need to find difference between actual_id 3 & actual_id 2 row.
actual_id, sal_id, gal_id, total_amount, total_tax date
3 101 201 -5 (15-20) -1 (2-3) 29-Aug-12
I am using Oracle 11g. Please help me.
Try this:
WITH t AS (SELECT DISTINCT "sal_id" sid,"gal_id" gid,"processed_flag" pf, max("amount") over
(partition BY "sal_id","gal_id","processed_flag") am,
max("tax") over
(partition BY "sal_id","gal_id","processed_flag") tx,
max("actual_id") over
(partition BY "sal_id","gal_id","processed_flag") aid,
max("date") over
(partition BY "sal_id","gal_id","processed_flag") dt
FROM sample)
SELECT t1.aid, t1.sid, t1.gid, t1.am-t2.am, t1.tx-t2.tx, t1.dt FROM t t1 JOIN t t2 ON t1.sid=t2.sid AND t1.gid=t2.gid
WHERE t1.pf='N' AND t2.pf='Y';
Here is a fiddle
UPDATE: The previous answer assumes that max(date) also has the max(amount).
a much better solution can be:
WITH t AS (SELECT *
FROM sample s
WHERE s."date" = (SELECT max("date") FROM sample s_in
WHERE s_in."sal_id" = s."sal_id"
AND s_in."gal_id" = s."gal_id"
AND s_in."processed_flag" = s."processed_flag"))
SELECT t1."actual_id", t1."sal_id", t1."gal_id", t1."amount"-t2."amount" total_amount, t1."tax"-t2."tax" total_tax, t1."date"
FROM t t1 JOIN t t2 ON t1."sal_id"=t2."sal_id" AND t1."gal_id"=t2."gal_id"
WHERE t1."processed_flag"='N' AND t2."processed_flag"='Y';
Here is the fiddle