So I'm trying to rewrite a MySQL query to MSSQL, but it's not as easy as I thought, since the functions from MySQL are not used in MSSQL(2014).
My MySQL query looks like this:
SELECT
(SELECT
REPLACE(GROUP_CONCAT(DISTINCT CONCAT(versions.Name, ': ', versions.Version)),
',',
', </br>') AS VersionString
FROM
target
LEFT JOIN
versions ON versions.targetid = target.id
WHERE
target.TestJobId = testruns.TestJobId
AND versions.Name = 'SW') AS NameVersion
FROM
testruns
I've tried to rewrite it, but I can't seem to make it work.
SELECT
(SELECT stuff(
(SELECT DISTINCT ', <br>' + cast([testreportingdebug].versions.Name + ': ' + [testreportingdebug].versions.Version AS VARCHAR(max))
FROM [SwMetrics].[testreportingdebug].[target]
FOR XML path('')
), 1, 1, '') AS VersionString
FROM
[testreportingdebug].[target]
LEFT JOIN
[testreportingdebug].[versions] ON [testreportingdebug].[versions].[targetid] = [testreportingdebug].[target].[id]
WHERE
[testreportingdebug].[target].[TestJobId] = [testreportingdebug].[testruns].[TestJobId]
AND [testreportingdebug].[versions].[Name] = 'SW') AS NameVersion
FROM
[testreportingdebug].[testruns];
The error I get is:
Msg 512, Level 16, State 1, Line 1
Subquery returned more than 1 value. This is not permitted when the subquery follows =, !=, <, <= , >, >= or when the subquery is used as an expression.
I'm a bit stuck what/how to rewrite this query. How can I rewrite it to work in SQL Server?
In SQL server STRING_AGG() replaces GROUP_CONCAT() and we can specify the seperator as </br>.
SELECT
STRING_AGG(
DISTINCT CONCAT(
versions.Name,
': ',
versions.Version,
'</br') AS VersionString
FROM target
LEFT JOIN versions
ON versions.targetid = target.id
AND target.TestJobId = testruns.TestJobId
WHERE versions.Name = 'SW';
Related
I have a multi-join query that targeting the hospital's chart database.
this takes 5~10 seconds or more.
This is the visual expain using mysql workbench.
The query is below.
select sc.CLIENT_ID as 'guardianId', sp.PET_ID as 'patientId', sp.NAME as 'petName'
, (select BW from syn_vital where HOSPITAL_ID = sp.HOSPITAL_ID and PET_ID = sp.PET_ID order by DATE, TIME desc limit 1) as 'weight'
, sp.BIRTH as 'birth', sp.RFID as 'regNo', sp.BREED as 'vName'
, (case when ss.NAME like '%fel%' or ss.NAME like '%cat%' or ss.NAME like '%pawpaw%' or ss.NAME like '%f' then '002'
when ss.NAME like '%canine%' or ss.NAME like '%dog%' or ss.NAME like '%can%' then '001' else '007' end) as 'sCode'
, (case when LOWER(replace(sp.SEX, ' ', '')) like 'male%' then 'M'
when LOWER(replace(sp.SEX, ' ', '')) like 'female%' or LOWER(replace(sp.SEX, ' ', '')) like 'fam%' or LOWER(replace(sp.SEX, ' ', '')) like 'woman%' then 'F'
when LOWER(replace(sp.SEX, ' ', '')) like 'c.m%' or LOWER(replace(sp.SEX, ' ', '')) like 'castratedmale' or LOWER(replace(sp.SEX, ' ', '')) like 'neutered%' or LOWER(replace(sp.SEX, ' ', '')) like 'neutrality%man%' or LOWER(replace(sp.SEX, ' ', '')) like 'M.N%' then 'MN'
when LOWER(replace(sp.SEX, ' ', '')) like 'woman%' or LOWER(replace(sp.SEX, ' ', '')) like 'f.s%' or LOWER(replace(sp.SEX, ' ', '')) like 'S%' or LOWER(replace(sp.SEX, ' ', '')) like 'neutrality%%' then 'FS' else 'NONE' end) as 'sex'
from syn_client sc
left join syn_tel st on sc.HOSPITAL_ID = st.HOSPITAL_ID and sc.CLIENT_ID = st.CLIENT_ID
inner join syn_pet sp on sc.HOSPITAL_ID = sp.HOSPITAL_ID and sc.FAMILY_ID = sp.FAMILY_ID and sp.STATE = 0
inner join syn_species ss on sp.HOSPITAL_ID = ss.HOSPITAL_ID and sp.SPECIES_ID = ss.SPECIES_ID
WHERE
trim(replace(st.NUMBER, '-','')) = '01099999999'
and trim(sc.NAME) = 'johndoe'
and sp.HOSPITAL_ID = 'HOSPITALID999999'
order by TEL_DEFAULT desc
I would like to know how to improve the performance of this complex query.
The most obvious performance killers in your query are the non-sargable criteria in your where clause.
trim(replace(st.NUMBER, '-','')) = '01099999999'
This cannot use any available index as you have applied a function to the column, which needs to be evaluated before the comparison can be made.
As suggested by Pham, you could change your criterion to -
st.number IN ('01099999999', '01-099-999-999', 'ALL_OTHERS_FORMAT_YOU_ACCEPTS...')
or better still would be to normalize the numbers before you store them (you can always apply formatting for display purposes), that way you know how to search the stored data. Strip all the hyphens and spaces from the existings numbers -
UPDATE syn_tel
SET number = REPLACE(REPLACE(number, '-',''), ' ', '')
WHERE number LIKE '% %' OR number LIKE '%-%';
Similarly for the next criterion -
trim(sc.NAME) = 'johndoe'
The name should be trimmed before being stored in the database so there is no need to trim it when searching it. Update already stored names to trim whitespace -
UPDATE syn_client
SET NAME = TRIM(NAME)
WHERE NAME LIKE ' %' OR NAME LIKE '% ';
Changing sp.HOSPITAL_ID = 'HOSPITALID999999' to sc.HOSPITAL_ID = 'HOSPITALID999999' will allow for the use of a composite index on syn_client (HOSPITAL_ID, name) assuming you drop the TRIM() from the previously discussed criterion.
The sorting in your sub-query for weight might be wrong -
order by DATE, TIME desc limit 1
presumably you want the most recent weight -
order by `DATE` desc, `TIME` desc limit 1
/* OR */
order by CONCAT(`DATE`, ' ', `TIME`) desc limit 1
order by DATE, TIME desc -- really? That's equivalent to date ASC, time DESC. If you want "newest first", then ORDER BY date DESC, time DESC. Furthermore, it is usually bad practice and clumsy to code when you have DATE and TIME in separate columns. Is there a strong reason for storing them separately? It is reasonably easy to split them apart in a SELECT.
Similarly, cleanse NUMBER and NAME when inserting.
This will make the first subquery much faster:
syn_vital needs INDEX(hostital_id, pet_id, date, time, BW)
LIKE with a leading wildcard (%) is slow, but you probably cannot avoid it in this case.
LOWER(replace(sp.SEX, ' ', '')) -- Cleanse the input during INSERT, not on output!.
LOWER(...) -- With a suitable COLLATION (eg, the default), calling LOWER is unnecessary.
Some of these 'composite' INDEXes may be useful:
ss: INDEX(HOSPITAL_ID, SPECIES_ID, NAME)
st: INDEX(HOSPITAL_ID, CLIENT_ID, NUMBER)
sp: INDEX(HOSPITAL_ID, PET_ID)
What table is TEL_DEFAULT in?
You may want to:
Create index on syn_client(hospital_id, name --,tel_default?)
Create index on syn_tel(hospital_id, client_id, number)
Create index on syn_pet(hospital_id, family_id, state)
Create index on syn_species(hospital_id, species_id)
Change your query to:
SELECT ...
FROM syn_client sc
INNER JOIN syn_tel st ON sc.hospital_id = st.hospital_id AND sc.client_id = st.client_id
INNER JOIN syn_pet sp ON sc.hospital_id = sp.hospital_id AND sc.family_id = sp.family_id AND sp.state = 0
INNER JOIN syn_species ss ON sp.hospital_id = ss.hospital_id AND sp.species_id = ss.species_id
WHERE st.number IN ('01099999999', '01-099-999-999', 'ALL_OTHERS_FORMAT_YOU_ACCEPTS...')
AND trim(sc.name) = 'johndoe' --sc.name = 'johndoe' with standardize data input
AND sc.hospital_id = 'HOSPITALID999999' --not sp.hospital_id
ORDER BY tel_default DESC;
This query is generated by a doctrine2 QueryBuilder (the concat function takes only 2 parameters), and it takes 4 seconds.
SELECT COUNT(*) AS dctrn_count
FROM
(
SELECT DISTINCT id_4
FROM
(
SELECT 1 / LOCATE( ?, CONCAT( CONCAT( CONCAT(w0_.firstname, ' '),
CONCAT(w0_.lastname, ' ') ), w1_.fullname )
) AS sclr_0,
1 / LOCATE( ?, CONCAT( CONCAT( CONCAT(w0_.firstname, ' '),
CONCAT(w0_.lastname, ' ') ), w1_.shortname )
) AS sclr_1,
1 / LOCATE( ?, CONCAT( CONCAT( CONCAT(w0_.nickname, ' '),
CONCAT(w0_.lastname, ' ') ), w1_.fullname )
) AS sclr_2,
1 / LOCATE( ?, CONCAT( CONCAT( CONCAT(w0_.nickname, ' '),
CONCAT(w0_.lastname, ' ') ), w1_.shortname )
) AS sclr_3,
w0_.id AS id_4, w0_.slug AS slug_5, w0_.firstname AS firstname_6,
w0_.lastname AS lastname_7, w0_.nickname AS nickname_8,
w0_.gender AS gender_9, w0_.email AS email_10, w0_.email_checked AS email_checked_11,
w0_.title_en AS title_en_12, w0_.short_title AS short_title_13,
-- lots of stuff removed (see edit) --
w5_.biography_en AS biography_en_55, w5_.created AS created_56, w5_.updated AS updated_57, w6_.id AS id_58, w6_.web_text AS web_text_59, w6_.created AS created_60
FROM wmn_executive w0_
INNER JOIN wmn_company w1_ ON w0_.company_id = w1_.id
INNER JOIN wmn_industry w7_ ON w1_.industry_id = w7_.id
INNER JOIN wmn_location w2_ ON w1_.location_id = w2_.id
INNER JOIN wmn_country w3_ ON w2_.country_id = w3_.id
INNER JOIN wmn_city w4_ ON w2_.city_id = w4_.id
LEFT JOIN wmn_executive_link w5_ ON w0_.link_id = w5_.id
LEFT JOIN wmn_web_executive w6_ ON w0_.id = w6_.executive_id
WHERE w0_.original_id IS NULL
AND w0_.user_id IS NOT NULL
AND ( w0_.firstname LIKE ?
OR w0_.lastname LIKE ?
OR w0_.nickname LIKE ?
OR w1_.fullname LIKE ?
OR w1_.shortname LIKE ?
OR w0_.title_en LIKE ?
OR w0_.short_title LIKE ?
OR w7_.industry_name_en LIKE ?
OR w7_.industry_name_fr LIKE ?
OR w3_.country_name_en LIKE ?
OR w3_.country_name_fr LIKE ?
OR w4_.city_name LIKE ?
)
ORDER BY sclr_0 DESC, sclr_1 DESC, sclr_2 DESC, sclr_3 DESC ) dctrn_result
) dctrn_table
** The ORDER BY provides no benefit to the end result; remove it.
**
SELECT COUNT(*) AS dctrn_count
FROM
(
SELECT DISTINCT id_4
can be simplified to
SELECT COUNT(DISTINCT(id_4))
** All the items in the SELECT clause are not use, except for id_4; get rid of them.
**** Those 3 optimization might shrink the run time from 4.0s to maybe 3.9s.
And then you will say that this is not the real query, but merely a count?
If you are going to do a messy text scan like that, you need all those strings in one table. Better yet, all the strings concatenated together into one column in one table. This would be just for searching, not for display. Then make a FULLTEXT index on that column. This will solve the OR and LIKE '%...' problems. But how to get it back into doctrine2, I don't know.
I have a query that I am trying to convert to MySQL from MS SQL Server 2008. It runs fine on MSSQL,
I get the error
"Incorrect parameter count in the call to native function 'ISNULL'".
How do I solve this?
SELECT DISTINCT
dbo.`#EIM_PROCESS_DATA`.U_Tax_year,
dbo.`#EIM_PROCESS_DATA`.U_Employee_ID,
CASE
WHEN dbo.`#EIM_PROCESS_DATA`.U_PD_code = 'SYS033' THEN SUM(dbo.`#EIM_PROCESS_DATA`.U_Amount)
END AS PAYE,
CASE
WHEN dbo.`#EIM_PROCESS_DATA`.U_PD_code = 'SYS014' THEN SUM(dbo.`#EIM_PROCESS_DATA`.U_Amount)
END AS TOTALTAXABLE,
dbo.OADM.CompnyName,
dbo.OADM.CompnyAddr,
dbo.OADM.TaxIdNum,
dbo.OHEM.lastName + ', ' + ISNULL(dbo.OHEM.middleName, '') + '' + ISNULL(dbo.OHEM.firstName, '') AS EmployeeName
FROM
dbo.`#EIM_PROCESS_DATA`
INNER JOIN
dbo.OHEM ON dbo.`#EIM_PROCESS_DATA`.U_Employee_ID = dbo.OHEM.empID
CROSS JOIN
dbo.OADM
GROUP BY dbo.`#EIM_PROCESS_DATA`.U_Tax_year , dbo.`#EIM_PROCESS_DATA`.U_Employee_ID , dbo.OADM.CompnyName , dbo.OADM.CompnyAddr , dbo.OADM.TaxIdNum , dbo.OHEM.lastName , dbo.OHEM.firstName , dbo.OHEM.middleName , dbo.`#EIM_PROCESS_DATA`.U_PD_code
MySQL
SELECT DISTINCT
processdata.taxYear, processdata.empID,
CASE WHEN processdata.edCode = 'SYS033' THEN SUM (processdata.amount) END AS PAYE,
CASE WHEN processdata.edCode = 'SYS014' THEN SUM (processdata.amount) END AS TOTALTAXABLE,
company.companyName, company.streetAddress, company.companyPIN, employeemaster.lastName + ', ' + IFNULL(employeemaster.middleName, '')
+ ' ' + IFNULL(employeemaster.firstName, '') AS EmployeeName
FROM
processdata INNER JOIN
employeemaster ON processdata.empID = employeemaster.empID
CROSS JOIN company
GROUP BY processdata.taxYear, processdata.empID, company.companyName, company.streetAddress, company.companyPIN,
employeemaster.lastName, employeemaster.firstName, employeemaster.middleName, processdata.edCode
The MySQL equivalent of ISNULL is IFNULL
If expr1 is not NULL, IFNULL() returns expr1; otherwise it returns
expr2.
Maybe also look at SQL NULL Functions
The ISNULL from MySQL is used to check if a value is null
If expr is NULL, ISNULL() returns 1, otherwise it returns 0.
I would suggest that you switch to the ANSI standard function coalesce():
(dbo.OHEM.lastName + ', ' + coalesce(dbo.OHEM.middleName, '') + '' + coalesce(dbo.OHEM.firstName, '')
) AS EmployeeName
You could also make your query easier to read by including table aliases.
EDIT:
As a note, I seemed to have missed the direction of conversion. The MySQL query would use concat():
CONCAT(OHEM.lastName, ', ', coalesce(OHEM.middleName, ''),
coalesce(concat(' ', OHEM.firstName), '')
) AS EmployeeName
I was getting an error when running JUnit tests against a query which had ISNULL(value) with the error saying ISNULL needed two parameters. I fixed this by changing the query to have value is null and the code works the same while the tests now work.
In particular table there exists a field of SET type with specific legal values:
personType SET('CUSTOMER','SUPPLIER','EMPLOYEE', 'CONTRACTOR') NOT NULL
Is there any way to query MySQL to get a list of the valid values? In the MySQL interpreter I would just run DESCRIBE someTable; however if there is a more direct method that one could use programmatically without lots of parsing it would be nice.
Thanks.
Now, this simply freaks out, but it is MySQL-only and it works!
SELECT TRIM("'" FROM SUBSTRING_INDEX(SUBSTRING_INDEX(
(SELECT TRIM(')' FROM SUBSTR(column_type, 5)) FROM information_schema.columns
WHERE table_name = 'some_table' AND column_name = 'some_column'),
',', #r:=#r+1), ',', -1)) AS item
FROM (SELECT #r:=0) deriv1,
(SELECT ID FROM information_schema.COLLATIONS) deriv2
HAVING #r <=
(SELECT LENGTH(column_type) - LENGTH(REPLACE(column_type, ',', ''))
FROM information_schema.columns
WHERE table_name = 'some_table' AND column_name = 'some_column');
Just replace "some_table" and "some_column" for your specific table/column, and see the magic!
You will see a weird usage of "information_schema.COLLATIONS" - this is because we need a table there - any table - containing at least N rows, where N is the number of elements in your set.
SELECT
column_type
FROM
information_schema.columns
WHERE
table_name = 'some_table'
AND
column_name = 'some_column';
Returns:
column_type
------------------
set('this','that')
The function below returns an array containing all available options for SET with some parsing but not "lots of parsing"... :)
function get_set_values($table_name, $field_name)
{
$sql = 'DESCRIBE ' . $table_name . ' ' . $field_name;
$result = mysql_query($sql);
$row = mysql_fetch_array($result);
return str_getcsv( trim( substr( $row['Type'], 3 ), '()' ), ',', "'" );
}
Remember that in a set column you may have a combination of values or even an empty value (these are also valid).
I have the following mssql query that I found on the net that is supposed to help me with a complex mysql query that I have been struggling with for a few days now.
SELECT
inv.typeID AS typeID,
inv.typeName AS typeName,
invGroups.groupName AS groupName,
inv.published AS published,
inv.description AS description,
rankVal.valueFloat AS rank,
replace (( SELECT skills.attributeName AS [data()]
FROM dgmTypeAttributes tattr -- Link between skillbook and attributes
INNER JOIN dgmAttributeTypes skills ON (skills.attributeID = tattr.valueInt)
WHERE (tattr.typeID = inv.typeID)
AND (tattr.attributeID IN (180, 181)) -- Primary and secondary attributes
ORDER BY inv.typeID FOR xml path('')), ' ', ',') AS prisec,
replace (( SELECT RTRIM(CAST(inv2.typeID AS varchar)) + ',' AS [data()]
FROM (SELECT * FROM dgmTypeAttributes WHERE (attributeID in (182, 183, 184)) -- Pre-req skills 1, 2, and 3
AND (typeID = inv.typeID)) tattr2
INNER JOIN invTypes inv2 ON (tattr2.valueInt = inv2.typeID)
ORDER BY inv.typeID FOR xml path('')), ' ', ' ') AS prereq,
replace (( SELECT RTRIM(CAST(tattr2.valueInt AS varchar)) + ',' AS [data()]
FROM (SELECT * FROM dgmTypeAttributes WHERE (attributeID in (277, 278, 279)) AND (typeID = inv.typeID)) tattr2 -- Link between skillbook and attributes
ORDER BY inv.typeID FOR xml path('')), ' ', ' ') AS prereqlvl
FROM invTypes inv
INNER JOIN invGroups ON (inv.groupID = invGroups.groupID)
INNER JOIN dgmTypeAttributes rankVal ON (inv.typeID = rankVal.typeID)
WHERE invGroups.categoryID = 16 -- Skillbooks category
AND rankVal.attributeID = 275 -- Skill rank attribute
AND inv.published = 1
GROUP BY inv.typeID, inv.typeName, invGroups.groupName, inv.published, inv.description, rankVal.valueFloat
ORDER BY invGroups.groupName, inv.typeName
I am so so with mysql but I know nothing of mssql. Can somebody recommend a good method of converting this query that is low or now cost? I do not expect somebody to convert it for me as that would be asking too much, but some suggestions that would point me in the rite direction (aside from learning mssql lolz) would be very nice. Thank you for your time and patience.
'Recommendation: extract the data out of you MySQL database in a delimited file (csv) using the utf8 (unicode) character set. Import into SQL Server using bcp specifying utf8 with "-Jutf8" parameter and character mode "-c".' See this site. Also, there's a nice tool for this.
Those subqueries with FOR XML PATH('') seem to be used to concatenate strings1. See if you can replace them with GROUP_CONCAT in MySQL. The other bits seem to be standard SQL.