Sybase - how do I return the first value that exists from a condition in SQL? - sybase-ase

Say I'm trying to return some results where a column in a table matches a condition I set. But I only want to return the first result from a list of possible values in the condition. Is there a quick and easy way to do that? I'm thinking that I can use coalesce somehow, but not sure how I can structure it.
Something like:
select identifier,purpose from table
where identifier = 'letters'
and purpose = coalesce('A','B','C')
group by purpose
So in the table, if A purpose isn't there, then I only want the B purpose to show up. if it isn't there, then I want the C to show up, if none of them are there, then I would ideally like a null or no results to be returned. I'd rather not make several case statements where if A is null then look to B, then if B is null to look to C. Is there a quick way syntactically to do so?
Edit: I also want this to work if I have multiple identifiers I list, such as:
select identifier,purpose from table
where identifier in ('letters1', 'letters2')
and purpose = coalesce('A','B','C')
group by purpose
where I return two results if they exist - one purpose for each identifier, with the purpose in the order of importance for A first, then B, then C, or null if none exist.
Unforunately my reasoning for caolesce doesn't work above, as none of the variables are null so my query will just try to return all purposes of 'A' without the fallback that I intend my query to do. I want to try and avoid using temp tables if possible.

Sybase ASE does not have support for the row_number() function (else this would be fairly simple), so one option would be to use a #temp table to simulate (to some extent) row_number() functionality.
Some sample data:
create table mytab
(identifier varchar(30)
,purpose varchar(30)
)
go
insert mytab values ('letters1','A')
insert mytab values ('letters1','B')
insert mytab values ('letters1','C')
insert mytab values ('letters2','A')
insert mytab values ('letters2','B')
insert mytab values ('letters2','C')
go
The #temp table is created with an identity column plus a 2nd column to hold the items you wish to prioritize; priority is determined by the order in which the rows are inserted into the #temp table.
create table #priority
(id smallint identity
,purpose varchar(30))
go
insert #priority (purpose)
select 'A' -- first priority
union all
select 'B' -- second priority
union all
select 'C' -- last priority
go
select * from #priority order by id
go
id purpose
------ -------
1 A
2 B
3 C
We'll use a derived table to find the highest priority purpose (ie, minimal id value). We then join this minimal id back to #priority to generate the final result set:
select dt.identifier,
p.purpose
from (-- join mytab with #priority, keeping only the minimal priority id of the rows that exist:
select m.identifier,
min(p.id) as min_id
from mytab m
join #priority p
on p.purpose = m.purpose
group by m.identifier) dt
-- join back to #priority to convert min(id) into the actual purpose:
join #priority p
on p.id = dt.min_id
order by 1
go
Some test runs with different set of mytab data:
/* contents of mytab:
insert mytab values ('letters1','A')
insert mytab values ('letters1','B')
insert mytab values ('letters1','C')
insert mytab values ('letters2','A')
insert mytab values ('letters2','B')
insert mytab values ('letters2','C')
*/
identifier purpose
---------- -------
letters1 A
letters2 A
/* contents of mytab:
--insert mytab values ('letters1','A')
--insert mytab values ('letters1','B')
insert mytab values ('letters1','C')
--insert mytab values ('letters2','A')
insert mytab values ('letters2','B')
insert mytab values ('letters2','C')
*/
identifier purpose
---------- -------
letters1 C
letters2 B
Returning NULL if a row does not exist is not going to be easy since generating a NULL requires existence of a row ... somewhere ... with which to associate the NULL.
One idea would be to expand on the #temp table idea by creating another #temp table (eg, #identifiers) with the list of desired identifier values you wish to search on. You could then make use of a left (outer) join from #identifiers to mytab to ensure you always generate a result record for each identifier.

Related

Summing columns x through x+n in an SQL table

