Could this be done more efficiently - sql

I have a table with two columns (p_id, id_type) where the p_id can have multiple types. I need to find the p_ids that do not have a specific type.
P_ID ID_TYPE
----------- -------------
12456 6
12456 7
56897 10
25686 9
25686 22
25686 7
56897 22
This is the query I used but wondering if there is a more efficient way to do this.
select p_id
into #temp1
from table2
where id_type = 6
SELECT
distinct table2.p_id
,table1.NAME
,table1.TYPE
FROM
table2 left join table1
on table2.p_id = table1.p_id
where
table2.p_id not in
(select p_id from #temp1)
and type = 'XYZ'
Expected outcome should be those P_IDs that DO NOT have an ID_TYPE = 6.
P_ID Name Type
56897 Smith Physician
25686 Jones Physician

Assuming I'm understanding your question correctly, you're trying to select all the p_id rows that don't have any corresponding p_id rows with a specific type.
If so, there are a couple of ways to do this. One is to use NOT IN:
select *
from yourtable
where p_id not in (
select p_id
from yourtable
where id_type = 6)
SQL Fiddle Demo
Using NOT EXISTS:
select *
from yourtable t
where not exists (
select 1
from yourtable t2
where t.p_id = t2.p_id and
t2.id_type = 6)
More Fiddle
You could also use an OUTER JOIN to achieve the same result.
If you want just specific p_id's, then you need to add DISTINCT. It's not clear what you're expected output should be.

A more SQLy way to do this is to use a single left join to find something called a Relative Complement. Essentially what we want to say is "Take all of the p_id, then take away all the ones that have an id_type of 6".
SELECT DISTINCT t.p_id
FROM table2 AS t
LEFT OUTER JOIN table2 AS t2 ON t.p_id = t2.p_id
AND t2.id_type = 6
WHERE t2.p_id IS NULL

Related

Select max value from a table and increment it in another

I'm struggling with a sql query, and will appreciate any help.
I have two tables, they both have a sort column. The first one looks like this:
person_id
image_name
sort_number
739
chest.png
1
739
legs.png
2
And the second table like this
person_id
advert
sort_number
739
house.png
1
739
car.png
2
I want to be able to select the max sort_number from the table1 and make the first sort_number in table2 (house.png) to become 3, and the sort_number for car.png) to become 4.
Essentially, what I'm looking to achieve is an insert statement that selects from table2 and insert into table1, but I need the sort_number not to have duplicate, so the starting sort_number from the table2 should be the max of table1+1...and so on. If table1 does not have the person, I simply insert and not change the sort_number value.
I would appreciate of someone can help me please.
Here's one way:
With grouped as
(Select person_id, max(sort_num) as maxsort
From table1
Group by person_id)
Select t2.person_id, t2.advert, t2.sort_num + coalesce(g.maxsort,0) as newsortnum
From table t2
Left join grouped g on t2.person_id = g.person_id
This will get max value of sort number for each key in the first table, and then attempt to join the second table to this grouped dataset. If there is a match, you add your second table's value to the max, and retain the value from the second table otherwise.
You could try using UNION ALL and ROW_NUMBER:
WITH CTE AS
(
SELECT
person_id,
image_name,
sort_number,
1 sort_table
FROM dbo.Table1 t1
UNION ALL
SELECT
person_id,
advert,
sort_number,
2
FROM dbo.Table2 t2
)
SELECT
person_id,
image_name,
ROW_NUMBER() OVER(PARTITION BY person_id ORDER BY sort_table, sort_number) sort_number
FROM CTE
;
insert into table2(person_id, advert, sort_number)
select table1.person_id, table1.image_name, table1.sort_number + table2.sort_number
from table1
join table2
on 1 = 1
left join table2 newer
on table2.sort_number < newer.sort_number
left join table1 mismatch
on table2.person_id = mistmatch.person_id and table2.advert = mismatch.image_name
where newer.person_id is null and mismatch.person_id is null
Firs join: we need to pair table1 and table2
Second join: we make sure that table2 record in the pair is the newest
Third join: we make sure that we do not insert what's already there.

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 combining count with other fields

Consider a scenario:
id name info done
-----------------------
1 abc x 0
2 abc y 1 <-- I have this id
3 pqr g 1
4 pqr h 0
5 pqr i 1 <-- I have this id
I have id for the last entry of every name.
The result I'm expecting consists of 2 things:
info for last entry of the name
number of done [having value 1] for that name
(1) can be easily achieved by select info from table where id = myid
But how can (2) be achieved in the same query? Can it be achieved in the same query?
Something like
select info, count(done) from table where id = myid group by name where ......
This is a bit complicated, but can be done using conditional aggregation:
select max(case when t.id = myid then info end), sum(done)
from table t
where t.name = (select name from table t2 where t2.id = myid);
The key is getting all the rows for the given name.
If you had multiple columns, then a correlated subquery might be the way to go:
select t.*,
(select sum(t2.done) from table t2 where t2.name = t.name) as numdone
from table t
where t.id = myid;
You could join the table back to itself on name to get this.
SELECT t1.myid, t1.info, sum(t2.done) as number_of_done
FROM table t1 INNER JOIN table t2 on t1.name = t2.name
WHERE t1.id = myid
GROUP BY t1.myid, t1.info
Considering done is either 1 or 0 you could just get the sum and display that.
select info, sum(done)
from table where id = mid
group by info
EDIT:
select info, s
from table
inner join (
select name, sum(done) as s
from table
group by name
) as zzz on zzz.name = table.name
where id = myid
If you want it to display with more detailed data, use a windowing function:
select info, count(done) over (partition by name) ...

How can I avoid a sub-query?

This is my table:
ID KEY VALUE
1 alpha 100
2 alpha 500
3 alpha 22
4 beta 60
5 beta 10
I'm trying to retrieve a list of all KEY-s with their latest values (where ID is in its maximum):
ID KEY VALUE
3 alpha 22
5 beta 10
In MySQL I'm using this query, which is not effective:
SELECT temp.* FROM
(SELECT * FROM t ORDER BY id DESC) AS temp
GROUP BY key
Is it possible to avoid a sub-query in this case?
Use an INNER JOIN to join with your max ID's.
SELECT t.*
FROM t
INNER JOIN (
SELECT ID = MAX(ID)
FROM t
GROUP BY
key
) tm ON tm.ID = t.ID
Assuming the ID column is indexed, this is likely as fast as its going to get.
here is the mysql documentation page that discusses this topic.
it presents three distinct options.
the only one that doesn't involve a sub query is:
SELECT t1.id, t1.k, t1.value
FROM t t1
LEFT JOIN t t2 ON t1.k = t2.k AND t1.id < t2.id
WHERE t2.k IS NULL;
There's page in the manual explaining how to do this

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?