select one row in a one-to-many relation - sql

I have a one-to-many relation.
Table 1: C_Id, Name
Table 2: Id, Name
I want to select only one row from table 2 for each Id in table C_Id.
Example:
Table 1
1 First Row
2 Second Row
Table 2
1 First Row 1
1 First Row 2
2 Second Row 1
2 Second Row 2
My query result is:
1 First Row 1 (First row for ID 1)
2 Second Row 1 (First row for ID 2)
What query do I need to get this?

SELECT t1.C_Id, MIN(t2.Name) -- or MAX, depending on your needs
FROM t1 INNER JOIN t2 ON (t1.C_Id = t2.Id)
GROUP BY t1.C_Id

select * from tab1, tab2 where tab1.c_id=tab2.id and rownum=1;
--use order by if you want to sort ace or desc.

If you can rely on the ID of table 2 then this is quite simple:
select t1.c_id
, t1.name
, t2.id as t2_id
from t1
join t2 on t1.name = t2.name
where t2.id = 1;
However I suspect your actual case is more complicated. But easily solvable with the ROW_NUMBER() analytic function:
select c_id
, name
, t2_id
from (
select t1.c_id
, t1.name
, t2.id as t2_id
, row_number() over ( partition by t1.name
order by t2.id asc ) rn
from t1
join t2 on t1.name = t2.name
)
where rn = 1;

Related

SQL Get Count Across 3 Tables

I have a 3 tables, one with these two columns
table1:
id, name
0 foo
1 etc
2 example
table2:
id table1_id
0 1
1 0
2 2
table3:
id table2_id
0 1
1 0
2 0
Which query can I find all 'name's from table1 where ALL ids in table2 have a count of atleast n in table3? i.e if n was 1 it should return foo and etc
EDIT:
Explained poorly, I'm trying to get the name of every record in table1 where ALL corresponding records in table2 (i.e records where the column table1_ID is equal to each id within table1. In my example tables, each ID has one) have a count in table3 of atleast n.
If n was 1, as the table2_id 0 appears twice in records 1 and 2, its 'parent' would be returned. It corresponds to the table 1 record 1, so the name of the record with table1 id: 1 should be returned, which is etc. Example also as it has a count of 1 in the bottom column, however foo does not appear so it shouldnt.
Expected result:
name
foo
etc
You can do this using a subquery in the where clause:
select t1.*
from table1 t1
where (select count(t3.id)
from table2 t2 left join
table3 t3
on t3.table2_id = t2.id
where t2.table1_id = t1.id
group by t2.id
order by count(*) asc -- to get the minimum
limit 1
) >= ? -- value you care about
I suspect that this might have the best performance with appropriate indexes: table2(table1_id, id) and table3(table2_id).
If I have understood the question - if a check on table3.table2_id is greater than 0, the answer would be 'etc' ?
Code below
select t1.name
from
(
select 0 as id, 1 as table2_id
union select 1, 0
union select 2 , 0
) t3
inner join
(
select 0 as id , 1 as table_id
union select 1, 0
union select 2, 2
) t2 on t2.table_id = t3.table2_id
inner join
(
select 0 as id, 'foo' as name
union select 1 , 'etc'
union select 2 , 'example'
) t1 on t1.id = t2.table_id
where t3.table2_id > 0
select table1.name
FROM table1
INNER JOIN table2 ON table1.id=table2.table1_id
INNER JOIN table3 ON table2.id=table3.table2_id
GROUP BY table1.name
HAVING count(*) >= 1
replace the last 1 with whatever n you desire
Here's the sql fiddle if you want to play around with it: http://sqlfiddle.com/#!7/14217/4
Use an INNER join of table1 to table2 and then a LEFT join to table3 and count the corresponding ids of table3.
Then by a 2nd level of aggregation return only the rows of table1 where all the counters are at least 1:
SELECT id, name
FROM (
SELECT t1.id, t1.name, COUNT(t3.id) counter
FROM table1 t1
INNER JOIN table2 t2 ON t2.table1_id = t1.id
LEFT JOIN table3 t3 ON t3.table2_id = t2.id
GROUP BY t1.id, t1.name, t2.id
)
GROUP BY id, name
HAVING MIN(counter) >= 1 -- change to the number that you want
See the demo.
Results:
id
name
0
foo
1
etc

How to get first row value from third table while joining three tables

