Update based on sequence/rank in SQL Server 2008? - sql-server-2008

Table looks like this:
create table #rankme (rankmeid int identity(1000, 1) primary key,
step int null, checkvalue int null)
insert into #rankme values ( 10 , 1 )
insert into #rankme values ( 15 , null )
insert into #rankme values ( 20 , null )
insert into #rankme values ( 40 , null )
select * from #rankme order by step,checkvalue
Taking step as a parameter, I am trying to find out if the requested checkvalue for the one before the step I asked for is null.
So I want to select where step=20 and get NULL.
And I want to select where step=15 and get a 1.
I was trying to come up with something based on "rank-1" but so far no cigar.
Help?

declare #step int = 15
select top(1) R.checkvalue
from #rankme as R
where R.step < #step
order by R.step desc

Related

How to Duplicate Column in same table in SQL?

i am trying to duplicate a column hotelid , and then i want to make a couples that begin with the same letter,
select cast (hotelid as int) as hotelid1 , cast (hotelid as int) as hotelid2
from hotel
where (postcode BETWEEN 1500 ANd 1999) or
( postcode BETWEEN 3000 and 3499)
and (hotelid2 > hotelid1 )
and ( LEFT(hotelid1, 1) like LEFT(hotelid2, 1))
how i can use the new lines in where conditions ?
MySQL uses HAVING for that
SELECT cast(hotelid as UNSIGNED ) as hotelid1 , cast(hotelid as UNSIGNED ) as hotelid2
from hotel
where (postcode BETWEEN 1500 ANd 1999) or
( postcode BETWEEN 3000 and 3499)
HAVING (hotelid2 > hotelid1 )
and ( LEFT(hotelid1, 1) like LEFT(hotelid2, 1))

Return Value from sql commands

I am currently using the SQL command
Select * from where name='john'
Is it possible to return 20 no matter the query, for example
Select * from where name='john' or return = 20
EDIT
If you have an oracle database you can do something like that:
SELECT *
FROM dual
WHERE 1=0
UNION
SELECT '20'
FROM dual;
check my answer
if exists (Select * from item where ItemName='ABC Daycare1')
begin
Select * from item where ItemName='ABC Daycare1'
end
else
select '20'
Try running this. This should return the top result (which is never 20 due to the custom sort) and then when the name doesn't match a value it returns 'Mark' and 20
SQL
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
CREATE TABLE #temp (id int NOT NULL, name varchar(255) NOT NULL)
INSERT INTO #temp (id, name) VALUES (88,'John')
INSERT INTO #temp (id, name) VALUES (20,'Mark')
SELECT TOP 1
*
FROM #temp
WHERE (name = 'Mark' OR name = 'John')
ORDER BY (
CASE
WHEN id = 20 THEN 0 ELSE 1
END) DESC
MySQL - MySQL fiddle
IF OBJECT_ID('tempdb..#temp') IS NOT NULL DROP TABLE #temp
CREATE TABLE #temp (id int NOT NULL, name varchar(255) NOT NULL)
INSERT INTO #temp (id, name) VALUES (88,'John')
INSERT INTO #temp (id, name) VALUES (20,'Mark')
SELECT
*
FROM temp
WHERE (name = 'Mark' OR name = 'John')
ORDER BY (
CASE
WHEN id = 20 THEN 0 ELSE 1
END) DESC
LIMIT 1

MySQL Select unique sorted field values

I've trying to concatenate the values of 2 GROUP_CONCAT( columns ) from a single table that's been joined twice, then get the unique items from the list.
I can do all this outside of my query but if possible it would be nice to just pull the data from the DB with a JOIN and some fancy string manipulation.
Simply put, I want to produce 1,2,3,4 from selecting 1,2,3 and 1,3,4. The 1,2,3 adn 1,3,4 are the results of the GROUP_CONCAT on the twice joined table. I can get this far:
SELECT CONCAT_WS(
",",
"1,2,3",
"1,3,4"
)
Which outputs 1,2,3,1,3,4
I'd like to be able to do something like:
-- NOTE TO SKIM READERS: THIS QUERY WILL NOT WORK
SELECT
SORT_LIST(
DISTINCT
CONCAT_WS(
",",
"1,2,3",
"1,3,4"
)
)
-- NOTE TO SKIM READERS: THIS QUERY WILL NOT WORK
But I can't find anything like that in MySQL.
The 1,2,3 and 1,3,4 have already been produced with GROUP_CONCAT( DISTINCTcol)
As stated in my comment I worked out a way to achieve distinct concatenated lists of strings using a sub query:
DROP TABLE IF EXISTS `test1234`;
CREATE TABLE `test1234` (
`val` int(1),
`type` varchar(1)
);
INSERT INTO `test1234` VALUES
( 1, 'a' ),
( 2, 'a' ),
( 3, 'a' ),
( 1, 'b' ),
( 3, 'b' ),
( 4, 'b' );
SELECT GROUP_CONCAT( `val` ) AS `vals`
FROM (
(
SELECT `val` FROM `test1234` WHERE `type` = 'a'
) UNION DISTINCT (
SELECT `val` FROM `test1234` WHERE `type` = 'b'
)
) AS `test`;
DROP TABLE IF EXISTS `test1234`;
This selected 1,2,3,4

