SQL Increment Column - mysql

I am trying to show one semesters aggregates in one column, the next semester's aggregates in the second column, and the third semesters aggregates in the third column. Also the real tables, I don't know how many status codes there are...
I have a semester table:
Id Semester
+----+----------+
| 1 Current |
| 2 Next |
| 3 2 Ahead |
+----+----------+
I have a simple project table:
Id Title Status termId
+----+--------+---------+--------+
| 1 A OK 1 |
| 2 B Bad 1 |
| 3 C OK 1 |
| 4 D Bad 2 |
| 5 E OK 2 |
| 6 F Bad 3 |
| 7 G OK 2 |
+----+--------+---------+--------+
This is the desired Output:
Status CurrentCount NextCount 2AheadCount
+---------+--------------+-----------+-------------+
| OK 2 1 0 |
| Bad 1 1 1 |
+---------+--------------+-----------+-------------+
What would you recommend I do to be able to achieve this?

You can use conditional aggregation with group by:
select status,
sum(case when termId = 1 then 1 else 0 end) CurrentCount,
sum(case when termId = 2 then 1 else 0 end) NextCount,
sum(case when termId = 3 then 1 else 0 end) 2AheadCount
from project
group by status

Related

MySQL pivoting a table while getting the related values

I am a very beginner to mysql and sql in general. None of my search results brought the solution I am looking for. I have a database and need to format the output like in the example below. Unfortunately I can't change the database anymore since it's an old project of another employee.
The source table:
CustomerID serviceID ratingID employeeID
1 1 A 1
1 2 B 5
1 3 B 2
1 4 A 3
2 1 A 1
2 2 C 5
2 3 D 2
2 4 C 3
3 1 T 1
3 2 O 5
3 3 T 2
3 4 O 3
Each Customer has four entries, one for each service. They have set ratings and employees.
The output that I need (employeeID can be ignored):
CustomerID ServiceID=1 ServiceID=2 ServiceID=3 ServiceID=4
1 A B B A
2 A C D C
3 T O T O
I found solutions with group_concat(if())... in the select statement and grouping by customerID, which only brought the values of "1" in each field and not the related values. I tried tying lose selects together with union or subselects but none gave the result I am looking for. Does anybody have a beginner friendly help?
For this sample data you can use conditional aggregation:
select CustomerID,
max(case when serviceID = 1 then ratingID end) `ServiceID=1`,
max(case when serviceID = 2 then ratingID end) `ServiceID=2`,
max(case when serviceID = 3 then ratingID end) `ServiceID=3`,
max(case when serviceID = 4 then ratingID end) `ServiceID=4`
from tablename
group by CustomerID
See the demo.
Results:
| CustomerID | ServiceID=1 | ServiceID=2 | ServiceID=3 | ServiceID=4 |
| ---------- | ----------- | ----------- | ----------- | ----------- |
| 1 | A | B | B | A |
| 2 | A | C | D | C |
| 3 | T | O | T | O |

How to select all distinct values from column A which have certain value in column B in mySQL

