MySql XNOR equivalent - mysql

I'm writing a SQL query which checks for incorrect equipment in its where clause. In order to perform this operation I need to check each of the equipment's revision cycles one by one, so this clause would limit the results to correct equipment (not having any of the cycles incorrect):
where not exists(select * from trevision_cycle cycle
where cycle.id_equipment = equip.id and cycle.`status` = 'NO_OK')
However, now I want to introduce a query parameter in order to retrieve only the correct equipment or only the incorrect equipment, depending on its value. Let's say I call it incorrect, so I would like to do the same as what's done here:
where (not incorrect and not exists(select * from trevision_cycle cycle
where cycle.id_equipment = equip.id and cycle.`status` = 'NO_OK'))
or (incorrect and exists (select * from trevision_cycle cycle
where cycle.id_equipment = equip.id and cycle.`status` = 'NO_OK'))
So, if the incorrect flag is present, check for equipment having at least one incorrect revision cycle. Otherwise, retrieve only the equipment having all of them correct. The query looks quite redundant though and the same result could be achieved with a logical XNOR.
Do I have a better choice for this?
Update
Sample data
Equipment 1 -> Has one NO_OK cycle
Equipment 2 -> All its cycles are OK
Query with incorrect = true -> Returns only Equipment 1
Query with incorrect = false -> Returns only Equipment 2

You can combine XOR with NOT - both exists in mysql. This is truth table for XOR:
+----------------+
|A | B | A XOR B |
+--+---+---------+
|0 | 0 | 0 |
|0 | 1 | 1 |
|1 | 0 | 1 |
|1 | 1 | 0 |
+----------------+
If we apply NOT to this table, it will look this:
+---------------------+
|A | B | NOT(A XOR B) |
+--+---+--------------+
|0 | 0 | 1 |
|0 | 1 | 0 |
|1 | 0 | 0 |
|1 | 1 | 1 |
+---------------------+
Same as the XNOR table. So, WHERE can look like this:
WHERE NOT (incorrect XOR EXISTS(SELECT * FROM trevision_cycle cycle
WHERE cycle.id_equipment = equip.id AND cycle.`status` = 'NO_OK'))

