SQL: 3 self-joins and then join them together - sql

I have 2 tables to join in a specific way. I think my query is right, but not sure.
select t1.userID, t3.Answer Unit, t5.Answer Demo
FROM
table1 t1
inner join (select * from table2) t3 ON t1.userID = t3.userID
inner join (select * from table2) t5 ON t1.userID = t5.userID
where
NOT EXISTS (SELECT * FROM table1 t2 WHERE t2.userID = t1.userID AND t2.date > t1.date)
and NOT EXISTS (SELECT * FROM table2 t4 WHERE t4.userID = t3.userID and t4.counter > t3.counter)
and NOT EXISTS (SELECT * FROM table2 t6 WHERE t6.userID = t5.userID and t6.counter > t5.counter)
and t1.date_submitted >'1/1/2009'
and t3.question = Unit
and t5.question = Demo
order by
t1.userID
From table1 I want distinct userID where date > 1/1/2009
table1
userID Date
1 1/2/2009
1 1/2/2009
2 1/2/2009
3 1/2/2009
4 1/1/2008
So The result I want from table1 should be this:
userID
1
2
3
I then want to join this on userID with table2, which looks like this:
table2
userID question answer counter
1 Unit A 1
1 Demo x 1
1 Prod 100 1
2 Unit B 1
2 Demo Y 1
3 Prod 100 1
4 Unit A 1
1 Unit B 2
1 Demo x 2
1 Prod 100 2
2 Unit B 2
2 Demo Z 2
3 Prod 100 2
4 Unit A 2
I want to join table1 with table2 with this result:
userID Unit Demo
1 B X
2 B Z
In other words,
select distinct userID from table2 where question = Unit for the highest counter
and then
select distinct userID from table2 where question = Demo for the highest counter.
I think what I've done is 3 self-joins then joined those 3 together.
Do you think it's right?

SELECT du.userID, unit.answer, demo.answer
FROM (
SELECT DISTINCT userID
FROM table1
WHERE date > '1/1/2009'
) du
LEFT JOIN
table2 unit
ON (userID, question, counter) IN
(
SELECT du.userID, 'Unit', MAX(counter)
FROM table2 td
WHERE userID = du.userID
AND question = 'Unit'
)
LEFT JOIN
table2 demo
ON (userID, question, counter) IN
(
SELECT du.userID, 'Demo', MAX(counter)
FROM table2 td
WHERE userID = du.userID
AND question = 'Demo'
)
Having an index on table2 (userID, question, counter) will greatly improve this query.
Since you mentioned SQL Server 2005, the following will be easier and more efficient:
SELECT du.userID,
(
SELECT TOP 1 answer
FROM table2 ti
WHERE ti.user = du.userID
AND ti.question = 'Unit'
ORDER BY
counter DESC
) AS unit_answer,
(
SELECT TOP 1 answer
FROM table2 ti
WHERE ti.user = du.userID
AND ti.question = 'Demo'
ORDER BY
counter DESC
) AS demo_answer
FROM (
SELECT DISTINCT userID
WHERE date > '1/1/2009'
FROM table1
) du
To aggregate:
SELECT answer, COUNT(*)
FROM (
SELECT DISTINCT userID
FROM table1
WHERE date > '1/1/2009'
) du
JOIN table2 t2
ON t2.userID = du.userID
AND t2.question = 'Unit'
GROUP BY
answer

Related

How to randomly update a column if the number of records between the 2 join tables are not equal in Postgres sql

Table 1(5 records):
id
name
date
units
1
abc
3/16/2021
1
abc
3/17/2021
1
abc
3/18/2021
1
abc
3/19/2021
1
abc
3/20/2021
Table 2(3 records):
id
name
startdate
enddate
units
1
abc
3/16/2021
03/23/2021
2
1
abc
3/16/2021
03/23/2021
2
1
abc
3/16/2021
03/23/2021
2
Below is the join condition:
select * from Table1 a right join Table2 b on
(a.id = b.id) and (a.name = b.name) and (a.date between b.startdate and b.enddate)
I am trying to update the units columns in Table 1 from Table 2. My requirement is since there are 3 records in Table 2, only 3 records in Table 1 should be updated based on the above join condition. It can be random. But the number of records updated should not go above 3.
I tried doing this.
with e as
(select *,
row_number() over(partition by a.id
order by id) as rn
from Table1 a right join Table 2 b on (a.id = b.id) and (a.name = b.name) and (a.date between b.startdate and b.enddate)
)
update table1
set units = e.units
from e
where e.rn = 1
However, in this case all 5 records get updated. How do I resolve this? Any help is appreciated. Thank you.
Join the tables together. Then choose one row from from table2 for each row in table1 and do the update:
update table1 t1
set t1.units = t2.units
from (select distinct on (t1.id, t1.name, t1.date) t1.*, t2.units
from table1 t1 join
table2 t2
on t2.id = t1.id and t2.name = t1.name and
t1.date between t2.startdate and t2.enddate
order by t1.id, t1.name, t1.date, random())
) tt1
where tt1.name = t1.name and
tt1.id = t1.id and
tt1.date = t1.date;