I'm new to mssql .Here am trying to get values from database by joining three tables .
Table 1:
Table 2 :
Here the image there is a possibility for a single user can have multiple image id form this I need to take any one of the image.
Table 3 :
Here am joining the Table 1 and Table 2 by using H_ID
and Table 2 and Table 3 by using IMG_ID.
What I want to do is Need to get all the colum values from Table 1 and Table 2 But the first URL from the Table 3.
In this case an employee has multiple images in the Table I need to take the 1 URL.
Result should be like this :
Query :
SELECT T1.H_ID AS 'ID',
T1.NAME,
T1.ROLE,
T2.SALARY,
T3.IMAGE
FROM TABLE1 T1
JOIN TABLE2 T2
ON T1.H_ID T2.H_ID
JOIN TABLE3 T3
ON T3.IMG_ID = T2.IMG_ID
WHERE T1.STATUS = 'ACTIVE'
Now this query returns 3 rows for the id H_ID = 1001 but It should be a single row.
Can anyone help me to fix this .
use row_number()
with cte as
(SELECT T1.H_ID AS 'ID',T1.NAME,T1.ROLE,T2.SALARY,T3.IMAGE
,row_number() over(partition by T2.img_id order by T3.id) rn
FROM TABLE1 T1
JOIN TABLE2 T2
ON T1.H_ID T2.H_ID
JOIN TABLE3 T3
ON T3.IMG_ID = T2.IMG_ID WHERE T1.STATUS = 'ACTIVE'
) select * from cte where rn=1
After you comments it seems you need subquery
select T1.*,T2.sal,a.url
FROM TABLE1 T1
JOIN TABLE2 T2
ON T1.H_ID T2.H_ID
left join ( select min(id),img_id,url from table3 group by img_id,url) a
on T2.IMG_ID= a.img_id
WHERE T1.STATUS = 'ACTIVE'
I think, You can simply use OUTER APPLY and TOP 1 for that
SELECT T1.H_ID AS 'ID',
T1.NAME,
T1.ROLE,
T2.SALARY,
T3.IMAGE
FROM TABLE1 T1
JOIN TABLE2 T2 ON T1.H_ID T2.H_ID
OUTER APPLY(SELECT TOP 1 T3.IMAGE
FROM TABLE3 T3 WHERE T3.IMG_ID = T2.IMG_ID
--ORDER BY <column_name> --to take top 1 value in specific order
) T3
WHERE T1.STATUS = 'ACTIVE'

insert new sequential records into child table for each record of parent table

I have two tables (SQL-server):
t1 (parent)
===========
id
1
2
t2 (child)
=======================
parent_id record_number
1 1
2 1
2 2
Is it possible in one SQL statement to insert new records into t2 for each of parent id, so the result will be:
t2:
=======================
parent_id record_number
1 1
1 2
2 1
2 2
2 3
Thank you!
I think what the OP is after is:
INSERT INTO T2
SELECT T1.id
MAX(T2.ID) + 1
FROM T1
JOIN T2 ON T1.ID = T2.parent_id
GROUP BY T1.id;
Something like this should get what you want.
Note I added it with a LEFT JOIN so you could use ISNULL in case the parent record isn't in t2 yet.
INSERT INTO t2 (parent_id, record_number)
SELECT
A.id,
ISNULL(MAX(B.record_number), 0) + 1
FROM t1 A
LEFT JOIN t2 B
ON A.id = B.parent_id
GROUP BY
A.id
As an aside, generating a record number could be done effectively using the ROW_NUMBER() function. Here is an example. Perhaps you have specific reasons why it needs to be persisted to the table, but if not this could be useful as well to calculate it on the fly.
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY parent_id ORDER BY id)
FROM t2
ORDER BY parent_id, record_number
Note it assumes you have an "id" column in t2, or something else to order the records by
Is this what you want?
insert into t2 (parent_id, record_number)
select id, id
from t1;
If so, I prefer using NULL to represent that that something has no parent:
insert into t2 (parent_id, record_number)
select null, id
from t1;
This is a more accurate representation of the data. Something is not its own parent.

Selecting Values from Table as Column Headers