Introduction to the problem:
I am working on barcode tracking system which has 5 scanning stations placed along the production line starting from StationID 1 and the final station is StationID 5. Each barcode corresponds to an item that is being manufactured along the line. Some items, like the one with Barcode 95821 don't go through preprocessing and therefore are not scanned by StationID 1.
The problem:
I am having hard time building a query that shows all barcodes that have passed StationID 2 but haven't reached StationID 3, or 4 or 5:
List all barcodes that:
- have passed both StationID 1 and StationID 2 (like 95813,95814,95823, 95824)
- have passed StationID 2 only (like 95821) since it doesn't go through preprocessing
Here is an example table called scanstations:
+---------+-----------+----------+
| Barcode | StationID | Time |
+---------+-----------+----------+
| 95813 | 1 | 11:30:01 |
| 95814 | 1 | 11:30:05 |
| 95823 | 1 | 11:30:10 |
| 95824 | 1 | 11:30:20 |
| 95821 | 2 | 11:35:10 |
| 95813 | 2 | 11:40:01 |
| 95814 | 2 | 11:40:02 |
| 95823 | 2 | 11:30:10 |
| 95824 | 2 | 11:30:20 |
| 95813 | 3 | 11:40:01 |
| 95814 | 3 | 11:40:02 |
| 95813 | 4 | 11:45:10 |
| 95814 | 4 | 11:45:30 |
| 95813 | 5 | 11:47:20 |
+---------+-----------+----------+
The expected result set:
+---------+
| Barcode |
+---------+
| 95821 |
| 95823 |
| 95824 |
+---------+
I use the following query to list all barcodes that have been scanned by StationID 1 and haven't been scanned by any other station.
SELECT
Barcode
FROM
scanstations s
GROUP BY Barcode
HAVING SUM(CASE
WHEN s.StationID = 1 THEN 0
ELSE 1
END) = 0
I don’t understand why everyone is trying to complicate, it’s not easier to use the query
SELECT Barcode FROM scanstations GROUP BY Barcode HAVING MAX(StationID) = 2
If you want to list all barcodes that have been scanned by StationID 1 and haven't been scanned by any other station, use HAVING MAX(StationID) = 1.
If you want to list all barcodes that have passed both StationID 1 and StationID 2, use HAVING MAX(StationID) = 2 AND COUNT(*) = MAX(StationID)
If you want to list all barcodes that have passed StationID 2 only, use HAVING MAX(StationID) = 2 AND COUNT(*) < MAX(StationID)
You could try select the barcode that have a number of station different form the max station number eg:
select barcode, max(StationID), count(*)
from scanstations
GROUP BY barcode
having max(StationID) <> count(*)
Following your intention you can test scores here are your queries.
(query 1)
Shows all barcodes that have passed StationID 2 but haven't reached StationID 3, or 4 or 5:
SELECT
Barcode
,SUM(CASE
WHEN s.StationID = 1 THEN -99999999
WHEN s.StationID = 2 THEN 10
WHEN s.StationID = 3 THEN 1
WHEN s.StationID = 4 THEN 1
WHEN s.StationID = 5 THEN 1
ELSE 0 END) as sumcheck
FROM
scanstations s
GROUP BY Barcode
HAVING sumcheck >= 10 and sumcheck <= 12
(query 2)
List all barcodes that: - have passed both StationID 1 and StationID 2 (like 95813,95814,95823, 95824)
SELECT
Barcode
,SUM(CASE
WHEN s.StationID = 1 THEN 10
WHEN s.StationID = 2 THEN 10
WHEN s.StationID = 3 THEN -99999999
WHEN s.StationID = 4 THEN -99999999
WHEN s.StationID = 5 THEN -99999999
ELSE 0 END) as sumcheck FROM
scanstations s
GROUP BY Barcode HAVING sumcheck = 20
(query 3)
List all barcodes that: - have passed StationID 2 only (like 95821) since it doesn't go through preprocessing
SELECT
Barcode
,SUM(CASE
WHEN s.StationID = 1 THEN -99999999
WHEN s.StationID = 2 THEN 10
WHEN s.StationID = 3 THEN -99999999
WHEN s.StationID = 4 THEN -99999999
WHEN s.StationID = 5 THEN -99999999
ELSE 0 END) as sumcheck
FROM
scanstations s
GROUP BY Barcode
HAVING sumcheck = 10
i edited because it was not so clear.
Maybe they aren't so short as you expected but i think they're easy to understand ( if you have basis of sql) and make more sense applied to your logic. Sometimes it's not the best way trying to compress because if you do maintenance in the future you'll have to reinterpret and it's not always a good choice.
Maybe you can try this:
SELECT BARCODE,S_id
FROM
(SELECT Barcode,GROUP_CONCAT(stationID ORDER BY stationID) S_id
FROM scanstations s
GROUP BY Barcode) a
WHERE SUBSTRING_INDEX(S_ID,',',1) <> 1
OR SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',3),',',-1) <> 3
GROUP_CONCAT in subquery to return all stationID linked with the barcode:
Then on the outer query, find the first & third value of the GROUP_CONCAT result that doesn't match with 1 OR 3.
Edit
I will attempt to explain this part as how I understand it:
WHERE SUBSTRING_INDEX(S_ID,',',1) <> 1
OR SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',3),',',-1) <> 3
There are three components here,
SUBSTRING_INDEX (ColumnName,separator,position)
the column we are using is S_Id which consist of the data value as 1,2,3
the separator in the data value of 1,2,3 is the comma ','
and the position of value separated by comma that we want to get is 1.
Therefore, SUBSTRING_INDEX( S_ID , ',' , 1 ) means "Get one value from the beginning of column S_Id separated by comma`.
For the second one SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',3),',',-1), the SUBSTRING_INDEX appear twice because the first one inside SUBSTRING_INDEX(S_ID,',',3) returns 1,2,3 is to get three value of column S_Id separated by comma. Then the second SUBSTRING_INDEX is basically SUBSTRING_INDEX('result of first SUBSTRING_INDEX', 'separate by comma' , '-1 means to take first value from the back').
<> symbol
this can be replaced with NOT IN () or != which all means 'not same'
OR operator
I don't usually use OR because in my previous queries I always get strange results (it turns out MySQL has some bug - using MySQL 4.1). In this test however I uses MariaDB 10.3 and so far it seems to be working fine. "The MySQL OR Condition is used to test two or more conditions where records are returned when any one of the conditions are met" ref: https://www.techonthenet.com/mysql/or.php .
You can add another two OR in the query like so:
+---------+-----------+----------+
| Barcode | StationID | timest |
+---------+-----------+----------+
| 95813 | 1 | 11:30:01 |
| 95814 | 1 | 11:30:05 |
| 95823 | 1 | 11:30:10 |
| 95824 | 1 | 11:30:20 |
| 95821 | 2 | 11:35:10 |
| 95813 | 2 | 11:40:01 |
| 95814 | 2 | 11:40:02 |
| 95823 | 2 | 11:30:10 |
| 95824 | 2 | 11:30:20 |
| 95813 | 3 | 11:40:01 |
| 95814 | 3 | 11:40:02 |
| 95813 | 4 | 11:40:10 |
| 95813 | 5 | 11:40:20 |
| 95814 | 4 | 11:40:30 |
+---------+-----------+----------+
SELECT BARCODE,S_id FROM
(SELECT
Barcode,GROUP_CONCAT(stationID ORDER BY stationID) S_id
FROM
scanstations s
GROUP BY Barcode) a
WHERE SUBSTRING_INDEX(S_ID,',',1) <> 1
OR SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',2),',',-1) <> 2
OR SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',3),',',-1) <> 3
OR SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',4),',',-1) <> 4
OR SUBSTRING_INDEX(SUBSTRING_INDEX(S_ID,',',5),',',-1) <> 5;
Which return the following on my test server:
+---------+---------+
| BARCODE | S_id |
+---------+---------+
| 95814 | 1,2,3,4 |
| 95821 | 2 |
| 95823 | 1,2 |
| 95824 | 1,2 |
+---------+---------+