more efficient group by for query with Case

I have the following query building a recordset which is used in a pie-chart as a report.
It's not run particularly often, but when it does it takes several seconds, and I'm wondering if there's any way to make it more efficient.
SELECT
CASE
WHEN (lastStatus IS NULL) THEN 'Unused'
WHEN (attempts > 3 AND callbackAfter IS NULL) THEN 'Max Attempts Reached'
WHEN (callbackAfter IS NOT NULL AND callbackAfter > DATE_ADD(NOW(), INTERVAL 7 DAY)) THEN 'Call Back After 7 Days'
WHEN (callbackAfter IS NOT NULL AND callbackAfter <= DATE_ADD(NOW(), INTERVAL 7 DAY)) THEN 'Call Back Within 7 Days'
WHEN (archived = 0) THEN 'Call Back Within 7 Days'
ELSE 'Spoke To'
END AS statusSummary,
COUNT(leadId) AS total
FROM
CO_Lead
WHERE
groupId = 123
AND
deleted = 0
GROUP BY
statusSummary
ORDER BY
total DESC;
I have an index for (groupId, deleted), but I'm not sure it would help to add any of the other fields into the index (if it would, how do I decide which should go first? callbackAfter because it's used the most?)
The table has about 500,000 rows (but will have 10 times that a year from now.)
The only other thing I could think of was to split it out into 6 queries (with the WHEN clause moved into the WHERE), but that makes it take 3 times as long.
EDIT:
Here's the table definition
CREATE TABLE CO_Lead (
objectId int UNSIGNED NOT NULL AUTO_INCREMENT,
groupId int UNSIGNED NOT NULL,
numberToCall varchar(20) NOT NULL,
firstName varchar(100) NOT NULL,
lastName varchar(100) NOT NULL,
attempts tinyint NOT NULL default 0,
callbackAfter datetime NULL,
lastStatus varchar(30) NULL,
createdDate datetime NOT NULL,
archived bool NOT NULL default 0,
deleted bool NOT NULL default 0,
PRIMARY KEY (
objectId
)
) ENGINE = InnoDB;
ALTER TABLE CO_Lead ADD CONSTRAINT UQIX_CO_Lead UNIQUE INDEX (
objectId
);
ALTER TABLE CO_Lead ADD INDEX (
groupId,
archived,
deleted,
callbackAfter,
attempts
);
ALTER TABLE CO_Lead ADD INDEX (
groupId,
deleted,
createdDate,
lastStatus
);
ALTER TABLE CO_Lead ADD INDEX (
firstName
);
ALTER TABLE CO_Lead ADD INDEX (
lastName
);
ALTER TABLE CO_Lead ADD INDEX (
lastStatus
);
ALTER TABLE CO_Lead ADD INDEX (
createdDate
);
Notes:
If leadId cannot be NULL, then change the COUNT(leadId) to COUNT(*). They are logically equivalent but most versions of MySQL optimizer are not so clever to identify that.
Remove the two redundant callbackAfter IS NOT NULL conditions. If callbackAfter satisfies the second part, it cannot be null anyway.
You could benefit from splitting the query into 6 parts and add appropriate indexes for each one - but depending on whether the conditions at the CASE are overlapping or not, you may have wrong or correct results.
A possible rewrite (mind the different format and check if this returns the same results, it may not!)
SELECT
cnt1 AS "Unused"
, cnt2 AS "Max Attempts Reached"
, cnt3 AS "Call Back After 7 Days"
, cnt4 AS "Call Back Within 7 Days"
, cnt5 AS "Call Back Within 7 Days"
, cnt6 - (cnt1+cnt2+cnt3+cnt4+cnt5) AS "Spoke To"
FROM
( SELECT
( SELECT COUNT(*) FROM CO_Lead
WHERE groupId = 123 AND deleted = 0
AND lastStatus IS NULL
) AS cnt1
, ( SELECT COUNT(*) FROM CO_Lead
WHERE groupId = 123 AND deleted = 0
AND attempts > 3 AND callbackAfter IS NULL
) AS cnt2
, ( SELECT COUNT(*) FROM CO_Lead
WHERE groupId = 123 AND deleted = 0
AND callbackAfter > DATE_ADD(NOW(), INTERVAL 7 DAY)
) AS cnt3
, ( SELECT COUNT(*) FROM CO_Lead
WHERE groupId = 123 AND deleted = 0
AND callbackAfter <= DATE_ADD(NOW(), INTERVAL 7 DAY)
) AS cnt4
, ( SELECT COUNT(*) FROM CO_Lead
WHERE groupId = 123 AND deleted = 0
AND archived = 0
) AS cnt5
, ( SELECT COUNT(*) FROM CO_Lead
WHERE groupId = 123 AND deleted = 0
) AS cnt6
) AS tmp ;
If it does return correct results, you could add indexes to be used for each one of the subqueries:
For subquery 1: (groupId, deleted, lastStatus)
For subquery 2, 3, 4: (groupId, deleted, callbackAfter, attempts)
For subquery 5: (groupId, deleted, archived)
Another approach would be to keep the query you have (minding only notes 1 and 2 above) and add a wide covering index:
(groupId, deleted, lastStatus, callbackAfter, attempts, archived)
Try removing the index to see if this improves the performance.
Indexes do not necessarily improve performance, in some databases. If you have an index, MySQL will always use it. In this case, that means that it will read the index, then it will have to read data from each page. The page reads are random, rather than sequential. This random reading can reduce performance, on a query that has to read all the pages anyway.