Referencing This Fellow's blog, if you were to create the function XNOR like so:
--XNOR
CREATE FUNCTION XNOR (#a bit, #b bit) RETURNS bit AS
BEGIN
RETURN #a ^ #b ^ 1
END
Then you could simplify your statement using it as so:
where dbo.XNOR(incorrect, exists(select * from trevision_cycle cycle
where cycle.id_equipment = equip.id and cycle.`status` = 'NO_OK'))
EDIT: My apologies, for MySQL, you would have to create the function using its XOR instead of '^' ('^' is the XOR function for SQL Server). I'm uncertain if the rest of the syntax is correct for MySQL, but you get the idea:
--XNOR
CREATE FUNCTION XNOR (#a bit, #b bit) RETURNS bit AS
BEGIN
RETURN #a XOR #b XOR 1
END
If you have reason not to make up a new function to do this, then the following is logically equivalent:
where incorrect XOR exists(select * from trevision_cycle cycle
where cycle.id_equipment = equip.id and cycle.`status` = 'NO_OK') XOR 1

Related

count different value in column, put into new columns [MySQL]

I've been looking for a tutorial everywhere but I couldn't find any specific solution to this. Does anybody know how to query from this table:
//////////////////
places // type /
//////////////////
alabama // ZX //
alabama // AQ //
africa // AQ //
vietnam // FD //
vietnam // FD //
////////////////
into this
///////////////////////////////////////////////////
places | ZX | AQ | FD
///////////////////////////////////////////////////
alabama| 1 | 2 | 0
africa | 0 | 1 | 0
vietnam| 0 | 0 | 2
///////////////////////////////////////////////////
EDIT: I edited the format, sorry for the error earlier.
Here is a possible solution that works across a variety of database engines.
-- Start: Setup Data
CREATE TABLE things (
places VARCHAR(50),
type VARCHAR(50)
);
INSERT INTO things VALUES ('alabama','ZX');
INSERT INTO things VALUES ('alabama','AQ');
INSERT INTO things VALUES ('africa','AQ');
INSERT INTO things VALUES ('vietnam','FD');
INSERT INTO things VALUES ('vietnam','FD');
-- End: Setup Data
SELECT
things.places,
SUM(
CASE
WHEN things.type = 'ZX' THEN 1
ELSE 0
END
) AS ZX_COUNT,
SUM(
CASE
WHEN things.type = 'AQ' THEN 1
ELSE 0
END
) AS AQ_COUNT,
SUM(
CASE
WHEN things.type = 'FD' THEN 1
ELSE 0
END
) AS FD_COUNT
FROM things
GROUP BY things.places
The drawback to this solution is that you need to know a priori which columns you want to have in your pivot table.
Here is an very well thought out answer to your question.

Two things to do in MySQL IF()

I have a problem concerning the IF() function in MySQL.
I would like to return a string and change the value of a variable. Somwhat like:
IF(#firstRow=1, "Dear" AND #firstRow:=0, "dear")
This outputs only '0' instead of 'Dear'...
I would be very thankful for some input on ways I could solve this problem!
Louis :)
AND is a boolean operator, not a "also do this other thing" operator.
"Dear" AND 0 returns 0 because 0 is treated as false in MySQL and <anything> AND false will return false.
Also because the integer/boolean value of "Dear" is 0 as well. Using a string in a numeric context just reads initial digits in the string, if any, and ignores the rest.
It's not clear what your problem is, but I guess you want to capitalize the word "dear" if the row is the first one in the result set.
Instead of being too clever by half trying to fit the side-effect into your expression, do yourself a favor and break it out into a separate column:
mysql> SELECT IF(#firstRow=1, 'Dear', 'dear'), #firstRow:=0 AS _ignoreThis
-> FROM (SELECT #firstRow:=1) AS _init
-> CROSS JOIN
-> mytable;
+---------------------------------+-------------+
| IF(#firstRow=1, 'Dear', 'dear') | _ignoreThis |
+---------------------------------+-------------+
| Dear | 0 |
| dear | 0 |
| dear | 0 |
+---------------------------------+-------------+
But if you really want to make your code as confusing and unreadable as possible, you can do something like this:
SELECT IF(#firstRow=1, CONCAT('Dear', IF(#firstRow:=0, '', '')), 'dear')
FROM (SELECT #firstRow:=1) AS _init
CROSS JOIN
...
But remember this important metric of code quality: WTFs per minute.
Use a case expression instead of IF() as the syntax is far easier to follow e.g.
select
case when #firstRow = 1 then 'Dear' else 'dear' end AS Salutation
, #firstRow := 0
from (
select 1 n union all
select 2 n union all
select 3
) d
cross join (SELECT #firstRow:=1) var
+---+------------+----------------+
| | Salutation | #firstRow := 0 |
+---+------------+----------------+
| 1 | Dear | 0 |
| 2 | dear | 0 |
| 3 | dear | 0 |
+---+------------+----------------+
Demo

Is there a way to merge boolean value fields in MySQL?

I have a table that contains several fields with Boolean values.
I need 3 of the fields to be merged into one only if all 3 of them are true.
I was thinking about a JOIN, but it looks like it cannot do what I need.
------------------------------------------------------------
| Email address| permission1 | permission2 | permission3
------------------------------------------------------------
| test#test.com| 1 | 0 | 1
------------------------------------------------------------
| test2#test.com| 1 | 1 | 1
------------------------------------------------------------
I would expect a result, perhaps in a new table to look like this:
--------------------------------
| Email address| permissionMerge
--------------------------------
| test#test.com| 0 |
--------------------------------
| test2#test.com| 1 |
--------------------------------
You could use CASE syntax
SELECT `email address`,
CASE WHEN `permission1` = 1
AND `permission2` = 1
AND `permission3` = 1
THEN 1
ELSE 0 END AS 'permissionMerge'
FROM `Table`
You could also do it that way:
SELECT `email address`,
CASE WHEN SUM(`permission1`,`permission2`,`permission3`) = 3
THEN 1
ELSE 0 END AS 'permissionMerge'
FROM `Table`
GROUP BY `email address`
Select `Email adderss`,(permission1 and permission2 and permission3) as permissionMerge from table
Depending on the quality of data, you could get away with something as simple as:
SELECT address,
CASE SUM(permission1, permission2, permission3)
WHEN 3 THEN 1
ELSE 0
END AS permissionMerge
FROM undisclosed_table
... or you'll need something more robust:
SELECT address,
CASE
WHEN permission1 IS TRUE AND permission2 IS TRUE AND permission3 IS TRUE THEN 1
ELSE 0
END AS permissionMerge
FROM undisclosed_table
You can generate a new table with:
CREATE TABLE table_with_duplicate_data
SELECT address,
CASE
WHEN permission1 IS TRUE AND permission2 IS TRUE AND permission3 IS TRUE THEN 1
ELSE 0
END AS permissionMerge
FROM undisclosed_table
... thought I'd advise against it: it'll just waste storage and you need to keep data up-to-date. If it's too much typing, you can create a view or (since MySQL/5.7.5) a generated column.

Two methods of performing cohort analysis in MySQL using joins

I make a cohort analysis processor. Input parameters: time range and step, condition (initial event) to exctract cohorts, additional condition (retention event) to check after each N hours/days/months. Output parameters: cohort analysis grid, like this:
0h | 16h | 32h | 48h | 64h | 80h | 96h |
cohort #00 15 | 6 | 4 | 1 | 1 | 2 | 2 |
cohort #01 1 | 35 | 8 | 0 | 2 | 0 | 1 |
cohort #02 0 | 3 | 31 | 11 | 5 | 3 | 0 |
cohort #03 0 | 0 | 4 | 27 | 7 | 6 | 2 |
cohort #04 0 | 1 | 1 | 4 | 29 | 4 | 3 |
Basically:
fetch cohorts: unique users who did something 1 in every period from time_begin every time_step.
find how many of them (in each cohort) did something 2 after N seconds, N*2 seconds, N*3, and so on until now.
In short - I have 2 solutions. One works too slow and includes a heavy select with joins for each data step: 1 day, 2 day, 3 day, etc. I want to optimize it by joining result for every data step to cohorts - and it's the second solution. It looks like it works but I'm not sure it's the best way and that it will give the same result even if cohorts will intersect. Please check it out.
Here's the whole story.
I have a table of > 100,000 events, something like this:
#user-id, timestamp, event_name
events_view (uid varchar(64), tm int(11), e varchar(64))
example input row:
"user_sampleid1", 1423836540, "level_end:001:win"
To make a cohort analisys first I extract cohorts: for example, users, who send special event '1st_launch' in 10 hour periods starting from 2015-02-13 and ending with 2015-02-16. All code in this post is simplified and shortened to see the idea.
DROP TABLE IF EXISTS tmp_c;
create temporary table tmp_c (uid varchar(64), tm int(11), c int(11) );
set beg = UNIX_TIMESTAMP('2015-02-13 00:00:00');
set en = UNIX_TIMESTAMP('2015-02-16 00:00:00');
select min(tm) into t_start from events_view ;
select max(tm) into t_end from events_view ;
if beg < t_start then
set beg = t_start;
end if;
if en > t_end then
set en = t_end;
end if;
set period = 3600 * 10;
set cnt_c = ceil((en - beg) / period) ;
/*works quick enough*/
WHILE i < cnt_c DO
insert into tmp_c (
select uid, min(tm), i from events_view where
locate("1st_launch", e) > 0 and tm > (beg + period * i)
AND tm <= (beg + period * (i+1)) group by uid );
SET i = i+1;
END WHILE;
Cohorts may consist the same user ids, though usually one user is exist only in one cohort. And in each cohort users are unique.
Now I have temp table like this:
user_id | 1st timestamp | cohort_no
uid1 1423836540 0
uid2 1423839540 0
uid3 1423841160 1
uid4 1423841460 2
...
uidN 1423843080 M
Then I need to again divide time range on periods and calculate for each period how many users from each cohort have sent event "level_end:001:win".
For each small period I select all unique users who have sent "level_end:001:win" event and left join them to tmp_c cohorts table. So I have something like this:
user_id | 1st timestamp | cohort_no | user_id | other fields...
uid1 1423836540 0 uid1
uid2 1423839540 0 null
uid3 1423841160 1 null
uid4 1423841460 2 uid4
...
uidN 1423843080 M null
This way I see how many users from my cohorts are in those who have sent "level_end:001:win", exclude not found by where clause: where t2.uid is not null.
Finally I perform grouping and have counts of users in each cohort, who have sent "level_end:001:win" in this particluar period.
Here's the code:
DROP TABLE IF EXISTS tmp_res;
create temporary table tmp_res (uid varchar(64) CHARACTER SET cp1251 NOT NULL, c int(11), cnt int(11) );
set i = 0;
set cnt_c = ceil((t_end - beg) / period) ;
WHILE i < cnt_c DO
insert into tmp_res
select concat(beg + period * i, "_", beg + period * (i+1)), c, count(distinct(uid)) from
(select t1.uid, t1.c from tmp_c t1 left join
(select uid, min(tm) from events_view where
locate("level_end:001:win", e) > 0 and
tm > (beg + period * i) AND tm <= (beg + period * (i+1)) group by uid ) t2
on t1.uid = t2.uid where t2.uid is not null) t3
group by c;
SET i = i+1;
END WHILE;
/*getting result of the first method: tooo slooooow!*/
select * from tmp_res;
The result I've got (it's ok that some cohorts are not appear on some periods):
"1423832400_1423890000","1","35"
"1423832400_1423890000","2","3"
"1423832400_1423890000","3","1"
"1423832400_1423890000","4","1"
"1423890000_1423947600","1","21"
"1423890000_1423947600","2","50"
"1423890000_1423947600","3","2"
"1423947600_1424005200","1","9"
"1423947600_1424005200","2","24"
"1423947600_1424005200","3","70"
"1423947600_1424005200","4","6"
"1424005200_1424062800","1","7"
"1424005200_1424062800","2","15"
"1424005200_1424062800","3","21"
"1424005200_1424062800","4","32"
"1424062800_1424120400","1","7"
"1424062800_1424120400","2","13"
"1424062800_1424120400","3","24"
"1424062800_1424120400","4","18"
"1424120400_1424178000","1","10"
"1424120400_1424178000","2","12"
"1424120400_1424178000","3","18"
"1424120400_1424178000","4","14"
"1424178000_1424235600","1","6"
"1424178000_1424235600","2","7"
"1424178000_1424235600","3","9"
"1424178000_1424235600","4","12"
"1424235600_1424293200","1","6"
"1424235600_1424293200","2","8"
"1424235600_1424293200","3","9"
"1424235600_1424293200","4","5"
"1424293200_1424350800","1","5"
"1424293200_1424350800","2","3"
"1424293200_1424350800","3","11"
"1424293200_1424350800","4","10"
"1424350800_1424408400","1","8"
"1424350800_1424408400","2","5"
"1424350800_1424408400","3","7"
"1424350800_1424408400","4","7"
"1424408400_1424466000","2","6"
"1424408400_1424466000","3","7"
"1424408400_1424466000","4","3"
"1424466000_1424523600","1","3"
"1424466000_1424523600","2","4"
"1424466000_1424523600","3","8"
"1424466000_1424523600","4","2"
"1424523600_1424581200","2","3"
"1424523600_1424581200","3","3"
It works but it takes too much time to process because there are many queries here instead of one, so I need to rewrite it.
I think it can be rewritten with joins, but I'm still not sure how.
I decided to make a temporary table and write period boundaries in it:
DROP TABLE IF EXISTS tmp_times;
create temporary table tmp_times (tm_start int(11), tm_end int(11));
set cnt_c = ceil((t_end - beg) / period) ;
set i = 0;
WHILE i < cnt_c DO
insert into tmp_times values( beg + period * i, beg + period * (i+1));
SET i = i+1;
END WHILE;
Then I get periods-to-events mapping (user_id + timestamp represent particular event) to temp table and left join it to cohorts table and group the result:
SELECT Concat(tm_start, "_", tm_end) per,
t1.c coh,
Count(DISTINCT( t2.uid ))
FROM tmp_c t1
LEFT JOIN (SELECT *
FROM tmp_times t3
LEFT JOIN (SELECT uid,
tm
FROM events_view
WHERE Locate("level_end:101:win", e) > 0)
t4
ON ( t4.tm > t3.tm_start
AND t4.tm <= t3.tm_end )
WHERE t4.uid IS NOT NULL
ORDER BY t3.tm_start) t2
ON t1.uid = t2.uid
WHERE t2.uid IS NOT NULL
GROUP BY per,
coh
ORDER BY per,
coh;
In my tests this returns the same result as method #1. I can't check the result manually, but I understand how method #1 work more and as far I can see it gives what I want. Method #2 is faster, but I'm not sure it's the best way and it will give the same result even if cohorts will intersect.
Maybe there are well-known common methods to perform a cohort analysis in SQL? Is method #1 I use more reliable than method #2? I work with joins not that often, that's why still do not fully understand joins magic yet.
Method #2 looks like pure magic, and I used to not believe in what I don't understand :)
Thanks for answers!

How do I check to see if a value is an integer in MySQL?

I see that within MySQL there are Cast() and Convert() functions to create integers from values, but is there any way to check to see if a value is an integer? Something like is_int() in PHP is what I am looking for.
I'll assume you want to check a string value. One nice way is the REGEXP operator, matching the string to a regular expression. Simply do
select field from table where field REGEXP '^-?[0-9]+$';
this is reasonably fast. If your field is numeric, just test for
ceil(field) = field
instead.
Match it against a regular expression.
c.f. http://forums.mysql.com/read.php?60,1907,38488#msg-38488 as quoted below:
Re: IsNumeric() clause in MySQL??
Posted by: kevinclark ()
Date: August 08, 2005 01:01PM
I agree. Here is a function I created for MySQL 5:
CREATE FUNCTION IsNumeric (sIn varchar(1024)) RETURNS tinyint
RETURN sIn REGEXP '^(-|\\+){0,1}([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+|[0-9]+)$';
This allows for an optional plus/minus sign at the beginning, one optional decimal point, and the rest numeric digits.
Suppose we have column with alphanumeric field having entries like
a41q
1458
xwe8
1475
asde
9582
.
.
.
.
.
qe84
and you want highest numeric value from this db column (in this case it is 9582) then this query will help you
SELECT Max(column_name) from table_name where column_name REGEXP '^[0-9]+$'
Here is the simple solution for it
assuming the data type is varchar
select * from calender where year > 0
It will return true if the year is numeric else false
This also works:
CAST( coulmn_value AS UNSIGNED ) // will return 0 if not numeric string.
for example
SELECT CAST('a123' AS UNSIGNED) // returns 0
SELECT CAST('123' AS UNSIGNED) // returns 123 i.e. > 0
To check if a value is Int in Mysql, we can use the following query.
This query will give the rows with Int values
SELECT col1 FROM table WHERE concat('',col * 1) = col;
The best i could think of a variable is a int Is a combination with MySQL's functions CAST() and LENGTH().
This method will work on strings, integers, doubles/floats datatypes.
SELECT (LENGTH(CAST(<data> AS UNSIGNED))) = (LENGTH(<data>)) AS is_int
see demo http://sqlfiddle.com/#!9/ff40cd/44
it will fail if the column has a single character value. if column has
a value 'A' then Cast('A' as UNSIGNED) will evaluate to 0 and
LENGTH(0) will be 1. so LENGTH(Cast('A' as UNSIGNED))=LENGTH(0) will
evaluate to 1=1 => 1
True Waqas Malik totally fogotten to test that case. the patch is.
SELECT <data>, (LENGTH(CAST(<data> AS UNSIGNED))) = CASE WHEN CAST(<data> AS UNSIGNED) = 0 THEN CAST(<data> AS UNSIGNED) ELSE (LENGTH(<data>)) END AS is_int;
Results
**Query #1**
SELECT 1, (LENGTH(CAST(1 AS UNSIGNED))) = CASE WHEN CAST(1 AS UNSIGNED) = 0 THEN CAST(1 AS UNSIGNED) ELSE (LENGTH(1)) END AS is_int;
| 1 | is_int |
| --- | ------ |
| 1 | 1 |
---
**Query #2**
SELECT 1.1, (LENGTH(CAST(1 AS UNSIGNED))) = CASE WHEN CAST(1.1 AS UNSIGNED) = 0 THEN CAST(1.1 AS UNSIGNED) ELSE (LENGTH(1.1)) END AS is_int;
| 1.1 | is_int |
| --- | ------ |
| 1.1 | 0 |
---
**Query #3**
SELECT "1", (LENGTH(CAST("1" AS UNSIGNED))) = CASE WHEN CAST("1" AS UNSIGNED) = 0 THEN CAST("1" AS UNSIGNED) ELSE (LENGTH("1")) END AS is_int;
| 1 | is_int |
| --- | ------ |
| 1 | 1 |
---
**Query #4**
SELECT "1.1", (LENGTH(CAST("1.1" AS UNSIGNED))) = CASE WHEN CAST("1.1" AS UNSIGNED) = 0 THEN CAST("1.1" AS UNSIGNED) ELSE (LENGTH("1.1")) END AS is_int;
| 1.1 | is_int |
| --- | ------ |
| 1.1 | 0 |
---
**Query #5**
SELECT "1a", (LENGTH(CAST("1.1" AS UNSIGNED))) = CASE WHEN CAST("1a" AS UNSIGNED) = 0 THEN CAST("1a" AS UNSIGNED) ELSE (LENGTH("1a")) END AS is_int;
| 1a | is_int |
| --- | ------ |
| 1a | 0 |
---
**Query #6**
SELECT "1.1a", (LENGTH(CAST("1.1a" AS UNSIGNED))) = CASE WHEN CAST("1.1a" AS UNSIGNED) = 0 THEN CAST("1.1a" AS UNSIGNED) ELSE (LENGTH("1.1a")) END AS is_int;
| 1.1a | is_int |
| ---- | ------ |
| 1.1a | 0 |
---
**Query #7**
SELECT "a1", (LENGTH(CAST("1.1a" AS UNSIGNED))) = CASE WHEN CAST("a1" AS UNSIGNED) = 0 THEN CAST("a1" AS UNSIGNED) ELSE (LENGTH("a1")) END AS is_int;
| a1 | is_int |
| --- | ------ |
| a1 | 0 |
---
**Query #8**
SELECT "a1.1", (LENGTH(CAST("a1.1" AS UNSIGNED))) = CASE WHEN CAST("a1.1" AS UNSIGNED) = 0 THEN CAST("a1.1" AS UNSIGNED) ELSE (LENGTH("a1.1")) END AS is_int;
| a1.1 | is_int |
| ---- | ------ |
| a1.1 | 0 |
---
**Query #9**
SELECT "a", (LENGTH(CAST("a" AS UNSIGNED))) = CASE WHEN CAST("a" AS UNSIGNED) = 0 THEN CAST("a" AS UNSIGNED) ELSE (LENGTH("a")) END AS is_int;
| a | is_int |
| --- | ------ |
| a | 0 |
see demo
What about:
WHERE table.field = "0" or CAST(table.field as SIGNED) != 0
to test for numeric and the corrolary:
WHERE table.field != "0" and CAST(table.field as SIGNED) = 0
I have tried using the regular expressions listed above, but they do not work for the following:
SELECT '12 INCHES' REGEXP '^(-|\\+){0,1}([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+|[0-9]+)$' FROM ...
The above will return 1 (TRUE), meaning the test of the string '12 INCHES' against the regular expression above, returns TRUE. It looks like a number based on the regular expression used above. In this case, because the 12 is at the beginning of the string, the regular expression interprets it as a number.
The following will return the right value (i.e. 0) because the string starts with characters instead of digits
SELECT 'TOP 10' REGEXP '^(-|\\+){0,1}([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+|[0-9]+)$' FROM ...
The above will return 0 (FALSE) because the beginning of the string is text and not numeric.
However, if you are dealing with strings that have a mix of numbers and letters that begin with a number, you will not get the results you want. REGEXP will interpret the string as a valid number when in fact it is not.
This works well for VARCHAR where it begins with a number or not..
WHERE concat('',fieldname * 1) != fieldname
may have restrictions when you get to the larger NNNNE+- numbers
for me the only thing that works is:
CREATE FUNCTION IsNumeric (SIN VARCHAR(1024)) RETURNS TINYINT
RETURN SIN REGEXP '^(-|\\+){0,1}([0-9]+\\.[0-9]*|[0-9]*\\.[0-9]+|[0-9]+)$';
from kevinclark all other return useless stuff for me in case of 234jk456 or 12 inches