I'm trying to sum columns x through x+n in an SQL table. Essentially, I have multiple tables that contain grades in them and a user_id. I want to sum all the grades to come up with a total grade column without specifying the column names as the names and number of columns changes with each table. For instance, one table might have columns (user_id, calculations, prelab, deductions) while another might have (user_id, accuracy, precision, graphs, prelab, deductions).
I could rename my columns col1, col2, col3, col4, col5, etc., but I can't figure out how to get around the varying number of columns.
As far as I know, there is no way to sum groups of columns without actually specifying the column names directly in SQL. It seems to me like this is a badly designed schema, but that's a separate topic.
In any your case, you're going to need to create a new column in each table that contains the sum of all the grades in that particular table, say called total, and then, do something like this:
select user_id, sum(table1.total, table2.total, table3.total)
from table1, table2, table3
where table1.user_id = table2.user_id
and table2.user_id = table3.user_id
group by user_id
1) You could write some pl/sql to go and hit the data dictionary and get the columns and then construct dynamic sql to do the work of adding them up correctly.
2) Or you could create views on top of the tables that contain the user_id and the sum of the interesting columns (the views themselves could be constructed programmatically - but that only needs to happen once rather than every time you want the totals).
But either of the above is probably over-kill compared to simply fixing your schema.
The following procedure would likely do the trick.
It will look for all column names for the given tableName in the INFORMATION_SCHEMA.COLUMNS table (excluding 'userid' - This may be subject to change if the name you use is different).
The procedure also creates a temporary table (this is also subject to improvement - it would probably be better to do a 'drop if exists before the create) to store the sum up to a point.
The items inside the loop is just building an SQL UPDATE statement with the given tableName argument and the columnName from the cursor and doing the math.
To test this (after creation):
call myProcedure('tableName');
DELIMITER //
DROP PROCEDURE IF EXISTS myProcedure //
CREATE PROCEDURE
myProcedure( tableName varchar(32) )
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE columnName varchar(64);
DECLARE cur1 CURSOR FOR SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = tableName and COLUMN_NAME <> 'userid';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
CREATE TEMPORARY TABLE intermediateresults(userid integer, sumOfScores integer);
SET #st1 = CONCAT('INSERT INTO intermediateresults (SELECT DISTINCT userid, 0 FROM ',tableName,' )' );
PREPARE stmt3 FROM #st1;
EXECUTE stmt3;
looping: LOOP
FETCH cur1 into columnName;
IF done THEN
LEAVE looping;
END IF;
SET #st1 = CONCAT('UPDATE intermediateresults set sumOfScores = sumOfScores + COALESCE( (SELECT ', columnName, ' FROM ',tableName, ' t WHERE t.userid=intermediateresults.userid) , 0)' );
PREPARE stmt3 FROM #st1;
EXECUTE stmt3;
DEALLOCATE PREPARE stmt3;
END LOOP;
CLOSE cur1;
SELECT * FROM intermediateresults;
DROP table intermediateresults;
END
//
DELIMITER ;
What might be of interest when doing this kind of thing:
INFORMATION_SCHEMA also has data on:
DATA_TYPE: which can be used to test if a specific column has the actual type you are expecting - a condition such as DATA_TYPE='int' can be added to the cursor definition to make sure that it is in fact an int (assuming that the columns to be summed are in fact INTs)
ORDINAL_POSITION: which can be used if you know in which order the columns are supposed to arrive (for cases where the last four are housekeeping, for instance)
TABLE_SCHEMA: the procedure above rather assumes that the table is only present in the current default schema. Using this would require an additional parameter in the procedure and a slight change in the constructed SQL statements.

insert into foreign key proper sql execution

I found that my error caused by my PK doesn't have a value so my insertion on FK failed.
like table A uId is PK of table B's uId (FK), so if I want to insert 1 in table B's uId, the table A's uId must have the value of 1.
But is that 2 query execution? how it should be written? I tried 2 sql execution the second - where insert into FK, has failed.
I've put the 3 methods discussed in the comments into a SqlFiddle here
-- Null Foreign Key, if the relationship is optional
INSERT INTO B(AID) VALUES(NULL);
-- Lookup - if you know of another `natural` column in table A,
-- e.g. here we know the Name we need to link to
INSERT INTO B(AID) VALUES((SELECT AID FROM A WHERE AName = 'Foo'))
-- Identity Insert - when A and B are inserted at the same time,
-- and you need to pass A ID to B
INSERT INTO A(AName) VALUES('Baz');
INSERT INTO B(AID) VALUES((SELECT SCOPE_IDENTITY()));
SELECT * FROM A;
SELECT * FROM B;
For MySql it is similar, just use LAST_INSERT_ID() vs SCOPE_IDENTITY()

Insert into free id MYSQL

I need to be able to safely insert a row with first available ID. I read alot answers about PRIMARY_KEY and AUTO_INCREMENT and all this stuff, but this is something else. I need to keep and be able to work on database with fixed ID range from 1 to 60000. Is there any way to do that with MySQL? Writing own function that check what is nearest "free" ID, is not an option cause there may be conflicts on multiuser usage.
In the best case scenario,MySQL would somehow work as with PRIMARY_KEY, but reusing keys.
Follow these steps:
1) Create a sequence table with columns id, rowstate.
2) Insert ids 1-60000 to that with the rowstate 1
3) Whenever you want to insert in your main table, search for the lowest id from the sequence table having rowstate=1 and update the sequence to -1.
When you want to delete a record from the main table, set the rowstate of the id to 1.
you are write, you need to concider the the concurrency issues
hence you need to implement a table lock mechnism
1) Lock mysql table
2) Insert the record, you can just use the auto_increment property since no two values would be added at the same time (i dont think you have to lock tables if this is used anyway)
3) If you dont want to use auto_increment, any of the above suggested code will work
You can try like this:
INSERT INTO tableName (a,b,c) VALUES (1,2,3)
ON DUPLICATE KEY UPDATE id=(Select max(id)+1 from tableName);
For more info: http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html
OR
Getting highest id from table and increament it and insert your row.
Select max(id) from tableName;
You will get the id. Than add 1 it and insert into table. Additionally you can check it should be less than 60000.
I know this is not the best answer, but can be consider as second alternative.
To get the first id that is free you can do
select id - (id-ra) as lowest
from
(
select id, #r := #r + 1 as ra
from t, (select #r := 0) rank
order by id
)
x
where ra <> id
limit 1
SQLFiddle demo
You can put that in a procedure where you lock the table during the operation
delimiter |
CREATE PROCEDURE create_save ()
BEGIN
LOCK TABLES your_table WRITE;
set #lowid := 0;
select #lowid := id - (id-ra)
from
(
select id, #r := #r + 1 as ra
from your_table, (select #r := 0) rank
order by id
)
x
where ra <> id
limit 1;
if #lowid between 1 and 59999
then
insert into your_table (id, othercolumn)
select #lowid, 12345;
end if;
UNLOCK TABLES;
end
|
delimiter ;

MySQL Insert: Avoid duplicates w/ multiple cols

I basically want to insert two Values ('Val', 1) into the columns (cls, jg) of the table t1.
Although cls is set to be unique, jg has to appear multiple times with the same value, so INSERT IGNORE wouldn't work. Thorefore, I have to check if the pair ('Val', 1) already exists, and if not so, I have to insert these values.
I tried it this way:
INSERT INTO t1 (`cls`,`jg`)
SELECT 'Val',1 FROM cls WHERE NOT EXISTS(
SELECT 1 FROM cls WHERE `cls`='Val' AND `jg`=1)
LIMIT 1;
But it doesn't work when the table is empty, because then the outer select statement doesn't contain any entries…
How should I proceed? I have no idea how to handle this a different way…
Use an unique composite index on cls, jg.
ALTER IGNORE TABLE t1 ADD UNIQUE INDEX(cls(200), jg)
Then use
INSERT.. ON DUPLICATE KEY UPDATE
You are mistaken. If you have a unique constraint (like a primary key) on cls that doesn't in any way interfere with the values of jg.
sql fiddle

How can I move existing data to new row?

I have a table with several thousand rows. I'd like to be able to take one of those rows and duplicate it into a new row. What is the best way to do this?
Should I grab the rows data and assign each field into a variable? And then take those variables and insert them into a new row?
So that'd be two queries -- is there a more efficient way of doing this?
INSERT INTO tablename SELECT * FROM tablename WHERE <your where condition>
If a primary key is in the way
INSERT INTO tablename (every, field, except, primary) SELECT every, field, except, primary FROM tablename WHERE <your where condition>
OR
INSERT INTO tablename SELECT NULL, every, field, except, primary FROM tablename WHERE <your where condition>
(assuming the id is the first column)
Explicitly naming the columns you're going the use (the 2nd query in this answer) should be preferred for production code.
Check out mysql's INSERT ... SELECT syntax.
You should be able to do something like:
INSERT INTO tbl (a, b, c) SELECT a, b, c FROM tbl WHERE id = 1234;

Resources