MySQL - Separating data within 1 column into 3 separate columns for a report - mysql

We have a report to track how many edits our sales reps are doing. The current query to pull the number of edits on all 3 pages is below. We didn't care before which page they were making edits on, but now we want to see which pages they are make those edits on.
We are wanting to have 3 different columns: bhns, hns, chns, show up on the report and need to modify this query to show the different columns. So, split the 1 column (customer_edits) into 3 columns base on page.
SELECT
count( `database2`.`sales_edits`.`id` ) AS `customer_edits`,
`database2`.`sales_edits`.`rep` AS `rep`
FROM
`database2`.`sales_edits`
WHERE
((
cast( `database2`.`sales_edits`.`date` AS date ) = curdate())
AND ((
`database2`.`sales_edits`.`page` = 'chs'
)
OR ( `database2`.`sales_edits`.`page` = 'chns' )
OR ( `database2`.`sales_edits`.`page` = 'bhns' )))
GROUP BY
`database2`.`sales_edits`.`rep`
sales_edit table:

It looks like you want conditional aggregation:
select
rep,
sum(page = 'chs') customer_edits_chs,
sum(page = 'chns') customer_edits_chns,
sum(page = 'bhns') customer_edits_bhns
from database2.sales_edits
where date = current_date and page in ('chs', 'chns', 'bhns')
group by rep
Your original code looks more complicated that it needs to:
no need to prefix all columns with the schema and table name - a single table comes into play anyway (and if you had more than one, then you should use table aliases to shorten the code)
the date casting seems unecessary; MySQL happily understands any string in 'yyyy-mm-dd' format as a date
the repeated or conditions can be shortened with in
it is probably unneeded to surround all identifiers with backticks, while they do not contain special characters

Related

Query to find entries and transpose

