How to accumulate matches when querying multiple tables in SQLite? - sql

Table1
ID Name CourseID
1 Course 1 4002
2 Course 2 2342
3 Course 3 2410
Table2
CourseID ProfName
4002 John
2342 bob
2410 Bill
4002 Hannah
2342 Cyrus
When I try
SELECT ID, Name, CourseID, ProfName
FROM Table1, Table2
WHERE Table1.CourseID = Table2.CourseID
I get multiple instances for the same CourseID returned such that when I print it out
"1, Course 1, 4002, John" and
"1, Course 1, 4002, Hannah" are two different outputs.
I would like them to be of the form
"1, Course 1, 4002, John and Hannah"
Not sure how to alter my SQL query to make this happen?

Use string aggregation:
select t1.id, t1.name, t1.courseid, group_concat(t2.profname, ' and ') profnames
from table1 t1
inner join table2 t2 on t1.courseid = t2.courseid
group by t1.id, t1.name, t1.courseid
Note that this uses standard join syntax (join ... on ...) rather than implicit joins (with a comma in the from clause): this old-school syntax should not be used in new code.
You could also use a subquery:
select t1.*
(
select group_concat(t2.profname, ' and ')
from table2 t2
where t2.courseid = t1.courseid
) profnames
from table1 t1
Unrelated note: and does not seem like a great choice for a list separator: if there are more than two values, the result is not correct English. A more usual choice is, for example, the comma (,) - that's the default separator in SQLite and most other databases.

I am guessing you want commas between most of the names and and only for the last one. That would be:
select t1.*, t2.profnames
from table1 t1 join
(select t2.courseid,
(group_concat(case when seqnum > 1 then profname end) || ', and '
max(case when seqnum = 1 then profname end)
) as profnames
from (select t2.*,
row_number() over (partition by courseid order by profname desc) as seqnum,
from table2 t2
) t2
group by t2.courseid
) t2
on t1.courseid = t2.courseid;

Related

Joining and grouping to equate on two tables

