I have this table:
it produces by this query SELECT DISTINCT code, tariff_diff FROM mytable
.
Now, I want to update tariff_diff = 1 if code appear more than 1. (as example, I want to update tariff_diff = 1 where row Kuta,DPS50xxx)
I have tried :
update mytable SET tariff_diff = 1
WHERE in(select distinct code, tariff_diff from mytable)
But i am getting error syntax.
Operand should contain 1 column
If you want to alter the all the rows with same code you can use this.
UPDATE mytable SET mytable.tariff_diff = 1 WHERE mytable.code IN(SELECT count(*), code, tariff_diff from mytable GROUP BY code HAVING count(*)>1)
It is not possible to use same update table in select statement in subquery , you can find the reason in this link: Reason for not use same table in sub-query.
try below query:
SET #r_code = (select code from mytable GROUP BY code having count(code) > 1);
update mytable SET tariff_diff = 1 WHERE code in (#r_code);
You can find more about variable here in this link.More about Variables.
First of all store the id's into the some variable and then update those id's using in query.
From what I understand, you're wanting to set the tariff_diff to 1 only if more than one of the rows that are prefixed with Kuta,DPS50. exist. Matching on Kuta,DPS50.06, Kuta,DPS50.07, Kuta,DPS50.08, Kuta,DPS50.09, Kuta,DPS50.10.
Assuming all of your records are formatted like: XXX,xxx.###. You can use SUBSTRING_INDEX to parse the prefixed text (Kuta,DPS50.) to use as an identifier.
Then you can use a derived JOIN to match the codes that have duplicates of the prefixed values and update the matching rows.
If there are no duplicate values, no update will occur.
Example: http://sqlfiddle.com/#!9/658034/1 (I added an additional entry for Petang,DPS50.02 to demonstrate it works on other prefixed values.)
Query:
UPDATE mytable AS p
JOIN (
SELECT SUBSTRING_INDEX(code, '.', 1) AS prefix_code
FROM mytable
GROUP BY prefix_code
HAVING COUNT(prefix_code) > 1
) AS c
ON c.prefix_code = SUBSTRING_INDEX(p.code, '.', 1)
SET p.tariff_diff = 1;
Result:
| code | tariff_diff |
|-----------------------|-------------|
| Abiansemal,DPS50.02 | 0 |
| Kuta,DPS50.06 | 1 |
| Kuta,DPS50.07 | 1 |
| Kuta,DPS50.08 | 1 |
| Kuta,DPS50.09 | 1 |
| Kuta,DPS50.10 | 1 |
| Kuta Selatan,DPS50.05 | 0 |
| Kuta Ultara,DPS50.04 | 0 |
| Mengwi,DPS50.01 | 0 |
| Petang,DPS50.02 | 1 |
| Petang,DPS50.03 | 1 |
This will also avoid the SQL Error (1093) https://dev.mysql.com/doc/refman/5.6/en/update.html
You cannot update a table and select from the same table in a subquery.
Related
I'm having trouble using/understanding the SQL ALL operator. I have a table FOLDER_PERMISSION with the following columns:
+----+-----------+---------+----------+
| ID | FOLDER_ID | USER_ID | CAN_READ |
+----+-----------+---------+----------+
| 1 | 34353 | 45453 | 0 |
| 2 | 46374 | 342532 | 1 |
| 3 | 46374 | 32352 | 1 |
+----+-----------+---------+----------+
I want to select the folders where all the users have permission to read, how could I do it?
Use aggregation and having:
select folder_id
from t
group by folder_id
having min(can_read) = 1;
Gordon's answer seems better but for the sake of completeness, using ALL a query could look like:
SELECT x1.folder_id
FROM (SELECT DISTINCT
fp1.folder_id
FROM folder_permission fp1) x1
WHERE 1 = ALL (SELECT fp2.can_read
FROM folder_permission fp2
WHERE fp2.folder_id = x1.folder_id);
If you have a table for the folders themselves replace the derived table (aliased x1) with it.
But this only respects users present in folder_permissions. If not all users have a reference in that table you possibly won't get the folders really all users can read.
You can do aggregation :
SELECT fp.FOLDER_ID
FROM folder_permission fp
GROUP BY fp.FOLDER_ID
HAVING SUM( can_read = 0 ) = 0;
You can also express it :
SELECT fp.FOLDER_ID
FROM folder_permission fp
GROUP BY fp.FOLDER_ID
HAVING MIN(CAN_READ) = MAX(CAN_READ) AND MIN(CAN_READ) = 1;
If you wanted to return the full matching records, you could try using some exists logic:
SELECT ID, FOLDER_ID, USER_ID, CAN_READ
FROM yourTable t1
WHERE NOT EXISTS (SELECT 1 FROM yourTable t2
WHERE t2.FOLDER_ID = t1.FOLDER_ID AND t2.CAN_READ = 0);
Demo
The existence of a matching record in the above exists subquery would imply that there exist one or more users for that folder who do not have read access rights.
I'm importing data where groups of rows need to be given an id but there is nothing unique and common to them in the incoming data. What there is is a known indicator of the first row of a group and that the data is in order so we can step through row by row setting an id and then increment that id whenever this indicator is found. I've done this however it's incredibly slow, so is there a better way to do this in mysql or am i better off perhaps pre-processing the text data going line by line to add the id.
Example of data coming in, I need to increment an id whenever we see "NEW"
id,linetype,number,text
1,NEW,1234,sometext
2,CONTINUE,2412,anytext
3,CONTINUE,1,hello
4,NEW,2333,bla bla
5,CONTINUE,333,hello
6,NEW,1234,anything
So i'll end up with
id,linetype,number,text,group_id
1,NEW,1234,sometext,1
2,CONTINUE,2412,anytext,1
3,CONTINUE,1,hello,1
4,NEW,2333,bla bla,2
5,CONTINUE,333,hello,2
6,NEW,1234,anything,3
I've tried a stored procedure where i go row by row updating as i go, but it's super slow.
select count(*) from mytable into n;
set i=1;
while i<=n do
select linetype into l_linetype from mytable where id = i;
if l_linetype = "NEW" then
set l_id = l_id + 1;
end if;
update mytable set group_id = l_id where id = i;
end while;
No errors, it's just something that i could go line by line reading and writing the text file and do in a second while in mysql it's taking 100 seconds, it'd be nice if there was a way within mysql to do this reasonably fast so separate pre-processing was not needed.
In absence of MySQL 8+ (non availability of Windowing functions), you can use a Correlated Subquery instead:
EDIT: As pointed out by #Paul in comments,
SELECT t1.*,
(SELECT COUNT(*)
FROM your_table t2
WHERE t2.id <= t1.id
AND t2.linetype = 'NEW'
) group_id
FROM your_table t1
Above query can be more performant, if we define the following composite index (linetype, id). The order of columns is important, because we have a Range condition on id.
Previously:
SELECT t1.*,
(SELECT SUM(t2.linetype = 'NEW')
FROM your_table t2
WHERE t2.id <= t1.id
) group_id
FROM your_table t1
Above query requires indexing on id.
Another approach using User-defined Variables (Session variables) would be:
SELECT
t1.*,
#g := IF(t1.linetype = 'NEW', #g + 1, #g) AS group_id
FROM your_table t1
CROSS JOIN (SELECT #g := 0) vars
ORDER BY t1.id
It is like a looping technique, where we use Session Variables whose previous value is accessible during next row's calculation during SELECT. So, we initialize the variable #g to 0, and then compute it row by row. If we can encounter a row with NEW linetype, we increment it, else use the previous row's value. You can also check https://stackoverflow.com/a/53465139/2469308 for more discussion and caveats to take care of while using this approach.
For MySql 8.0+ you can use SUM() window function:
select *,
sum(linetype = 'NEW') over (order by id) group_id
from tablename
See the demo.
For previous versions you can simulate this functionality with the use of a variable:
set #group_id := 0;
select *,
#group_id := #group_id + (linetype = 'NEW') group_id
from tablename
order by id
See the demo.
Results:
| id | linetype | number | text | group_id |
| --- | -------- | ------ | -------- | -------- |
| 1 | NEW | 1234 | sometext | 1 |
| 2 | CONTINUE | 2412 | anytext | 1 |
| 3 | CONTINUE | 1 | hello | 1 |
| 4 | NEW | 2333 | bla bla | 2 |
| 5 | CONTINUE | 333 | hello | 2 |
| 6 | NEW | 1234 | anything | 3 |
I have this in my database:
75012
75016
94400
94500
94300
78400
I would like to select only the string where only the first two numbers match and show how many 94 there are so it will output 75012 = 2, 94 = 3, 78 = 1.
Here is what I tried:
select cpostal from fiche_personne WHERE cpostal LIKE LEFT(cpostal, 2);
you need to use a group by clause in your query.
SELECT LEFT(cpostal,2), COUNT(*) AS total
FROM fiche_personne
GROUP BY LEFT(cpostal,2)
please note that the COUNT(*) isn't the best way to complete the query but I don't know your actual table structure, so you should change this to an actual column name
select count(cpostal) from fiche_personne WHERE cpostal LEFT(cpostal, 2) = 94;
Resource: https://www.w3schools.com/sql/func_mysql_count.asp
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(i INT NOT NULL PRIMARY KEY);
INSERT INTO my_table VALUES
(75012),
(75016),
(94400),
(94500),
(94300),
(78400);
SELECT MIN(i) i, COUNT(*) total FROM my_table GROUP BY LEFT(i,2);
+-------+-------+
| i | total |
+-------+-------+
| 75012 | 2 |
| 78400 | 1 |
| 94300 | 3 |
+-------+-------+
I have some sql that looks like this:
SELECT
stageName,
count(*) as `count`
FROM x2production.contact_stages
WHERE FROM_UNIXTIME(createDate) between '2016-05-01' AND DATE_ADD('2016-08-31', INTERVAL 1 DAY)
AND (stageName = 'DI-Whatever' OR stageName = 'DI-Quote' or stageName = 'DI-Meeting')
Group by stageName
Order by field(stageName, 'DI-Quote', 'DI-Meeting', 'DI-Whatever')
This produces a table that looks like:
+-------------+-------+
| stageName | count |
+-------------+-------+
| DI-quote | 1230 |
| DI-Meeting | 985 |
| DI-Whatever | 325 |
+-------------+-------+
Question:
I would like a percentage from one row to the next. For example the percentage of DI-Meeting to DI-quote. The math would be 100*985/1230 = 80.0%
So in the end the table would look like so:
+-------------+-------+------+
| stageName | count | perc |
+-------------+-------+------+
| DI-quote | 1230 | 0 |
| DI-Meeting | 985 | 80.0 |
| DI-Whatever | 325 | 32.9 |
+-------------+-------+------+
Is there any way to do this in mysql?
Here is an SQL fiddle to mess w/ the data: http://sqlfiddle.com/#!9/61398/1
The query
select stageName,count,if(rownum=1,0,round(count/toDivideBy*100,3)) as percent
from
( select stageName,count,greatest(#rn:=#rn+1,0) as rownum,
coalesce(if(#rn=1,count,#prev),null) as toDivideBy,
#prev:=count as dummy2
from
( SELECT
stageName,
count(*) as `count`
FROM Table1
WHERE FROM_UNIXTIME(createDate) between '2016-05-01' AND DATE_ADD('2016-08-31', INTERVAL 1 DAY)
AND (stageName = 'DI-Underwriting' OR stageName = 'DI-Quote' or stageName = 'DI-Meeting')
Group by stageName
Order by field(stageName, 'DI-Quote', 'DI-Meeting', 'DI-Underwriting')
) xDerived1
cross join (select #rn:=0,#prev:=-1) as xParams1
) xDerived2;
Results
+-----------------+-------+---------+
| stageName | count | percent |
+-----------------+-------+---------+
| DI-Quote | 16 | 0 |
| DI-Meeting | 13 | 81.250 |
| DI-Underwriting | 4 | 30.769 |
+-----------------+-------+---------+
Note, you want a 0 as the percent for the first row. That is easily changed to 100.
The cross join brings in the variables for use and initializes them. The greatest and coalesce are used for safety in variable use as spelled out well in this article, and clues from the MySQL Manual Page Operator Precedence. The derived tables names are just that: every derived table needs a name.
If you do not adhere to the principles in those referenced articles, then the use of variables is unsafe. I am not saying I nailed it, but that safety is always my focus.
The assignment of variables need to follow a safe form, such as the #rn variable being set on the inside of a function like greatest or least. We know that #rn is always greater than 0. So we are using the greatest function to force our will on the query. Same trick with coalesce, null will never happen, and := has lower precedence in the column that follows it. That is, the last one: #prev:= which follows the coalesce.
That way, a variable is set before other columns in that select row attempt to use its value.
So, just getting the expected results does not mean you did it safely and that it will work with your real data.
What you need is to use a LAG function, since MySQL doesn't support it your have to mimic it this way:
select stageName,
cnt,
IF(valBefore is null,0,((100*cnt)/valBefore)) as perc
from (SELECT tb.stageName,
tb.cnt,
#ct AS valBefore,
(#ct := cnt)
FROM (SELECT stageName,
count(*) as cnt
FROM Table1,
(SELECT #_stage = NULL,
#ct := NULL) vars
WHERE FROM_UNIXTIME(createDate) between '2016-05-01'
AND DATE_ADD('2016-08-31', INTERVAL 1 DAY)
AND stageName in ('DI-Underwriting', 'DI-Quote', 'DI-Meeting')
Group by stageName
Order by field(stageName, 'DI-Quote', 'DI-Meeting', 'DI-Underwriting')
) tb
WHERE (CASE WHEN #_stage IS NULL OR #_stage <> tb.stageName
THEN #ct := NULL
ELSE NULL END IS NULL)
) as final
See it working here: http://sqlfiddle.com/#!9/61398/35
EDIT I've actually edited it to remove an unnecessary step (subquery)
I'm a beginner at MySQL and I'm having a hard time trying to figure out how to solve this problem:
I have two tables with many entries each. Let's say these are the tables:
Table 1 || Table 2
------------- || -------------------
| dt1 | dt2 | || | dt3 | dt4 | dt5 |
------------- || -------------------
| 1 | abc | || | 3 | wsx | 123 |
| 7 | asd | || | 3 | qax | 456 |
| 19 | zxc | || | 4 | rfv | 789 |
------------- || -------------------
What I want to do is to have as a result one table with columns "dt2", "dt4" and "dt5" and with only one entry. For that, the query I'll apply to each table may even have to LIMIT the results. To get the results I want from each table separetelly I would do the following:
SELECT `dt2` FROM `table1` WHERE `dt1`=7;
and
SELECT `dt4`,`dt5` FROM `table2` WHERE `dt3`=3 LIMIT 0,1;
One more thing, I don't want to use a subquery for each column, because in the real thing I'm trying to solve, I'm calling 5 or 6 columns from each table.
Just to make clear, what I want to get is something like this:
-------------------
| dt2 | dt4 | dt5 |
-------------------
| asd | qax | 456 |
-------------------
SELECT a.dt2, b.dt4, b.dt5
FROM table1 a, table2 b
WHERE a.dt2 = 'asd'
LIMIT 0,1;
Ben's answer solved my similar issue.
SELECT t1.dt2, t2.dt4, t2.dt5, t2.dt3 #get dt3 data from table2
FROM table1 t1, table2 t2
WHERE t1.dt2 = 'asd' AND t2.dt4 = 'qax' AND t2.dt5 = 456
| asd | qax | 456 | 3 |
'3' being the data I require by querying the 'qax', 456 data in table2, otherwise you're specifying exactly what data will be returned from the columns.
I only had 2 tables to query in my instance, so the AND expression I can get away with using, it probably isn't best practice and there's most likely a better way for matching data from multiple tables.
EDIT: I've just realised this question is 5 years old.. I hope you achieved what you wanted to by now.
SELECT a.dt2, b.dt4, b.dt5
FROM table1 a, table2 b
WHERE a.dt2 = 'asd'
LIMIT 0,1;
Ben's answer is good, you can use more tables just by separating them by comma (,) , but if there's relationship between those tables then you should use some Sub Query or JOIN
In here there is smth called INNER JOIN , CROSS JOIN , LEFT JOIN and RIGHT JOIN in MYSQL and also SQL Server that allows you yo get data from different tables as much you want via conditions based on your columns;
So let's start :
First Let's create our tables (sample1,sample2) :
--Create Table sample1 :
CREATE TABLE sample1 (id BIGINT NOT NULL AUTO_INCREMEN , name_sample1 VARCHAR(100),age INT);
--Create Table sample2 :
CREATE TABLE sample2 (id BIGINT NOT NULL AUTO_INCREMEN , name_sample2 VARCHAR(100));
-- Now Let's put an trigger in order to avoid getting incorrect values :
DELIMITER $$
CREATE TRIGGER IF NOT EXISTS insert_sample1_trigger BEFORE INSERT ON sample1
FOR EACH ROW
BEGIN
IF NEW.name_sample1 <> "" AND NEW.age <> "0" THEN
INSERT INTO sample2 (name_sample2) VALUES (NEW.name_sample1);
ELSE
INSERT INTO sample1 (name_sample1,age) VALUES ("Unknown" , 10);
INSERT INTO sample2 (name_sample2) VALUES ("Unknown");
END IF;
END$$
DELIMITER ;
After U ran this query , trigger will be added;
--- Now Inserting
INSERT INTO sample1(name_sample1,age) VALUES ("SomeOne Name" , 15);
After running this query the name will be added to sample2 table, because id is auto increment it's not needed to be called in the insert query
id
name_sample1
age
1
SomeOne Name
15
But if I give another value ...
INSERT INTO sample1(name_sample1,age) VALUES ("SomeOne Name" , 0);
id
name_sample1
age
1
Unknown
10
-- Now at last Select query what we were waiting for :
SELECT * FROM sample1 s1 INNER JOIN sample2 s2 USING(id) GROUP BY s1.name_sample1
ORDER BY s1.name_sample1 DESC
This query selects all columns from tables sample1 and sample2 if you want to show some other columns change * via your column name.
That's it