I've got a machine log available in an SQL table. I can do a bit in SQL, but I'm not good enough to process the following:
In the data column there are entries containing "RUNPGM: Recipe name" and "RUNBRKPGM: Recipe name"
What I want is a view containing 4 columns:
TimeStamp RUNPGM
TimeStamp RUNBRKPGM
Recipe Name
Time Difference in seconds
There is a bit of a catch:
Sometimes the machine logs an empty RUNBRKPGM that should be ignored
The RUNBRKPGM is sometimes logged with an error message. This entry should also be ignored.
It's always the RUNBRKPGM entry with just the recipe name that's the actual end of the recipe.
NOTE: I understand this is not a full/complete answer, but with info available in question as of now, I believe it at least helps give a starting point since this is too complicated (and formatted) to put in the comments:
If Recipe is everything in the DATA field except the 'RUNPGM = ' part you can do somethign similar to this:
SELECT
-- will give you a col for TimeStamp for records with RUNPGM
CASE WHEN DATA LIKE 'RUNPGM%' THEN TS ELSE '' END AS RUNPGM_TimeStamp,
-- will give you a col for TimeStamp for records with RUNBRKPGM
CASE WHEN DATA LIKE 'RUNBRKPGM%' THEN TS ELSE '' END AS RUNBRKPGM_TimeStamp,
-- will give you everything after the RUNPGM = (which I think is the recipe you are referring to)
CASE WHEN DATA LIKE 'RUNPGM%' THEN REPLACE(DATA, 'RUNPGM = ', '' AS RUNPGM_Recipe,
-- will give you everything after the RUNBRKPGM = (which I think is the recipe you are referring to)
CASE WHEN DATA LIKE 'RUNBRKPGM:%' THEN REPLACE(DATA, 'RUNBRKPGM = ', '' AS RUNPGM_Recipe
FROM TableName
Im not sure what columns you want to get the Time Difference on though so I dont have that column in here.
Then if you need to do additional logic/formatting on the columns once they are separated you can put the above in a sub select.
As a first swing, I'd try the following:
Create a view that uses string splitting to break the DATA column into a its parts (e.g. RunType and RecipeName)
Create a simple select that outputs the recipe name and tstamp where the runtype is RUNPGM.
Then add an OUTER APPLY:
Essentially, joining onto itself.
SELECT
t1.RecipeName,
t1.TimeStamp AS Start,
t2.TimeStamp AS Stop
--date func to get run time, pseudo DATEDIFF(xx,t1.TimeStamp, t2.TimeStamp) as RunTime
FROM newView t1
OUTER APPLY ( SELECT TOP ( 1 ) *
FROM newView x
WHERE x.RecipeName = t1.RecipeName
AND RunType = 'RUNBRKPGM'
ORDER BY ID DESC ) t2
WHERE t1.RunType = 'RUNPGM';

SQL Combining fields in one table

There is a script that displays two tables. Names of type String and counting of type Long.
How can I combine the same fields “Out of stock” in one field and separate fields “marriage”, “re-sorting” in one field “marriage / re-sorting” in one table. In doing so, save the types of two tables and get the combined values ​​in the new fields. And how not to display extra fields, for example, a form for employees, etc.
I know that you can use the CASE, WHEN, THEN structure. But I don’t understand how to correctly describe it in my script.
SELECT rl.reason AS reject_reason, COUNT(*)
FROM
mp.reservation_log AS rl
JOIN
mp.store AS st ON rl.store_id = st.md_id
JOIN mp.order_item oi ON oi.reserve_id=rl.reservation_id
JOIN mp.sku s ON s.id=oi.item_id
JOIN mp.product p ON p.id=s.product_id
WHERE rl.created_at > DATE(NOW()) - INTERVAL 1 MONTH AND rl.is_successful=0
GROUP BY rl.reason;
Table example:
table example
It seems like you want to combine several reasons in the same group.
You can use a case expression as a no-aggregated column for this, like so:
select
case
when r1.reason in ('marriage', 're-sorting') then 'marriage/re-sorting'
else r1.reason
end real_reason,
count(*) cnt
from ...
where rl.created_at > current_date - interval 1 month and rl.is_successful = 0
group by real_reason
Side note: DATE(NOW()) is better written CURRENT_DATE .

MySQL calculation within query

My current query looks like this:
SELECT * FROM `Entrys` WHERE `Timestamp` > 1469308755 AND `Timestamp` < 1469308765 AND (`Exchange` = 1 OR `Exchange` = 2) AND `Market` = 1
It gives me the following result:
For each timestamp value (1469308756 and 1469308761) , I want to calculate the spreads between the prices like this:
For each timestamp-value
For each Exchange
For each Other-Exchange
Divide Exchanges price where type = 2 by Other-Exchanges price where type = 1
This is the best I can explain it. If you don't understand it please leave a comment and I'll try to explain it better.
With the above data it should work like this:
Format: EnId.Field
943.Price / 940.Price
944.Price / 939.Price
961.Price / 985.Price
962.Price / 957.Price
The calculated numbers should be the output of the query.
My SQL skills are way to low for that. Is it even possible to do such calculation inside a single query?
You need to use a JOIN. A JOIN is useful when you want data from two different rows to be referenced in the same expression (either an expression in the WHERE clause or an expression in the select-list).
So you need to join a row to another row that satisfies these conditions:
the same timestamp
a different exchange
the first row is type=2 and the second row is type=1
When you're doing a self-join like this one, you must use table aliases. I will use "numerator" and "denominator" for the two rows.
SELECT numerator.timestamp, numerator.price / denominator.price AS spread
FROM Entrys AS numerator
JOIN Entries AS denominator ON numerator.timestamp=denominator.timestamp
AND numerator.exchange <> denominator.exchange
AND numerator.type = 2 AND denominator.type = 1;

Reduce MySQL Code down or combine SELECT Statements

I have made a few relations to do with a banking database system.
this is my current code. The table has
SELECT COUNT(AccountType) AS Student_Total FROM Account
WHERE AccountType ='Student'
and SortCode = 00000001;
SELECT COUNT(AccountType) AS Student_Total FROM Account
WHERE AccountType ='Student'
and SortCode = 00000002;
SELECT COUNT(AccountType) AS Student_Total FROM Account
WHERE AccountType ='Student'
and SortCode = 00000003;
the rest of the code is a duplicate of this part with the next type of 'Account' and looping back through sortcode's 1-3 again.
I was wondering if there was a more elegant way of producing this. I need to count the number of student, current and saver accounts for each bank.
Or is there a way to combine lots of selects together to make a neat table?
That's what GROUP BY is for!
SELECT SortCode,COUNT(AccountType) AS Student_Total FROM Account
WHERE AccountType ='Student'
GROUP BY SortCode;
UPDATE:
You can also GROUP BY with multiple grouping fields:
SELECT SortCode,AccountType,COUNT(AccountType) AS Student_Total FROM Account
GROUP BY SortCode,AccountType;
You could also apply a PIVOT approach to this query to always return a single row and know the fixed-final columns of the result set. However, applying a group by allows for more flexibility of returned rows, especially if you have a large amount of individual things you are trying to tally up.
select
A.AccountType,
SUM( IF( A.SortCode = 1, 1, 0 )) as SortCode1Cnt,
SUM( IF( A.SortCode = 2, 1, 0 )) as SortCode2Cnt,
SUM( IF( A.SortCode = 3, 1, 0 )) as SortCode3Cnt
from
Account A
where
A.AccountType = 'Student'
AND A.SortCode IN ( 1, 2, 3 )
group by
A.AccountType
Note... it appears your sort code is a numeric as you have no quotes around indicating a character string. So, all the leading zeros are irrelevant. And if you were only doing based on a single Account Type, you don't even need the leading Account Type column and can remove the group by too.

How can I sanitize my DB from these duplicates

I have a table with the following fields:
id | domainname | domain_certificate_no | keyvalue
An example for the output of a select statement can be as:
'57092', '02a1fae.netsolstores.com', '02a1fae.netsolstores.com_1', '55525772666'
'57093', '02a1fae.netsolstores.com', '02a1fae.netsolstores.com_2', '22225554186'
'57094', '02a1fae.netsolstores.com', '02a1fae.netsolstores.com_3', '22444356259'
'97168', '02aa6aa.netsolstores.com', '02aa6aa.netsolstores.com_1', '55525772666'
'97169', '02aa6aa.netsolstores.com', '02aa6aa.netsolstores.com_2', '22225554186'
'97170', '02aa6aa.netsolstores.com', '02aa6aa.netsolstores.com_3', '22444356259’
I need to sanitize my db such that: I want to remove the domain names that have repeated keyvalue for the first domain_certificate_no (i.e, in this example, I look for the field domain_certificate_no: 02aa6aa.netsolstores.com_1, since it is number 1, and has repeated value for the key, then I want to remove the whole chain which is 02aa6aa.netsolstores.com_2 and 02aa6aa.netsolstores.com_3 and this by deleting the domain name that this chain belongs to which is 02aa6aa.netsolstores.com.
How can I automate the checking process for the whole DB. So, I have a query that checks any domain name in the pattern ('%.%.%) EDIT: AND they have share domain name (in this ex: netsolstores.com) , if it finds cert no. 1 that belongs to this domain name has a repeated key value, then delete. Otherwise no. Please, note tat, it is ok for domain_certificate_no to have repeated value if it is not number 1.
EDIT: I only compare the repeated valeues for the same second level domain name. Ex: in this question, I compare the values that share the domain name: .netsolstores.com. If I have another domain name, with sublevel domains, I do the same. But the point is that I don't need to compare the whole DB. Only the values with shared domain name (but different sub domain).
I'm not sure what happens with '02aa6aa.netsolstores.com_1' in your example.
The following keeps only the minimum id for any repeated key:
with t as (
select t.*,
substr(domain_certificate_no,
instr(domain_certificate_no, '_') + 1, 1000) as version,
left(domain_certificate_no, instr(domain_certificate_no, '_') - 1) as dcn
from t
)
select t.*
from t join
(select keyvalue, min(dcn) as mindcn
from t
group by keyvalue
) tsum
on t.keyvalue = tsum.keyvalue and
t.dcn = tsum.mindcn
For the data you provide, this seems to do the trick. This will not return the "_1" version of the repeats. If that is important, the query can be pretty easily modified.
Although I prefer to be more positive (thinking about the rows to keep rather than delete), the following should delete what you want:
with t as (
select t.*,
substr(domain_certificate_no,
instr(domain_certificate_no, '_') + 1, 1000) as version,
left(domain_certificate_no, instr(domain_certificate_no, '_') - 1) as dcn
from t
),
tokeep as (
select t.*
from t join
(select keyvalue, min(dcn) as mindcn
from t
group by keyvalue
) tsum
on t.keyvalue = tsum.keyvalue and
t.dcn = tsum.mindcn
)
delete from t
where t.id not in (select id from tokeep)
There are other ways to express this that are possibly more efficient (depending on the database). This, though, keeps the structure of the original query.
By the way, when trying new DELETE code, be sure that you stash a copy of the table. It is easy to make a mistake with DELETE (and UPDATE). For instance, if you leave out the WHERE clause, all the rows will disappear, after the long painful process of logging all of them. You might find it faster to simply select the desired results into a new table, validate them, then truncate the old table and re-insert them.