I've tried to minify this problem as much as possible. I've got two tables which share some Id's (among other columns)
id id
---- ----
1 1
1 1
2 1
2
2
Firstly, I can get each table to resolve to a simple count of how many of each Id there is:
select id, count(*) from tbl1 group by id
select id, count(*) from tbl2 group by id
id | tbl1-count id | tbl2-count
--------------- ---------------
1 2 1 3
2 1 2 2
but then I'm at a loss, I'm trying to get the following output which shows the count from tbl2 for each id, divided by the count from tbl1 for the same id:
id | count of id in tbl2 / count of id in tbl1
==========
1 | 1.5
2 | 2
So far I've got this:
select tbl1.Id, tbl2.Id, count(*)
from tbl1
join tbl2 on tbl1.Id = tbl2.Id
group by tbl1.Id, tbl2.Id
which just gives me... well... something nowhere near what I need, to be honest! I was trying count(tbl1.Id), count(tbl2.Id) but get the same multiplied amount (because I'm joining I guess?) - I can't get the individual representations into individual columns where I can do the division.
This gives consideration to your naming of tables -- the query from tbl2 needs to be first so the results will include all records from tbl2. The LEFT JOIN will include all results from the first query, but only join those results that exist in tbl1. (Alternatively, you could use a FULL OUTER JOIN or UNION both results together in the first query.) I also added an IIF to give you an option if there are no records in tbl1 (dividing by null would produce null anyway, but you can do what you want).
Counts are cast as decimal so that the ratio will be returned as a decimal. You can adjust precision as required.
SELECT tb2.id, tb2.table2Count, tb1.table1Count,
IIF(ISNULL(tb1.table1Count, 0) != 0, tb2.table2Count / tb1.table1Count, null) AS ratio
FROM (
SELECT id, CAST(COUNT(1) AS DECIMAL(18, 5)) AS table2Count
FROM tbl2
GROUP BY id
) AS tb2
LEFT JOIN (
SELECT id, CAST(COUNT(1) AS DECIMAL(18, 5)) AS table1Count
FROM tbl1
GROUP BY id
) AS tb1 ON tb1.id = tb2.id
(A subqquery with a LEFT JOIN will allow the query optimizer to determine how to generate the results and will generally outperform a CROSS APPLY, as that executes a calculation for every record.)
Assuming your expected results are wrong, then this is how I would do it:
CREATE TABLE T1 (ID int);
CREATE TABLE T2 (ID int);
GO
INSERT INTO T1 VALUES(1),(1),(2);
INSERT INTO T2 VALUES(1),(1),(1),(2),(2);
GO
SELECT T1.ID AS OutID,
(T2.T2Count * 1.) / COUNT(T1.ID) AS OutCount --Might want a CONVERT to a smaller scale and precision decimal here
FROM T1
CROSS APPLY (SELECT T2.ID, COUNT(T2.ID) AS T2Count
FROM T2
WHERE T2.ID = T1.ID
GROUP BY T2.ID) T2
GROUP BY T1.ID,
T2.T2Count;
GO
DROP TABLE T1;
DROP TABLE T2;
You can aggregate in subqueries and then join:
select t1.id, t2.cnt * 1.0 / t1.cnt
from (select id, count(*) as cnt
from tbl1
group by id
) t1 join
(select id, count(*) as cnt
from tbl2
group by id
) t2
on t1.id = t2.id

SQL Select Between Two tables

I have these two tables:
T1:
ref || Name
===========
1 || A
2 || B
3 || C
4 || D
5 || E
And
T2:
ref || Name
===========
1 || w
2 || x
6 || y
7 || z
I need this result:
Name1 || Name2
==============
A || w
B || x
C || y
D || z
E || NULL
I mean some kind of full outer join, on column ref, that will not produce NULL value until there is not any record.
The priority of join is with the values that have same ref, and if row count of tables are not equal, there are some NULL results
Use UPD: here is how you may combine values by values count from different tables:
with t1_values as (
SELECT
name,
row_number() over (order by ref) as position
FROM #t1
),
t2_values as (
SELECT
name,
row_number() over (order by ref) as position
FROM #t2
)
SELECT
t1_values.name as name1,
t2_values.name as name2
FROM t1_values
left JOIN t2_values on t1_values.position = t2_values.position
This is very complicated. You want rows that match to match. Then you want unmatched rows to match unmatched rows by position, and then everything else.
with matches as (
select distinct t1.ref
from t1
where exists (select 1 from t2 where t2.ref = t1.ref)
),
tt1 as (
select t1.*, m.ref as match_ref,
row_number() over (partition by m.ref order by t1.ref) as alt_ref
from t1 left join
matches m
on t1.ref = m.ref
),
tt2 as (
select t2.*, m.ref as match_ref,
row_number() over (partition by m.ref order by t2.ref) as alt_ref
from t2 left join
matches m
on t2.ref = m.ref
)
select tt1.name, tt2.name
from tt1 left join
tt2
on tt1.match_ref = tt2.match_ref or
(tt1.match_ref is null and tt2.match_ref is null and tt1.alt_ref = tt2.alt_ref);
Here is the idea. For each row in both tables, add two new columns:
match_ref is ref when ref exists in the other table.
alt_ref is an enumerated column for the ref values that do not match.
Once you have these columns, it is possible to join the tables together, by first checking match_ref and then -- if that is not present -- checking alt.ref.
SQL Fiddle does not appear to be working for SQL Server. However, here is an identical Postgres version that does work. Here is a working version using SQL Server (this is identical to the Postgres version).
A look at your Question
Correct me if I am wrong, but you have two tables that have an reference ID column of names that you wish to return results sets....only, you do not say distinct so you might end up with extras.
...some kind of full outer join, on column ref, that will not
produce NULL value until there is not any record..
The priority of the JOIN is with the values that have same ref, and if row count of tables are
not equal, there are some NULL results
Actually, the result is not really deterministic. Anyways, this question is still too vague. However, I think you really just want rows matching columns to appear together and anything not....well, not. But return everything.
So, if you know which side is larger, try this:
WITH C AS (SELECT DENSE_RANK() OVER (PARTITION BY ref ORDER BY NAME DESC) AS ROW_ID
, ref
, Name AS Name1
FROM T1)
SELECT Name1, B.Name2
FROM C
LEFT OUTER JOIN (SELECT DENSE_RANK() (OVER PARTITION BY ref ORDER BY NAME DESC) AS ROW_ID
, ref
, Name AS Name2) B ON B.Row_ID = C.Row_ID AND B.ref = C.ref
Each of the ref columns have a distinct ID to attach with. It is done in consecutive order, so if there is still an issue, well, you can figure that logic out. But I'm sure this will help you tremendously get where you are wanting. :)
Thank you every body and excuse me if I speak badly sometimes, count it on my fatigue because of hours working.
I think I couldn't say my meaning correctly, so I answered my own question. this is not the best answer and it is not optimized, I know, but it works!
SELECT t.ref,
t.NAME AS n1,
t2.NAME AS n2
INTO #tbl1
FROM #T1 AS t
LEFT JOIN #T2 AS t2
ON t2.ref = t.ref
WHERE t2.ref IS NOT NULL
SELECT ROW_NUMBER() OVER(ORDER BY t.NAME) AS rn,
*
INTO #tbl2
FROM #T1 AS t
WHERE t.ref NOT IN (SELECT ref
FROM #tbl1)
SELECT ROW_NUMBER() OVER(ORDER BY t.NAME) AS rn,
*
INTO #tbl3
FROM #T2 AS t
WHERE t.ref NOT IN (SELECT ref
FROM #tbl1)
SELECT n1,
n2
FROM #tbl1
UNION
SELECT t1.NAME AS n1,
t2.NAME AS n2
FROM #tbl2 t1
FULL OUTER JOIN #tbl3 t2
ON t1.rn = t2.rn

Querying two tables to filter data using select case

I have two tables
Table 1 looks like this
ID Repeats
-----------
A 1
A 1
A 0
B 2
B 2
C 2
D 1
Table 2 looks like this
ID values
-----------
A 100
B 200
C 100
D 300
Using a view I need a result like this
ID values Repeats
-------------------
A 100 NA
B 200 2
C 100 2
D 300 1
that means, I want unique ID, its values and Repeats. Repeats value should display NA when there are multiple values against single ID and it should display the Repeats value in case there is single value for repeats.
Initially I needed to display the max value of repeats so I tried the following view
ALTER VIEW [dbo].[BookingView1]
AS
SELECT bv.*, bd2.Repeats FROM Table1 bv
JOIN
(
SELECT distinct bd.id, bd.Repeats FROM table2 bd
JOIN
(
SELECT Id, MAX(Repeats) AS MaxRepeatCount
FROM table2
GROUP BY Id
) bd1
ON bd.Id = bd1.Id
AND bd.Repeats = bd1.MaxRepeatCount
) bd2
ON bv.Id = bd2.Id;
and this returns the correct result but when trying to implement the CASE it fails to return unique ID results. Please help!!
One method uses outer apply:
select t2.*, t1.repeats
from table2 t2 outer apply
(select (case when max(repeats) = min(repeats) then max(repeats)
else 'NA'
end) as repeats
from table1 t1
where t1.id = t2.id
) t1;
Two notes:
This assumes that repeats is a string. If it is a number, you need to cast it to a string.
repeats is not null.
For the sake of completeness, I'm including another approach that will work if repeats is NULL. However, Gordon's answer has a much simpler query plan and should be preferred.
Option 1 (Works with NULLs):
SELECT
t1.ID, t2.[Values],
CASE
WHEN COUNT(*) > 1 THEN 'NA'
ELSE CAST(MAX(Repeats) AS VARCHAR(2))
END Repeats
FROM (
SELECT DISTINCT t1.ID, t1.Repeats
FROM #table1 t1
) t1
LEFT OUTER JOIN #table2 t2
ON t1.ID = t2.ID
GROUP BY t1.ID, t2.[Values]
Option 2 (does not contain explicit subqueries, but does not work with NULLs):
SELECT DISTINCT
t1.ID,
t2.[Values],
CASE
WHEN COUNT(t1.Repeats) OVER (PARTITION BY COUNT(DISTINCT t1.Repeats), t1.ID) > 1 THEN 'NA'
ELSE CAST(t1.Repeats AS VARCHAR(2))
END Repeats
FROM #table1 t1
LEFT OUTER JOIN #table2 t2
ON t1.ID = t2.ID
GROUP BY t1.ID, t2.[Values], t1.Repeats
NOTE:
This may not give desired results if table2 has different values for the same ID.

SQL select 1 to many within the same row

I have a table with 1 record, which then ties back to a secondary table which can contain either no match, 1 match, or 2 matches.
I need to fetch the corresponding records and display them within the same row which would be easy using left join if I just had 1 or no matches to tie back, however, because I can get 2 matches, it produces 2 records.
Example with 1 match:
Select T1.ID, T1.Person1, T2.Owner
From T1
Left Join T2
ON T1.ID = T2.MatchID
Output
ID Person1 Owner1
----------------------
1 John Frank
Example with 2 match:
Select T1.ID, T1.Person1, T2.Owner
From T1
Left Join T2
ON T1.ID = T2.MatchID
Output
ID Person1 Owner
----------------------
1 John Frank
1 John Peter
Is there a way I can formulate my select so that my output would reflect the following When I have 2 matches:
ID Person1 Owner1 Owner2
-------------------------------
1 John Frank Peter
I explored Oracle Pivots a bit, however couldn't find a way to make this work. Also explored the possibility of using left join on the same table twice using MIN() and MAX() when fetching the matches, however I can only see myself resorting this as a "no other option" scenario.
Any suggestions?
** EDIT **
#ughai - Using CTE does address the issue to some extent, however when attempting to retrieve all of the records, the details derived from this common table isn't showing any records on the LEFT JOIN unless I specify the "MatchID" (CASE_MBR_KEY) value, meaning by removing the "where" clause, my outer joins produce no records, even though the CASE_MBR_KEY values are there in the CTE data.
WITH CTE AS
(
SELECT TEMP.BEAS_KEY,
TEMP.CASE_MBR_KEY,
TEMP.FULLNAME,
TEMP.BIRTHDT,
TEMP.LINE1,
TEMP.LINE2,
TEMP.LINE3,
TEMP.CITY,
TEMP.STATE,
TEMP.POSTCD,
ROW_NUMBER()
OVER(ORDER BY TEMP.BEAS_KEY) R
FROM TMP_BEN_ASSIGNEES TEMP
--WHERE TEMP.CASE_MBR_KEY = 4117398
)
The reason for this is because the ROW_NUMBER value, given the amount of records won't necessarily be 1 or 2, so I attempted the following, but getting ORA-01799: a column may not be outer-joined to a subquery
--// BEN ASSIGNEE 1
LEFT JOIN CTE BASS1
ON BASS1.CASE_MBR_KEY = C.CASE_MBR_KEY
AND BASS1.R IN (SELECT min(R) FROM CTE A WHERE A.CASE_MBR_KEY = C.CASE_MBR_KEY)
--// END BA1
--// BEN ASSIGNEE 2
LEFT JOIN CTE BASS2
ON BASS2.CASE_MBR_KEY = C.CASE_MBR_KEY
AND BASS2.R IN (SELECT MAX(R) FROM CTE B WHERE B.CASE_MBR_KEY = C.CASE_MBR_KEY)
--// END BA2
** EDIT 2 **
Fixed the above issue by moving the Row number clause to the "Where" portion of the query instead of within the JOIN clause. Seems to work now.
You can use CTE with ROW_NUMBER() with 2 LEFT JOIN OR with PIVOT like this.
SQL Fiddle
Query with Multiple Left Joins
WITH CTE as
(
SELECT MatchID,Owner,ROW_NUMBER()OVER(ORDER BY Owner) r FROM t2
)
select T1.ID, T1.Person, t2.Owner as Owner1, t3.Owner as Owner2
FROM T1
LEFT JOIN CTE T2
ON T1.ID = T2.MatchID AND T2.r = 1
LEFT JOIN CTE T3
ON T1.id = T3.MatchID AND T3.r = 2;
Query with PIVOT
WITH CTE as
(
SELECT MatchID,Owner,ROW_NUMBER()OVER(ORDER BY Owner) R FROM t2
)
SELECT ID, Person,O1,O2
FROM T1
LEFT JOIN CTE T2
ON T1.ID = T2.MatchID
PIVOT(MAX(Owner) FOR R IN (1 as O1,2 as O2));
Output
ID PERSON OWNER1 OWNER2
1 John Maxwell Peter
If you know there are at most two matches, you can also use aggregation:
Select T1.ID, T1.Person1,
MIN(T2.Owner) as Owner1,
(CASE WHEN MIN(t2.Owner) <> MAX(t2.Owner) THEN MAX(t2.Owner) END) as Owner2
From T1 Left Join
T2
on T1.ID = T2.MatchID
Group By t1.ID, t1.Person1;

mysql - union tables by unique field

I have two tables with the same structure:
id name
1 Merry
2 Mike
and
id name
1 Mike
2 Alis
I need to union second table to first with keeping unique names, so that result is:
id name
1 Merry
2 Mike
3 Alis
Is it possible to do this with MySQL query, without using php script?
This is not a join (set multiplication), this is a union (set addition).
SELECT #r := #r + 1 AS id, name
FROM (
SELECT #r := 0
) vars,
(
SELECT name
FROM table1
UNION
SELECT name
FROM table2
) q
This will select all names from table1 and combine those with all the names from table2 which are not in table1.
(
select *
from table1
)
union
(
select *
from table2 t2
left join table1 t1 on t2.name = t1.name
where t1.id is null
)
Use:
SELECT a.id,
a.name
FROM TABLE_A a
UNION
SELECT b.id,
b.name
FROM TABLE_B b
UNION will remove duplicates.
As commented, it all depends on what your 'id' means, cause in the example, it means nothing.
SELECT DISTINCT(name) FROM t1 JOIN t2 ON something
if you only want the names
SELECT SUM(something), name FROM t1 JOIN t2 ON something GROUP BY name
if you want to do some group by
SELECT DISTINCT(name) FROM t1 JOIN t2 ON t1.id = t2.id
if the id's are the same
SELECT DISTINCT COALESCE(t1.name,t2.name) FROM
mytable t1 LEFT JOIN mytable t2 ON (t1.name=t2.name);
will get you a list of unique names from the 2 tables. If you want them to get new ids (like Alis does in your desired results), that's something else and requires the answers to a couple of questions:
do any of the names need to maintain their previous id. And if they do, which table's id should be preferred?
why do you have 2 tables with the same structure? ie what are you trying to accomplish when you generate the unique name list?