I have a table with 2 columns
NAME SYNONYM
----------------
A ALPHA
B ALPHA
B BITA
C GAMA
D DELTA
E BITA
I am looking for a SQL query that will check column SYNONYM. If it finds the same SYNONYM, for example it finds ALPHA in second row which is the same with row 1 it will change B and make it A. The change of B will take place everywhere in column NAME, not only in one row.
NAME SYNONYM
----------------
A ALPHA
A ALPHA
A BITA
C GAMA
D DELTA
A BITA
If it is difficult to alter the column NAME we could add a new column like this
NAME SYNONYM NEW
----------------------------
A ALPHA 1
B ALPHA 1
B BITA 1
C GAMA 2
D DELTA 3
E BITA 1
NOTE: This type of query is not possible in straight MySQL if its supposed to be a dynamic query... MySQL does not support recursive queries.. however if you know the column you would like to update then you can do this query in a roundabout way.
So if there are some columns you would like to update you can do it like this and it'll do exactly what you're asking for..
EDIT:
if you use user defined variables you could walk through the table like so:
SET #A := (SELECT DISTINCT name FROM example_table ORDER BY name LIMIT 0,1);
UPDATE example_table et,
(
SELECT
DISTINCT synonym
FROM example_table
WHERE name IN
(
SELECT
DISTINCT name
FROM example_table
WHERE synonym IN
(
SELECT
DISTINCT synonym
FROM example_table
WHERE name = #A
)
)
) t
SET et.name = #A WHERE et.synonym = t.synonym;
all you have to do is increment the 0 in the SET #A statement.. LIMIT 0,1... LIMIT 1,1.... that will give you a new distinct name each time that you can execute the update on. Hope that helps
Related
I have a (MYSQL) table in the following format; assume the name of the table is mytable:
id
name
group
123
name1
1
124
name2
2
125
name3
1
126
name4
id is unique and auto-increments. name is a unique string, group is just an integer
I now want to assign name4 to a new group that does not exist yet, so the group for name4cannot be 1 or 2 in this example.
The result could,for example, be:
id
name
group
126
name4
3
At the moment I am sorting by group descending and just insert the highest number + 1 manually, but I was wondering if there was a better/quicker way to generate a new, unique value in a column. group has no other constraints, besides being an integer.
I am using the MySQL Workbench, so I can work with both SQL commands, as well as Workbench-specific options, if there are any.
If anything is unclear I'll gladly provide clarification.
In MySQL 8.0, you can get help with two window functions:
MAX, to retrieve the maximum "group" value
ROW_NUMBER, to retrieve the incremental value for each NULL existing in your table.
You can then sum up these two values and update your table where your "group" field is null.
WITH cte AS (
SELECT id, name, MAX(group_) OVER() + ROW_NUMBER() OVER(PARTITION BY group_ IS NULL ORDER BY name) AS new_group
FROM tab
)
UPDATE tab
INNER JOIN cte
ON tab.id = cte.id AND tab.name = cte.name
SET tab.group_ = cte.new_group
WHERE tab.group_ IS NULL;
Check the demo here.
In MySQL 5.X you can instead use a variable, initialized with your maximum "group" value, then updated incrementally inside the UPDATE statement, in the SET clause.
SET #maxgroup = NULL;
SELECT MAX(group_) INTO #maxgroup FROM tab;
UPDATE tab
SET group_ = (#maxgroup:= #maxgroup + 1)
WHERE group_ IS NULL;
ORDER BY id;
Check the demo here.
I have an sql which select value from a table calculated by another sql:
select t.netlist_id from
(select c from cl2 where pid = 1 order by id limit 1) as t
and I get error message below:
#1054 Unknown column "t.netlist_id" in "field list"
if I change the original sql like this:
select t.* from
(select c from cl2 where pid = 1 order by id limit 1) as t
I get the result of select c from cl2 where pid = 1 order by id limit 1
Why would this happen and how can I correct my sql?
content of table cl2
|pic|c |
------------
|1 |dcdc |
content of table dcdc
|netlist_id|
------------
|1 |
If you use SQL Server I could suggest this answer.
But I wrote this answer, and hope make some answers in mysql.
In SQL Server there is a stored procedure named
Declare #text varchar(100)
Select #text = 'select * from' + (select top 1 c from cl2 where pid = 1 order by id)
Exec sp_sqlexec #text
This code is very useful as a stored procedure -In SQL Server-.
You cannot fetch a table name from a subquery to the FROM clause of another query. I suggest that you merge all the tables that can appear in the column c of the table cl2 and put them into one table. Put the former table name in a column like this:
table merged_tbl:
|netlist_id|type|
-----------------
|1 |dcdc|
After this you can simply do:
select t.netlist_id from merged_tbl AS t WHERE type IN
(select c from cl2 where pid = 1 order by id limit 1)
There might be another redesign moves you would have to do to your database but those cannot be known just be looking at the example data. Take a look at this question with a similar problem and similar suggestions.
Before anything, I am not looking for a re-write. This was presented to me, and I can't seem to figure out if this is a bug in general or some kind of syntactic craziness that occurs due to the peculiarity of the script. Okay with that said on with the setup:
Microsoft SQL Server Standard Edition (64-bit)
Version 10.50.2500.0
On a table located in a generic database, defined as:
CREATE TABLE [dbo].[Regions](
[RegionID] [int] NOT NULL,
[RegionGroupID] [int] NOT NULL,
[IsDefault] [bit] NOT NULL,
CONSTRAINT [PK_Regions] PRIMARY KEY CLUSTERED
(
[RegionID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
insert some values:
INSERT INTO [dbo].[Regions]
([RegionID],[RegionGroupID],[IsDefault])
VALUES
(0,1,0),
(1,1,0),
(2,1,0),
(3,2,0),
(4,2,0),
(5,2,0),
(6,3,0),
(7,3,0),
(8,3,0)
Now run the query (to select a single from each group, remember no rewrite suggestions!):
SELECT RXXID FROM (
SELECT
RXX.RegionID as RXXID,
ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
FROM Regions as RXX
) AS tmp
WHERE tmp.RXXNUM = 1
You should get:
RXXID
-----------
0
3
6
Now stick that inside an update statement (with a preset to 0 and a select all after):
UPDATE Regions SET IsDefault = 0
UPDATE Regions
SET IsDefault = 1
WHERE RegionID IN (
SELECT RXXID FROM (
SELECT
RXX.RegionID as RXXID,
ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
FROM Regions as RXX
) AS tmp
WHERE tmp.RXXNUM = 1
)
SELECT * FROM Regions
ORDER BY RegionGroupID
and get this result:
RegionID RegionGroupID IsDefault
----------- ------------- ---------
0 1 1
1 1 1
2 1 1
3 2 1
4 2 1
5 2 1
6 3 1
7 3 1
8 3 1
zomg wtf lamaz?
While I don't claim to be a SQL guru, this seems neither proper nor correct. And to make things more crazy, if you drop the primary key it seems to work:
Drop primary key:
IF EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Regions]') AND name = N'PK_Regions')
ALTER TABLE [dbo].[Regions] DROP CONSTRAINT [PK_Regions]
And re-run update statement set, result:
RegionID RegionGroupID IsDefault
----------- ------------- ---------
0 1 1
1 1 0
2 1 0
3 2 1
4 2 0
5 2 0
6 3 1
7 3 0
8 3 0
Isn't that a b?
Does anyone have any clue what is going on here? My guess is some kind of sub-query caching and is this a bug? It sure doesn't seem like what SQL should be doing?
Just update as a CTE directly:
WITH tmp AS (
SELECT
RegionID as RXXID,
RegionGroupID,
IsDefault,
ROW_NUMBER() OVER (PARTITION BY RegionGroupID ORDER BY RegionID) AS RXXNUM
FROM Regions
)
UPDATE tmp SET IsDefault = 1 WHERE RXXNUM = 1
select * from Regions
Added more columns to illustrate. You can see this on http://sqlfiddle.com/#!3/03913/9
Not 100% sure what is going on in your example, but since you partition and order by the same column, you're not really certain to get the same order back, since they are all tied. Shouldn't you order by RegionID or some other column, as i did on sqlfiddle?
Back to your question:
If you change your UPDATE (with the clustered index) to a SELECT, you'll get all 9 rows back.
If you drop the PK, and do the SELECT, you only get 3 rows. Back to your update statement. Inspecting the execution plans show that they differ slightly:
What you can see here is that in the first (with PK) query, you'll scan the clustered index for the outer reference, note that it does not have the alias RXX. Then for each row in the top, do a lookup to the RXX. And yes, because of your row number ordering, every RegionID can be row_number() 1 for each RegionGroupID. SQL Server would know this based on your PK, i guess, and can say that For every RegionID, this RegionID can be row number 1. Therefore the statement is rather valid.
In the second query, there is no index, and you get a table scan on Regions, then it builds a probe table using the RXX, and joins differently (single pass, ROW_NUMBER() can only be 1 for one row per regiongroupid now). This way in that scan, every RegionID has only one ROW_NUMBER(), though you cannot be 100% certain it'll be the same every time.
This means:
Using your subquery which doesn't have a deterministic order for every execution, you should avoid using a multiple pass (NESTED LOOP) join type, but a single pass (MERGE OR HASH) join.
To fix this without changing the structure of your query, add OPTION (HASH JOIN) or OPTION (MERGE JOIN) to the first UPDATE:
So, you'll need the following update statement (when you have the PK):
UPDATE Regions SET IsDefault = 0
UPDATE Regions
SET IsDefault = 1
WHERE RegionID IN (
SELECT RXXID FROM (
SELECT
RXX.RegionID as RXXID,
ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
FROM Regions as RXX
) AS tmp
WHERE tmp.RXXNUM = 1
)
OPTION (HASH JOIN)
SELECT * FROM Regions
ORDER BY RegionGroupID
Here are the execution plans using these two join types (note actual number of rows: 3 in the properties):
Your query in plain language is something like:
For each row in Regions check if RegionID exists in some sub query. Meaning that the sub query is executed for each row in Regions. (I know that is not the case but it is the semantics of the query).
Since you are using RegionGroupID as order and partition you really have no idea what RegionID will be returned so it might very well be a new ID for each time the sub-query is checked against.
Update:
Doing the update with a join against the derived table instead instead of using in changes the semantics of the query and it changed the result as well.
This works as expected:
UPDATE R
SET IsDefault = 1
FROM Regions as R
inner join
(
SELECT RXXID FROM (
SELECT
RXX.RegionID as RXXID,
ROW_NUMBER() OVER (PARTITION BY RXX.RegionGroupID ORDER BY RXX.RegionGroupID) AS RXXNUM
FROM Regions as RXX
) AS tmp
WHERE tmp.RXXNUM = 1
) as C
on R.RegionID = C.RXXID
I have this fairly straightforward table with ID, Position, Name columns.
ID Position Name
1 1 RecordX
2 3 RecordY
3 2 RecordZ
The Position column serves as an index for displaying the records in a user defined order, it should be unique, can not be lower than 1 and not be higher than the number of records in the table, in this case 3. The column doesn't enforce uniqueness so temporarily there can be 2 records with the same Position, but eventually no two records should have the same position for the correct working of the program.
Currently, in order to swap the position of two records I need to do 3 queries, namely:
find the other record's ID
update the current record's Position to match the other record's Position
update the other record's Position by it's previously found ID (Since momentarily there will be two records with the same Position, updating by Position is not possible.
I feel there should be a way to do this with less rounds to the database, and thus with less than 3 queries. How should I approach this problem?
Single "swap" operation...
SWAP(#old_pos, #new_pos)
UPDATE
my_table
SET
position = CASE WHEN position = #old_pos THEN #new_pos ELSE #old_pos END
WHERE
position IN (#old_pos, #new_pos)
This doesn't easily expand to a table of swap-operations though. This is because it will try to do all the swaps at once, when in fact the swaps must happen in a specific order...
Also, if you want to do SWAP(#id, #new_pos) you need to either do a sub-query or self join on the table you are updating. MySQL doesn't like that, and although there are ways around the limitation, it makes things get a bit messy...
UPDATE
my_table
INNER JOIN
(SELECT position AS old_pos, #new_pos AS new_pos FROM (SELECT position FROM my_table WHERE id = #id)) AS params
ON my_table.position IN (params.old_pos, params.new_pos)
SET
myTable.position = CASE WHEN position = old_pos THEN new_pos ELSE old_pos END
(I think that will work)
NOTE:
Both of these assume that BOTH #old_pos and #new_pos, or #id and #new_pos are found, it doesn't check, and will make a mess if they don't exist.
This can be resolved by putting it in a transaction, and rolling back if ROW_COUNT() shows that only 1 record is updated.
SET #new_pos_for_id_1:=3, #new_pos_for_id_3:=1;
UPDATE my_table
JOIN (
SELECT 1 as id, #new_pos_for_id_1 as new_position
UNION ALL
SELECT 3 as id, #new_pos_for_id_3 as new_position) as positions
USING (id)
SET position = new_position
This query can be used to change positions for several rows at a time. I like the #Dems' solution as well.
UPD:
Explanation
SELECT 1 as id, 3 as new_position
UNION ALL
SELECT 3 as id, 1 as new_position
is a on-fly constructed table of two columns: id, new_position where each id is mapped to some new intended position. THen I just JOIN the table with my_table on the common id field and substitute values in my_table with values from the constructed table.
This will probably work for any DBMS.
-- create some data
DROP TABLE ztable CASCADE;
CREATE TABLE ztable
( id integer NOT NULL PRIMARY KEY
, val INTEGER
);
INSERT INTO ztable(id,val) VALUES (1,1), (2,3), (3,2);
SELECT * FROM ztable;
UPDATE ztable t1
SET val=t2.val
FROM ztable t2
WHERE t1.id IN (2,3)
AND t2.id IN (2,3)
AND t1.id <> t2.id
;
SELECT * FROM ztable;
Results:
CREATE TABLE
INSERT 0 3
id | val
----+-----
1 | 1
2 | 3
3 | 2
(3 rows)
UPDATE 2
id | val
----+-----
1 | 1
2 | 2
3 | 3
(3 rows)
I'd like to use a single SQL query (in MySQL) to find the record which comes after one that I specify.
I.e., if the table has:
id, fruit
-- -----
1 apples
2 pears
3 oranges
I'd like to be able to do a query like:
SELECT * FROM table where previous_record has id=1 order by id;
(clearly that's not real SQL syntax, I'm just using pseudo-SQL to illustrate what I'm trying to achieve)
which would return:
2, pears
My current solution is just to fetch all the records, and look through them in PHP, but that's slower than I'd like. Is there a quicker way to do it?
I'd be happy with something that returned two rows -- i.e. the one with the specified value and the following row.
EDIT: Sorry, my question was badly worded. Unfortunately, my definition of "next" is not based on ID, but on alphabetical order of fruit name. Hence, my example above is wrong, and should return oranges, as it comes alphabetically next after apples. Is there a way to do the comparison on strings instead of ids?
After the question's edit and the simplification below, we can change it to
SELECT id FROM table WHERE fruit > 'apples' ORDER BY fruit LIMIT 1
SELECT * FROM table WHERE id > 1 ORDER BY id LIMIT 1
Even simpler
UPDATE:
SELECT * FROM table WHERE fruit > 'apples' ORDER BY fruit LIMIT 1
So simple, and no gymnastics required
Select * from Table
where id =
(Select Max(id) from Table
where id < #Id)
or, based on the string #fruitName = 'apples', or 'oranges' etc...
Select * from Table
where id =
(Select Max(id) from Table
where id < (Select id from Table
Where fruit = #fruitName))
I'm not familiar with the MySQL syntax, but with SQL Server you can do something with "top", for example:
SELECT TOP 1 * FROM table WHERE id > 1 ORDER BY id;
This assumes that the id field is unique. If it is not unique (say, a foreign key), you can do something similar and then join back against the same table.
Since I don't use MySQL, I am not sure of the syntax, but would imagine it to be similar.
Unless you specify a sort order, I don't believe the concepts of "previous" or "next" are available to you in SQL. You aren't guaranteed a particular order by the RDBMS by default. If you can sort by some column into ascending or descending order that's another matter.
This should work. The string 'apples' will need to be a parameter.
Fill in that parameter with a string, and this query will return the entire record for the first fruit after that item, in alphabetical order.
Unlike the LIMIT 1 approach, this should be platform-independent.
--STEP THREE: Get the full record w/the ID we found in step 2
select *
from
fruits fr
,(
--STEP TWO: Get the ID # of the name we found in step 1
select
min(vendor_id) min_id
from
fruits fr1
,(
--STEP ONE: Get the next name after "apples"
select min(name) next_name
from fruits frx
where frx.name > 'apples'
) minval
where fr1.name = minval.next_name
) x
where fr.vendor_id = x.min_id;
The equivalent to the LIMIT 1 approach in Oracle (just for reference) would be this:
select *
from
(
select *
from fruits frx
where frx.name > 'apples'
order by name
)
where rownum = 1
I don't know MySQL SQL but I still try
select n.id
from fruit n
, fruit p
where n.id = p.id + 1;
edit:
select n.id, n.fruitname
from fruits n
, fruits p
where n.id = p.id + 1;
edit two:
Jason Lepack has said that that doesn't work when there are gaps and that is true and I should read the question better.
I should have used analytics to sort the results on fruitname
select id
, fruitname
, lead(id) over (order by fruitname) id_next
, lead(fruitname) over (order by fruitname) fruitname_next
from fruits;
If you are using MS SQL Server 2008 (not sure if available for previous versions)...
In the event that you are trying to find the next record and you do not have a unique ID to reference in an applicable manner, try using ROW_NUMBER(). See this link
Depending on how savvy your T-SQL skill is, you can create row numbers based on your sorting order. Then you can find more than just the previous and next record. Utilize it in views or sub-queries to find another record relative to the current record's row number.
SELECT cur.id as id, nxt.id as nextId, prev.id as prevId FROM video as cur
LEFT JOIN video as nxt ON nxt.id > cur.id
LEFT JOIN video as prev ON prev.id < cur.id
WHERE cur.id = 12
ORDER BY prev.id DESC, nxt.id ASC
LIMIT 1
If you want the item with previous and next item this query lets you do just that.
This also allows You to have gaps in the data!
How about this:
Select * from table where id = 1 + 1