How to get the smallest Integers not yet in a database column

I have a table in a MySQL DB with an UNIQUE INT(10) column. The table is pretty populated and the row contains non-consecutive entries of Integer numbers in that column. I would like to do a query, which gets me the smallest number (or the n smallest numbers) that is not in any row.
Example: The table contains rows with values (1, 2, 3, 5, 7, 8, 10, 12, 15) for the column. The sql statement should return i.e. the five lowest non-contained values, which are 4, 6, 9, 11, 13 in this case.
Is this possible with MySQL?
You can use a "numbers" table (it's handy for various operations):
CREATE TABLE num
( i UNSIGNED INT NOT NULL
, PRIMARY KEY (i)
) ;
INSERT INTO num (i)
VALUES
(1), (2), ..., (1000000) ;
Then:
SELECT
num.i
FROM
num
LEFT JOIN
tableX AS t
ON num.i = t.columnX
WHERE
t.columnX IS NULL
ORDER BY
num.i
LIMIT 5
or:
SELECT
num.i
FROM
num
WHERE
NOT EXISTS
( SELECT *
FROM tableX AS t
WHERE num.i = t.columnX
)
ORDER BY
num.i
LIMIT 5
Another approach, without using an auxilary table, would be to use MySQL variables. You can test it in SQL-Fiddle, test-2. The output is not the same as the previous (just to show that it can be done):
SELECT start_id, end_id
FROM
( SELECT
IF( t.columnX <> #id, #id, NULL) AS start_id
, IF( t.columnX <> #id, t.columnX-1, NULL) AS end_id
, #rows := #rows + (t.columnX - #id) AS r
, #id := t.columnX + 1 AS running_id
FROM
tableX AS t
CROSS JOIN
( SELECT #rows := 0
, #id := 1
) AS dummy
WHERE
#rows < 5
ORDER BY
t.columnX
) AS tmp
WHERE
start_id IS NOT NULL
This will work, but I think it is pretty inefficient. You won't need an extra table though (a table that would be (2^31-1)*4/1024^3 = 8GB for all positive numbers in INT). Also I advise you look at why you need this, because it might not be neccesary.
Also it will return the start and end of a range, but not all numbers in that range. (e.g. if you have numbers 1 and 5 it will return {0,2,4,6})
SELECT (t.num-1) AS bound FROM t
WHERE t.num-1 NOT IN (SELECT t.num FROM t)
UNION
SELECT (t.num+1) AS bound FROM t
WHERE t.num+1 NOT IN (SELECT t.num FROM t)
As I said this will be pretty inefficient, JOINs might be faster but you would need benchmark it.
SELECT (t.num-1) AS bound FROM t
LEFT JOIN t AS u ON t.num-1 = u.num
WHERE u.num IS NULL
UNION
SELECT (t.num+1) AS bound FROM t
LEFT JOIN t AS u ON t.num+1 = u.num
WHERE u.num IS NULL