How to group complicated condition in sql

I'd like to group by region where there are customerswho has type=a
region customer type score
A a a 1
A b b 2
A c a 3
B d c 4
B e d 5
C f a 6
C g c 7
Therefore after first step
region customer type score
A a a 1
A b b 2
A c a 3
C f a 6
C g c 7
And then I groupby in region
region sum(score)
A 6
C 13
also I'd like to extract customer whose type=a
region customer type
A a a
A c a
C f a
Then I'd like to merge above.
My desired result is like following
customer sum_in_region
a 6
c 6
f 13
Are there any way to achieve this?
My work is till the second step..
How can I proceed further?
SELECT t1.region,t1.customer, t1.type, t1.score
FROM yourTable t1
WHERE EXISTS (SELECT 1
FROM yourTable t2
WHERE t2.region = t1.region
AND t2.type = 'a');
Thanks
Join the table to a derived table that does your first two steps.
SELECT t3.customer,
x1.score
FROM yourtable t3
INNER JOIN (SELECT t1.region,
sum(score) score
FROM yourtable t1
WHERE EXISTS (SELECT *
FROM yourtable t2
WHERE t2.region = t1.region
AND t2.type = 'a')
GROUP BY t1.region) x1
ON x1.region = t3.region
WHERE t2.type = 'a';
You could use the windows functions to get your result; the first step filters for only rows where type is a, based on the region. The second step then gets the sum of scores, based again on the region, before selecting only customer and sum columns :
with filter_type_a as
(select region, customer, type, score
from
(select *,
sum(type=="a") over (partition by region) as counter
from your_table)
where counter > 0)
select customer, sum_region
from
(select customer, type,
sum(score) over (partition by region) as sum_region
from filter_type_a)
where type=="a";
You can use below query:
SQLFiddle
with country_tmp as
(SELECT t1.region,t1.customer, t1.type, t1.score
FROM country t1
WHERE EXISTS (SELECT 1
FROM country t2
WHERE t2.region = t1.region
AND t2.type = 'a'))
select y.customer, x.score from
(select a.region, sum(a.score) score from (
SELECT t1.region,t1.customer, t1.type, t1.score
FROM country_tmp t1) a
group by region) x , (SELECT t1.region,t1.customer, t1.type
FROM country_tmp t1
Where t1.type = 'a') y where x.region = y.region;

Waterfall join conditions

I have two tables similar to:
Table 1 --unique ID's
ID Date
1 3/8/2017
2 3/8/2017
3 3/8/2017
Table 2
ID Date SourceID
1 3/8/2017 1
1 3/8/2017 2
1 3/8/2017 3
2 3/8/2017 2
3 3/8/2017 1
3 3/8/2017 3
And I want to write a query that has a result like:
Result
ID SourceID
1 2
2 2
3 1
Where the source ID ordering should be 2, 1, 3
I have:
select Table1.ID
, COALESCE(Join1.SourceID, Join2.SourceID, Join3.SourceID) as SourceID
from Table1
left outer join Table2 Join1
on Table1.date = Join1.date
and Table1.ID = Join1.ID
and Join1.SourceID = 2
left outer join Table2 Join2
on Table1.date = Join2.date
and Table1.ID = Join2.ID
and Join2.SourceID = 1
and Join1.SourceID is null
left outer join Table2 Join3
on Table1.date = Join3.date
and Table1.ID = Join3.ID
and Join3.SourceID = 3
and Join1.SourceID is null
and Join2.SourceID is null
But this currently just keeps the records where sourceid = 2 and does not add in the other sourceid's.
Thanks in advance for any help. Let me know if you need any clarification. Using SQL-Server. I only need a few and fixed amount of sources so I am avoiding using a cursor.
This is a prioritization query. I would do it using outer apply:
select t1.*, t2.sourceId
from table1 t1 outer apply
(select top 1 t2.*
from table2 t2
where t2.id = t1.id and t2.date = t1.date
order by (case t2.sourceid when 2 then 1 when 1 then 2 when 3 then 3 end)
) t2;
Note: For readability, you can simplify the order by to:
order by charindex(cast(t2.sourceId as varchar(255)), '2,1,3')
If you are uncomfortable with outer apply, you can do the same thing with a single join:
select t1.*, t2.sourceId
from table1 t1 join
(select t2.*,
row_number() over (partition by id, date
order by (case t2.sourceid when 2 then 1 when 1 then 2 when 3 then 3 end)
) as seqnum
from table2 t2
) t2
on t2.id = t1.id and t2.date = t1.date and t2.seqnum = 1;

