how to find id in a table that is skipped - mysql

Lets say I have a table which has a primary key of 1 to X. During the development of the table there are entries which have been deleted, say I deleted entry 5. And hence the table entry will be 1,2,3,4,6,7,8. Is there a query to find all id of the primary key which has been skipped?

Take a look at this link, it have a solution to your problem
Quoted from the link:
select l.id + 1 as start
from sequence as l
left outer join sequence as r on l.id + 1 = r.id
where r.id is null;
Supposing you have a table called sequence with primary key column Id starting from 1, With values: 1,2,3,4, 6,7, 9,...
This sample code will select 5 and 8.

simsim's answer will not return all missing keys if 2 keys in a sequence are missing. SQLFiddle demo: http://sqlfiddle.com/#!2/cc241/1
Instead create a numbers table and join where the key is null:
CREATE TABLE Numbers(
Num INTEGER
)
DECLARE #id INTEGER
SELECT #id = 1
WHILE #id >=1 AND #id <= 100000
BEGIN
INSERT INTO Numbers
VALUES(#id)
SELECT #id += 1
END
SELECT *
FROM Numbers N
LEFT JOIN your_table YT ON N.Num = YT.PrimaryKey
WHERE YT.Primary IS NULL

Related

SQL : Find if table's 'A' ID exists to table's 'B' ID

I have 2 tables. Table A is priceList that contains 2 columns. ID and currency_id . Table B is priceListItems that contains 3 columns. Product priceListID ProductID.
For Example priceList
ID
CurrencyID
3
DF10CCE
And priceListItems
ID
priceListID
Product
1
3
DESK
I would like to write a statement in SQL to return a boolean (0 or 1) if the priceList has Items in, comparing with priceListItems based on their ID columns (For table A: ID , and for Table B: priceListID )
So if the priceList's ID exists in priceListItems's priceListID, the result should be true.
How i can achieve that?
Is the follwing correct?
SELECT priceList.id,
IF(priceListItems.id IS NULL, FALSE, TRUE) as priceListItems
FROM priceList
LEFT JOIN priceListItems ON (priceList.id = priceListItems.id)
SELECT id,
EXISTS ( SELECT NULL
FROM priceListItems
WHERE priceList.id = priceListItems.priceListID )
FROM priceList;
priceList.id = priceListItems.id this test is incorrect and should be priceList.id = priceListItems.pricelistid
and IF(priceListItems.id IS NULL, FALSE, TRUE) is dubious surely if null then true makes more sense..
The if() function can get the job nicely done. Assuming ID 4 from the pricelist has two rows in the pricelistitems and ID 5 from the pricelist has no entry in the pricelistitems, we can try:
insert priceList values(3,'DF10CCE'),(4,'DF11223'),(5,'DD11225');
insert priceListItems values(1,3,'desk'),(2,4,'pp'),(3,4,'ss');
select id, if((select count(*) from priceListItems where pricelistid=pl.id),1,0) from priceList pl;
The correlated subquery (select count(*) from priceListItems where pricelistid=pl.id) returns an int which is greater or equal to 0, which can be used directly as a boolean in the if() function to determine which value should be returned.

recursive query for single table [duplicate]

My database schema looks like this:
Table t1:
id
valA
valB
Table t2:
id
valA
valB
What I want to do, is, for a given set of rows in one of these tables, find rows in both tables that have the same valA or valB (comparing valA with valA and valB with valB, not valA with valB). Then, I want to look for rows with the same valA or valB as the rows in the result of the previous query, and so on.
Example data:
t1 (id, valA, valB):
1, a, B
2, b, J
3, d, E
4, d, B
5, c, G
6, h, J
t2 (id, valA, valB):
1, b, E
2, d, H
3, g, B
Example 1:
Input: Row 1 in t1
Output:
t1/4, t2/3
t1/3, t2/2
t2/1
...
Example 2:
Input: Row 6 in t1
Output:
t1/2
t2/1
I would like to have the level of the search at that the row was found in the result (e.g. in Example 1: Level 1 for t1/2 and t2/1, level 2 for t1/5, ...) A limited depth of recursion is okay. Over time, I maybe want to include more tables following the same schema into the query. It would be nice if it was easy to extend the query for that purpose.
But what matters most, is the performance. Can you tell me the fastest possible way to accomplish this?
Thanks in advance!
try this although it's not fully tested but looked like it was working :P (http://pastie.org/1140339)
drop table if exists t1;
create table t1
(
id int unsigned not null auto_increment primary key,
valA char(1) not null,
valB char(1) not null
)
engine=innodb;
drop table if exists t2;
create table t2
(
id int unsigned not null auto_increment primary key,
valA char(1) not null,
valB char(1) not null
)
engine=innodb;
drop view if exists t12;
create view t12 as
select 1 as tid, id, valA, valB from t1
union
select 2 as tid, id, valA, valB from t2;
insert into t1 (valA, valB) values
('a','B'),
('b','J'),
('d','E'),
('d','B'),
('c','G'),
('h','J');
insert into t2 (valA, valB) values
('b','E'),
('d','H'),
('g','B');
drop procedure if exists find_children;
delimiter #
create procedure find_children
(
in p_tid tinyint unsigned,
in p_id int unsigned
)
proc_main:begin
declare done tinyint unsigned default 0;
declare dpth smallint unsigned default 0;
create temporary table children(
tid tinyint unsigned not null,
id int unsigned not null,
valA char(1) not null,
valB char(1) not null,
depth smallint unsigned default 0,
primary key (tid, id, valA, valB)
)engine = memory;
insert into children select p_tid, t.id, t.valA, t.valB, dpth from t12 t where t.tid = p_tid and t.id = p_id;
create temporary table tmp engine=memory select * from children;
/* http://dec.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
while done <> 1 do
if exists(
select 1 from t12 t
inner join tmp on tmp.valA = t.valA or tmp.valB = t.valB and tmp.depth = dpth) then
insert ignore into children
select
t.tid, t.id, t.valA, t.valB, dpth+1
from t12 t
inner join tmp on tmp.valA = t.valA or tmp.valB = t.valB and tmp.depth = dpth;
set dpth = dpth + 1;
truncate table tmp;
insert into tmp select * from children where depth = dpth;
else
set done = 1;
end if;
end while;
select * from children order by depth;
drop temporary table if exists children;
drop temporary table if exists tmp;
end proc_main #
delimiter ;
call find_children(1,1);
call find_children(1,6);
You can do it with stored procedures (see listings 7 and 7a):
http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html
You just need to figure out a query for the step of the recursion - taking the already-found rows and finding some more rows.
If you had a database which supported SQL-99 recursive common table expressions (like PostgreSQL or Firebird, hint hint), you could take the same approach as in the above link, but using a rCTE as the framework, so avoiding the need to write a stored procedure.
EDIT: I had a go at doing this with an rCTE in PostgreSQL 8.4, and although i can find the rows, i can't find a way to label them with the depth at which they were found. First, i create a a view to unify the tables:
create view t12 (tbl, id, vala, valb) as (
(select 't1', id, vala, valb from t1)
union
(select 't2', id, vala, valb from t2)
)
Then do this query:
with recursive descendants (tbl, id, vala, valb) as (
(select *
from t12
where tbl = 't1' and id = 1) -- the query that identifies the seed rows, here just t1/1
union
(select c.*
from descendants p, t12 c
where (p.vala = c.vala or p.valb = c.valb)) -- the recursive term
)
select * from descendants;
You would imagine that capturing depth would be as simple as adding a depth column to the rCTE, set to zero in the seed query, then somehow incremented in the recursive step. However, i couldn't find any way to do that, given that you can't write subqueries against the rCTE in the recursive step (so nothing like select max(depth) + 1 from descendants in the column list), and you can't use an aggregate function in the column list (so no max(p.depth) + 1 in the column list coupled with a group by c.* on the select).
You would also need to add a restriction to the query to exclude already-selected rows; you don't need to do that in the basic version, because of the distincting effect of the union, but if you add a count column, then a row can be included in the results more than once with different counts, and you'll get a Cartesian explosion. But you can't easily prevent it, because you can't have subqueries against the rCTE, which means you can't say anything like and not exists (select * from descendants d where d.tbl = c.tbl and d.id = c.id)!
I know all this stuff about recursive queries is of no use to you, but i find it riveting, so please do excuse me.

Looking for missed IDs in SQL Server 2008

I have a table that contains two columns
ID | Name
----------------
1 | John
2 | Sam
3 | Peter
6 | Mike
It has missed IDs. In this case these are 4 and 5.
How do I find and insert them together with random names into this table?
Update: cursors and temp tables are not allowed. The random name should be 'Name_'+ some random number. Maybe it would be the specified value like 'Abby'. So it doesn't matter.
Using a recursive CTE you can determine the missing IDs as follows
DECLARE #Table TABLE(
ID INT,
Name VARCHAR(10)
)
INSERT INTO #Table VALUES (1, 'John'),(2, 'Sam'),(3,'Peter'),(6, 'Mike')
DECLARE #StartID INT,
#EndID INT
SELECT #StartID = MIN(ID),
#EndID = MAX(ID)
FROM #Table
;WITH IDS AS (
SELECT #StartID IDEntry
UNION ALL
SELECT IDEntry + 1
FROM IDS
WHERE IDEntry + 1 <= #EndID
)
SELECT IDS.IDEntry [ID]
FROM IDS LEFT JOIN
#Table t ON IDS.IDEntry = t.ID
WHERE t.ID IS NULL
OPTION (MAXRECURSION 0)
The option MAXRECURSION 0 will allow the code to avoid the recursion limit of SQL SERVER
From Query Hints and WITH common_table_expression (Transact-SQL)
MAXRECURSION number Specifies the maximum number of recursions
allowed for this query. number is a nonnegative integer between 0 and
32767. When 0 is specified, no limit is applied. If this option is not specified, the default limit for the server is 100.
When the specified or default number for MAXRECURSION limit is reached
during query execution, the query is ended and an error is returned.
Because of this error, all effects of the statement are rolled back.
If the statement is a SELECT statement, partial results or no results
may be returned. Any partial results returned may not include all rows
on recursion levels beyond the specified maximum recursion level.
Generating the RANDOM names will largly be affected by the requirements of such a name, and the column type of such a name. What exactly does this random name entail?
You can do this using a recursive Common Table Expression CTE. Here's an example how:
DECLARE #MaxId INT
SELECT #MaxId = MAX(ID) from MyTable
;WITH Numbers(Number) AS
(
SELECT 1
UNION ALL
SELECT Number + 1 FROM Numbers WHERE Number < #MaxId
)
SELECT n.Number, 'Random Name'
FROM Numbers n
LEFT OUTER JOIN MyTable t ON n.Number=t.ID
WHERE t.ID IS NULL
Here are a couple of articles about CTEs that will be helpful to Using Common Table Expressions and Recursive Queries Using Common Table Expressions
Start by selecting the highest number in the table (select top 1 id desc), or select max(id), then run a while loop to iterate from 1...max.
See this article about looping.
For each iteration, see if the row exists, and if not, insert into table, with that ID.
I think recursive CTE is a better solution, because it's going to be faster, but here is what worked for me:
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[TestTable]') AND type in (N'U'))
DROP TABLE [dbo].[TestTable]
GO
CREATE TABLE [dbo].[TestTable](
[Id] [int] NOT NULL,
[Name] [varchar](50) NOT NULL,
CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED
(
[Id] ASC
))
GO
INSERT INTO [dbo].[TestTable]([Id],[Name]) VALUES (1, 'John')
INSERT INTO [dbo].[TestTable]([Id],[Name]) VALUES (2, 'Sam')
INSERT INTO [dbo].[TestTable]([Id],[Name]) VALUES (3, 'Peter')
INSERT INTO [dbo].[TestTable]([Id],[Name]) VALUES (6, 'Mike')
GO
declare #mod int
select #mod = MAX(number)+1 from master..spt_values where [type] = 'P'
INSERT INTO [dbo].[TestTable]
SELECT y.Id,'Name_' + cast(newid() as varchar(45)) Name from
(
SELECT TOP (select MAX(Id) from [dbo].[TestTable]) x.Id from
(
SELECT
t1.number*#mod + t2.number Id
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
WHERE t1.[type] = 'P' and t2.[type] = 'P'
) x
WHERE x.Id > 0
ORDER BY x.Id
) y
LEFT JOIN [dbo].[TestTable] on [TestTable].Id = y.Id
where [TestTable].Id IS NULL
GO
select * from [dbo].[TestTable]
order by Id
GO
http://www.sqlfiddle.com/#!3/46c7b/18
It's actually very simple :
Create a table called #All_numbers which should contain all the natural number in the range that you are looking for.
#list is a table containing your data
select a.num as missing_number ,
'Random_Name' + convert(varchar, a.num)
from #All_numbers a left outer join #list l on a.num = l.Id
where l.id is null

SQL Group By And Display With Different Values

I want to create group query where Table values are like this below:
EMP_ID ProjectID
815 1
985 1
815 3
985 4
815 4
And i want output like this
EMP_ID ProjectID1 ProjectID2 ProjectID3
815 1 3 4
985 1 4 0
can anyone know how can i achieve this thing in SQL query.
Thank in advance.
The short way:
Using http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
SELECT
tbl.emp_id,
GROUP_CONCAT( DISTINCT project_id ) project_id_list
FROM tbl
GROUP BY tbl.emp_id
In this case, you have to split/process the concatenated project_id_list string (or NULL) in your application
The long way:
We will use a little trick:
http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html
For MyISAM tables you can specify AUTO_INCREMENT on a secondary column
in a multiple-column index. In this case, the generated value for the
AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1
WHERE prefix=given-prefix. This is useful when you want to put data
into ordered groups.
CREATE TEMPORARY TABLE temp (
emp_id INT NOT NULL,
-- project_num will count from 1 to N PER emp_id!
project_num INT NOT NULL AUTO_INCREMENT,
project_id INT NOT NULL,
PRIMARY KEY ( emp_id, project_num )
) ENGINE=MyISAM; -- works only with myisam!
Generate the per-group auto increments:
INSERT INTO temp ( emp_id, project_id )
SELECT emp_id, project_id FROM tbl
Calculate how many project_id columns are needed:
$MAX_PROJECTS_PER_EMP =
SELECT MAX( max_projects_per_emp ) FROM
( SELECT COUNT(*) AS max_projects_per_emp project_id FROM tbl GROUP BY emp_id )
Programmatically create the select expression:
SELECT
temp.emp_id,
t1.project_id AS project_id_1,
t2.project_id AS project_id_2,
t98.project_id AS project_id_98,
t99.project_id AS project_id_99,
FROM temp
LEFT JOIN temp AS t1 ON temp.emp_id = t1.id AND t1.project_num = 1
LEFT JOIN temp AS t2 ON temp.emp_id = t2.id AND t1.project_num = 2
// create $MAX_PROJECTS_PER_EMP lines of LEFT JOINs
LEFT JOIN temp AS t98 ON temp.emp_id = t98.id AND t98.project_num = 98
LEFT JOIN temp AS t99 ON temp.emp_id = t99.id AND t99.project_num = 99

Delete rows without leading zeros

I have a table with a column (registration_no varchar(9)). Here is a sample:
id registration no
1 42400065
2 483877668
3 019000702
4 837478848
5 464657588
6 19000702
7 042400065
Please take note of registration numbers like (042400065) and (42400065), they are almost the same, the difference is just the leading zero.
I want to select all registration numbers that have the same case as above and delete the ones without a leading zero i.e (42400065)
pls, also note that before i delete the ones without leading zeros (42400065), i need to be sure that there is an equivalent with leading zeros(042400065)
declare #T table
(
id int,
[registration no] varchar(9)
)
insert into #T values
(1, '42400065'),
(2, '483877668'),
(3, '019000702'),
(4, '837478848'),
(5, '464657588'),
(6, '19000702'),
(7, '042400065')
;with C as
(
select row_number() over(partition by cast([registration no] as int)
order by [registration no]) as rn
from #T
)
delete from C
where rn > 1
create table temp id int;
insert into temp select id from your_table a where left (registration_no, ) = '0' and
exists select id from your_table
where a.registration_no = concat ('0', registration_no)
delete from your_table where id in (select id from temp);
drop table temp;
I think you can do this with a single DELETE statement. The JOIN ensures that only duplicates can get deleted, and the constraint limits it further by the registration numbers that don't start with a '0'.
DELETE
r1
FROM
Registration r1
JOIN
Registration r2 ON RIGHT(r1.RegistrationNumber, 8) = r2.RegistrationNumber
WHERE
LEFT(r1.RegistrationNumber, 1) <> '0'
Your table looks like this after running the above DELETE. I tested it on a SQL Server 2008 instance.
ID RegistrationNumber
----------- ------------------
2 483877668
3 019000702
4 837478848
5 464657588
7 042400065
This solution won't depend on the registration numbers being a particular length, it just looks for the ones that are the same integer, yet not the same value (because of the leading zeroes) and selects for the entry that has a '0' as the first character.
DELETE r
FROM Registration AS r
JOIN Registration AS r1 ON r.RegistrationNo = CAST(r1.RegistrationNo AS INT)
AND r.RegistrationNo <> r1.RegistrationNo
WHERE CHARINDEX('0',r.registrationno) = 1