I have a table with the following structure:
ID KEY VALUE SEQ
1 Amount 5 2
1 Amount 4 1
1 Type T1 2
1 Type T1 1
2 Amount 10 2
2 Amount 5 1
2 Type T2 2
2 Type T2 1
I would like to create a query to get this:
ID Amount Type
1 5 T1
2 10 T2
As you can see there could be multiple combinations of (ID, Key) but (ID, Key, Seq) is unique.
SELECT T.ID,
T1.VALUE as Amount,
T2.VALUE as Type
FROM
(SELECT ID, MAX(SEQ) as MAXSEQ FROM TABLE GROUP BY ID) as T
JOIN
TABLE as T1
ON T1.ID = T.ID
AND T1.KEY = 'Amount'
AND T1.SEQ = MAXSEQ
JOIN
TABLE as T2
ON T2.ID = T.ID
AND T2.KEY = 'Type'
AND T2.SEQ = MAXSEQ
But I am getting results that I wasn't expecting
ID Amount Type
1 5 T1
1 4 T1
1 10 T1
1 5 T1
2 10 T2
2 5 T2
2 4 T2
2 5 T2
I already read this post but it doesn't apply to my case although it helps here
Any idea on who to fix this?
SELECT
id, amount, type
FROM TABLE1
natural join (SELECT ID, MAX(SEQ) as SEQ FROM TABLE1 GROUP BY ID)
pivot (
max(VALUE) for key in ('Amount' as amount, 'Type' as type)
)
fiddle
Something you are missing from the linked question is the DISTINCT keyword.
See also the explanation below the query posted by ypercube. In fact, you are getting duplicates because you are joining a table on itself. Thus, rows will be mirrorred.
Your subquery should be:
(SELECT DISTINCT ID, MAX(SEQ) as MAXSEQ FROM TABLE GROUP BY ID) as T
I realized that there was another column, that I didn't put in the question (to avoid giving out real data)which was affecting the results. The unique constraint was (ID, Key, Seq, Time) instead of (ID, Key, Seq). As #Andrew Mentioned the query is returning the correct results.
Here goes the query again
SELECT T.ID,
T1.VALUE as Amount,
T2.VALUE as Type
FROM
(SELECT ID, MAX(SEQ) as MAXSEQ FROM TABLE GROUP BY ID) as T
JOIN
TABLE as T1
ON T1.ID = T.ID
AND T1.KEY = 'Amount'
AND T1.SEQ = MAXSEQ
JOIN
TABLE as T2
ON T2.ID = T.ID
AND T2.KEY = 'Type'
AND T2.SEQ = MAXSEQ
Thanks Andrew for the clarification and the sqlfiddle. I apologize if I wasted anyone's time.

SQL JOINING main table with two tables returning number of records - maximum of other two tables?

I have my MAIN table T1 from which I am doing select of many fields:
ID
1000
I have table T2:
ID SERVICE
1000 IPTV
1000 VOIP
I have table T3:
ID DEVICE
1000 MODEM
1000 ROUTER
1000 DVC
I want to JOIN T1 with T2 or T3 which can but also and might not have values at all !!!!
When they have values I want to have in SELECT number of records of maximum number of records from T2 or T3. So in this case T3 has 3 records which is maximum and I want 3 records in SELECT. (in case that T2 has 3 records that would be maximum in case that T3 has 2 records)
But in my SELECT statement I am having 5 records which I do not want. What is the correct expression for that? My below query returns 5 records (I want 3)
select t1.id,t2.service,t3.device
from t1
left outer join T2 on t1.id=t2.id
left outer join T3 on t1.id=t3.id
SELECT
t1.id
, t2.service
, t3.device
FROM
t1
LEFT JOIN
( SELECT
ROW_NUMBER() OVER (PARTITION BY id ORDER BY service) AS rn
, id
, service
FROM
t2
) AS t2
FULL JOIN
( SELECT
ROW_NUMBER() OVER (PARTITION BY id ORDER BY device) AS rn
, id
, device
FROM
t3
) AS t3
ON t3.id = t2.id
AND t3.rn = t2.rn
ON COALESCE(t2.id, t3.id) = t1.id ;
this requirement doesn't sound right, but the following should work
SELECT T1.*, z2.ID, z2.SERVICE FROM
(
SELECT ID, CASE WHEN SUM(INDICATOR) >=0 THEN 1 ELSE -1 END AS INDICATOR FROM
(
SELECT ID, SERVICE, 1 AS INDICATOR FROM t2
UNION ALL
SELECT ID , DEVICE AS SERVICE , -1 AS INDICATOR FROM t3
) z
GROUP BY ID
) z1
INNER JOIN
(
SELECT ID, SERVICE, 1 AS INDICATOR FROM t2
UNION ALL
SELECT ID , DEVICE AS SERVICE, -1 AS INDICATOR FROM t3
) z2
ON z1.ID = z2.ID AND z1.INDICATOR = z2.INDICATOR
RIGHT JOIN T1 ON T1.ID = Z2.ID