SQL- Count number of 'similar' rows and place results in another table

I have a table called of 'orders' that records an order id for a meal, a person id for the person who ordered the meal and the meal (which can be 1, 2, or 3)
orders
oid | pid | meal
________________
1 | 1 | 2
2 | 1 | 3
3 | 3 | 1
4 | 5 | 2
5 | 5 | 2
6 | 5 | 1
I want to generate a result that will allow me to view the number of orders for each meal for each pid:
pid | meal1 | meal2 | meal3
____________________________
1 | 0 | 1 | 1
3 | 1 | 0 | 0
5 | 0 | 2 | 1
I am not an SQL expert, but I was thinking that I would first group the list by pid, then by meal. But how would I do the counting? I am truly lost.
Basically you need to first create multiple columns for the different meals using case when, then you can group by pid and sum it up
Select pid, sum(case when meal =1 then 1 else 0 end) as meal1,
sum(case when meal =2 then 1 else 0 end) as meal2,
sum(case when meal =3 then 1 else 0 end) as meal3
from Table
group by pid

MySQL Crosstab aggregation with 2 conditions

I have a query which creates a crosstab. The results are a count of the txn_id for branda, and the count of txn_id for brandb.
The txn_id is NOT UNIQUE. This is an example of the transactions table.:
txn_id | nationality_id | sku | sales | units
1 | 1 | 1 | 20 | 2
1 | 1 | 2 | 15 | 1
2 | 4 | 1 | 20 | 2
3 | 2 | 1 | 10 | 1
4 | 3 | 2 | 15 | 1
5 | 4 | 1 | 10 | 1
There are 2 other tables (products) - (sku, brand, product name), and (nationalities) - (nationality_id, nationality).
I would like to add a third column which gets me the count of txn_id where BOTH brands are purchased
The output should be
nationality | branda | brandb | combined
1 | 1 | 1 | 1
2 | 1 | 0 | 0
3 | 0 | 1 | 0
4 | 2 | 0 | 0
Current query.
SELECT
nationalities.nationality,
COUNT((CASE brand WHEN 'branda' THEN txn_id ELSE NULL END)) AS branda,
COUNT((CASE brand WHEN 'brandb' THEN txn_id ELSE NULL END)) AS brandb
<I want my 3rd column here>
FROM
transaction_data
INNER JOIN
products USING (sku)
INNER JOIN
nationalities USING (nationality_id)
GROUP BY nationality
ORDER BY branda DESC
LIMIT 20;
I have tried using:
COUNT((CASE brand WHEN 'brandb' OR 'brandb' THEN txn_id ELSE NULL END)) AS combined - however this obviously returns too many (returns branda or brandb regardless of whether they were purchased together). I know I can't use AND, because obviously no single cell is going to be both branda AND brandb.
I have also tried using:
COUNT((CASE brand WHEN IN('branda', 'brandb') THEN txn_id ELSE NULL END)) AS combined - However this isn't valid syntax.
I feel that I should be using a HAVING clause, but I'm not sure how this would work in the column list.
I think you are going to need two levels of aggregation:
SELECT n.nationality,
sum(branda), sum(brandb), sum(branda * brandb)
FROM (SELECT t.txn_id, n.nationality,
MAX(CASE brand WHEN 'branda' THEN 1 ELSE 0 END) AS branda,
MAX(CASE brand WHEN 'brandb' THEN 1 ELSE 0 END) AS brandb
FROM transaction_data t INNER JOIN
products p
USING (sku) INNER JOIN
nationalities n
USING (nationality_id)
GROUP BY t.txn_id, n.nationality
) tn
GROUP BY n.nationality
ORDER BY max(txn_id) DESC
LIMIT 20;