SQL Query - ID Join, Just Duplicates

I am working with Oracle SQL. I have two tables. One has ItemID and DatePurchased and the other has ItemID, CustomerID. I'm trying to join the tables so that I can see only those customers with multiple items.
In other words, if I had:
TABLE 1
ItemID---DatePurchased
1 MAR15
2 JUN10
3 APR02
and
TABLE 2
ItemID---CustomerID
1 1
2 1
3 2
I would want this returned:
TABLE 3
ItemID--DatePurchased--CustomerID
1 MAR15 1
2 JUN10 1
(Customer 2 is left out because he only has one item (ItemID=3)).
Any ideas on how to do this in SQL?
select ItemID, DatePurchased, CustomerID
from
(
select
T1.ItemID, T1.DatePurchased, T2.CustomerID,
count(*) over (partition by T2.CustomerId) as ItemCnt
from TABLE2 T2
join TABLE1 T1 on T1.ItemID = T2.ItemID
) dt
where ItemCnt > 1
select
T2.ItemID, T2.CustomerID, T1.DatePurchased
from TABLE2 as T2
inner join TABLE1 as T1 on T1.ItemID = T2.ItemID
where
T2.CustomerID in
(
select TT.CustomerID
from TABLE2 as TT
group by TT.CustomerID
having count(*) > 1
)

SQL: Outputting Multiple Rows When Joining From Same Table

My question is this: Is it possible to output multiple rows when joining from the same table?
With this code for example, I would like it to output 2 rows, one for each table. Instead, what it does is gives me 1 row with all of the data.
SELECT t1.*, t2.*
FROM table t1
JOIN table t2
ON t2.id = t1.oldId
WHERE t1.id = '1'
UPDATE
Well the problem that I have with the UNION/UNION ALL is this: I don't know what the t1.oldId value is equal to. All I know is the id for t1. I am trying to avoid using 2 queries so is there a way I could do something like this:
SELECT t1.*
FROM table t1
WHERE t1.id = '1'
UNION
SELECT t2.*
FROM table t2
WHERE t2.id = t1.oldId
SAMPLE DATA
messages_users
id message_id user_id box thread_id latest_id
--------------------------------------------------------
8 1 1 1 NULL NULL
9 2 1 2 NULL 16
10 2 65 1 NULL 15
11 3 65 2 2 NULL
12 3 1 1 2 NULL
13 4 1 2 2 NULL
14 4 65 1 2 NULL
15 5 65 2 2 NULL
16 6 1 1 2 NULL
Query:
SELECT mu.id FROM messages_users mu
JOIN messages_users mu2 ON mu2.latest_id IS NOT NULL
WHERE mu.user_id = '1' AND mu2.user_id = '1' AND ((mu.box = '1'
AND mu.thread_id IS NULL AND mu.latest_id IS NULL) OR mu.id = mu2.latest_id)
This query fixes my problem. But it seems the answer to my question is to not use a JOIN but a UNION.
You mean one row for t1 and one row from t2?
You're looking for UNION, not JOIN.
select * from table where id = 1
union
select * from table where oldid = 1
If you are trying to multiply rows in a table, you need UNION ALL (not UNION):
select *
from ((select * from t) union all
(select * from t)
) t
I also sometimes use a cross join to do this:
select *
from t cross join
(select 1 as seqnum union all select 2) vals
The cross join is explicitly multiplying the number of rows, in this case, with a sequencenumber attached.
Well, since it's the same table, you could do:
SELECT t2.*
FROM table t1
JOIN table t2
ON t2.id = t1.oldId
OR t2.id = t1.id
WHERE t1.id = '1'