Mysql Inner Join based on condition

I have two tables that I'm attempting to retrieve specific information from (duh I know). The first table seasons is semi-static data storage and the second table users_cards is used to store user choices.
The result I am hoping to achieve would go through each season, assign a "card_total" = 10 for seasons 1-3 and 11 for each season moving forward. The result would look something similar to:
SEASON_ID | TOTAL |
------------ ------------
1 | 123
2 | 234
3 | 345
4 | 456
The abbreviated & pertinent columns / sample data is as follows:
# `seasons`:
ID | ACTIVE | COMPLETE |
---- ----------- ---------------
1 | 0 | 1
2 | 0 | 1
3 | 0 | 1
4 | 1 | 0
5 | 0 | 0
# `users_cards`
# DESC: this table can store up to 10 choices per user for seasons 1-3
# and up to 11 choices for every season thereafter.
USER_ID | SEASON_ID |
------------ ---------------
1 | 1
1 | 1
2 | 1
2 | 1
1 | 2
1 | 2
1 | 2
I've played around with a few variations of this query but nothing seems to be doing the trick. This query returns the total count for each season but it's not based off of the "card_total" I mentioned above.
SELECT
c.season_id AS season_id,
c.card_total AS card_total,
c.total AS total
FROM seasons s
INNER JOIN (
SELECT
uc.season_id,
COUNT(DISTINCT(user_id)) AS total,
CASE WHEN
uc.season_id = 1
OR uc.season_id = 2
OR uc.season_id = 3
THEN 10
ELSE 11
END AS card_total
FROM users_cards uc
GROUP BY uc.season_id
) AS c ON c.season_id = s.id
WHERE s.is_active = 1
OR s.is_complete = 1
Put SUM() around your CASE...END.