Using a foreach/while/loop with sql query - mysql

I'm using the following query to create a view. It's currently only grabbing data from two different tables, subscriptions and subscriptionitems.
For each subscription, I want to grab the item data and output it in the column, the concat function is grabbing one row at the moment and outputting the data in the correct format.
The problem I have is that a subscription can have multiple items, so I need to grab each one and tie it to the correct subscription via the where statement.
How can I do that?
I've read about using UNION ALL, is that the right direction to go?
CREATE VIEW Sub_Products AS
(
SELECT
i.subscription_id as "Subscription ID",
concat('product_id:',i.product_id,'|quantity:',i.quantity,'|total:',(i.unit_price * i.quantity),'|meta:|tax:0;') as "Products"
FROM subscriptions s, subscriptionitems i, customerdata c
WHERE s.id = i.subscription_id
AND i.active = 1
);
So as an example of the output - any with the same subscription id should be combined and the products should be output in the same row.
So the subscription 217 should have in the products column "product_id:253|quantity:1|total:2.34|meta:|tax:0;product_id:252|quantity:1|total:2.43|meta:|tax:0;"
Sample data from the subscriptionitems table:
id
subscription_id
customer_id
product_id
quantity
active
unit_price
556
230
184
262
1
0
2.79
8100
230
184
262
1
1
2.79
555
230
184
260
1
0
2.52
This is my attempt:
CREATE VIEW Sub_Products AS
(
SELECT
i.subscription_id as "Subscription ID",
GROUP_CONCAT('product_id:',i.product_id,'|quantity:',i.quantity,'|total:',(i.unit_price * i.quantity),'|meta:|tax:0;') as "Products"
FROM subscriptions s, subscriptionitems i, customerdata c
WHERE s.id = i.subscription_id
AND i.active = 1
GROUP BY i.subscription_id
);

Never use commas in the FROM clause. Always use proper, explicit, standard, readable JOIN syntax.
If you did so, you would probably notice that there is no JOIN condition for customerdata. In fact, that table is not used at all. And neither is subscriptions.
I would suggest
SELECT i.subscription_id ,
GROUP_CONCAT('product_id:', i.product_id,
'|quantity:', i.quantity,
'|total:', (i.unit_price * i.quantity),
'|meta:|tax:0;'
) as Products
FROM subscriptionitems i
WHERE i.active = 1 ;
GROUP BY i.subscription_id;
Note that I fixed the column names so no escaping is needed either.

Related

What is the correct way to join two complex mysql select statements?

I am able to pull out two lists from my tables. One shows all the units for each student in each cohort. Another shows if all the parts of each unit have been submitted, for a particular student's work in a specific cohort. I want to join the lists, so that I can see who has submitted (or not) each part of each unit, for each student in each cohort.
cohort_units:
cohort_id unit part
235 ABC A
235 ABC B
246 DEF A
246 DEF B
246 DEF C
cohort_students:
user_id cohort_id
21 235
24 235
43 235
53 246
assignments:
user_id cohort_id unit draft1recdt
21 235 ABCA 2023-01-03
21 235 ABCB NULL
24 235 ABCA 2023-02-01
24 235 ABCB 2023-02-02
This pulls a list of units with the user id and cohort id.
SELECT cohort_students.user_id,
cohort_units.unit,
cohort_units.cohort_id
FROM cohort_units
LEFT JOIN cohort_students
ON cohort_units.cohort_id = cohort_students.cohort_id
GROUP BY cohort_units.unit,cohort_students.user_id
ORDER BY cohort_students.user_id;
result:
user_id unit cohort_id
21 ABC 235
24 ABC 235
43 ABC 235
53 DEF 236
This returns a row IF there are more parts to an assignment than parts that have been submitted, for each unit that each student in each cohort should have completed given the cohort id, user id and unit name.
SELECT GROUP_CONCAT(CASE WHEN draft1recdt IS NOT NULL THEN draft1recdt END) AS drafts,
(LENGTH(GROUP_CONCAT(DISTINCT draft1recdt))-LENGTH(REPLACE(GROUP_CONCAT(DISTINCT draft1recdt), ',', '')))+1 as numDrafts,
cohort_units.unit,
GROUP_CONCAT(cohort_units.part) as parts,
(LENGTH(GROUP_CONCAT(DISTINCT cohort_units.part))-LENGTH(REPLACE(GROUP_CONCAT(DISTINCT cohort_units.part), ',', '')))+1 as numParts
FROM assignments
LEFT JOIN cohort_units
ON assignments.cohort_id = cohort_units.cohort_id
AND assignments.unit = CONCAT(cohort_units.unit,cohort_units.part)
WHERE assignments.cohort_id = 235
AND cohort_units.unit = 'ABC' AND assignments.user_id = 21
GROUP BY cohort_units.unit
HAVING numParts > numDrafts;
How do I make the second select statement part of the first, using the three columns on the first select statement as the joining information?
I want to run the second query on every result from the first query. Using the data above, I would expect to pull out user id 21 as they have only submitted one part of a two part unit.
user_id unit cohort_id parts numParts numDrafts
21 ABC 235 A,B 2 1
Is this a JOIN? Or a SUBQUERY?
(For what it's worth, I believe cohort is an accepted term in various social science disciplines.)
Your problem would be made easier if your assignments table had a part column matching the part table in your cohort_units table. So let's start with a subquery to generate a virtual table with that column present.
SELECT assignments.user_id, assignments.cohort_id,
cohort_units.unit, cohort_units.part,
assignments.draft1recdt
FROM assignments
JOIN cohort_units
ON assignments.unit = CONCAT(cohort_units.unit, cohort_units.part)
We'll use this subquery in place of assignments moving forward. This is a bit of a kludge, but it will make later work cleaner.
Next we need the number of parts in each unit. That's a simple aggregate:
SELECT COUNT(*) num_parts,
cohort_id,
unit
FROM cohort_units
GROUP BY cohort_id, unit
We can organize our query using common table expressions, like so.
WITH completed AS (
SELECT assignments.user_id, assignments.cohort_id,
cohort_units.unit, cohort_units.part,
assignments.draft1recdt
FROM assignments
JOIN cohort_units
ON assignments.unit = CONCAT(cohort_units.unit, cohort_units.part)
),
partcount AS (
SELECT COUNT(*) num_parts,
cohort_id,
unit
FROM cohort_units
GROUP BY cohort_id, unit
)
SELECT completed.user_id, cohort_units.cohort_id, cohort_units.unit,
GROUP_CONCAT(completed.part) parts,
COUNT(*) completed_parts,
partcount.num_parts
FROM cohort_units
JOIN partcount
ON cohort_units.cohort_id = partcount.cohort_id
AND cohort_units.unit = partcount.unit
JOIN completed
ON completed.cohort_id = cohort_units.cohort_id
AND completed.unit = cohort_units.unit
AND completed.part = cohort_units.part
GROUP BY completed.user_id, cohort_units.cohort_id, cohort_units.unit, num_parts
HAVING COUNT(*) < partcount.num_parts
Here's a fiddle. https://dbfiddle.uk/FvGkiAnl
One of the tricks to this is the separate aggregate to get the part counts.

how to select data from multiple table with variable condition | MySQL

I have two tables in the datbase to store client basic info (name, location, phone number) and another table to store client related transactions (date_sub, profile_sub,isPaid,date_exp,client_id) and i have an html table to view the client basic info and transaction if are available, my problem that i can't get a query to select the client info from table internetClient and from internetclientDetails at the same time, because query is only resulting when client have trans in the detail table. the two table fields are as follow:
internetClient
--------------------------------------------------------
id full_name location phone_number
-------------------------------------------------------
4 Joe Amine beirut 03776132
5 Mariam zoue beirut 03556133
and
internetclientdetails
--------------------------------------------------------------------------
incdid icid date_sub date_exp isPaid sub_price
----------------------------------------------------------------------------
6 4 2018-01-01 2018-01-30 0 2000
7 5 2017-01-01 2017-01-30 0 1000
8 4 2018-03-01 2018-03-30 1 50000
9 5 2018-05-01 2019-05-30 1 90000
// incdid > internetClientDetailsId
// icid> internetClientId
if client have trans in orderdetails, the query should return value like that:
client_id full_name date_sub date_exp isPaid sub_price
-------------------------------------------------------------------------------------
4 Joe Amine 2018-03-01 2018-03-30 1 50000
5 Mariam zoue 2018-05-01 2019-05-30 1 90000
else if the client has no id in internetOrederDetails
--------------------------------------------------------
icid full_name location phone_number
-------------------------------------------------------
4 Joe Amine beirut 03776132
5 Mariam zoue beirut 0355613
Thanks in advance
try with left join. It will display all records from internetClient and related record from internetclientdetails
Select internetClient.id, internetClient.full_name
, internetClient.location, internetClient.phone_number
, internetclientdetails.incdid, internetclientdetails.icid
, internetclientdetails.date_sub, internetclientdetails.date_exp
, internetclientdetails.isPaid, internetclientdetails.sub_price
from internetClient
left join internetclientdetails
on internetClient.id=internetclientdetails.icid group by internetclientdetails.icid order by internetclientdetails.incdid desc
if you want to get records of, only paid clients then you can try the following
Select internetClient.id, internetClient.full_name
, internetClient.location, internetClient.phone_number
, internetclientdetails.icid, internetclientdetails.incdid
, internetclientdetails.date_sub, internetclientdetails.date_exp
, internetclientdetails.isPaid, internetclientdetails.sub_price
from internetClient
left join internetclientdetails
on internetClient.id=internetclientdetails.icid
and internetclientdetails.isPaid=1 group by internetclientdetails.icid
order by internetclientdetails.incdid desc
SUMMARY
We generate a dataset containing just the ICID and max(date_sub) (alias:ICDi) We join this to the InternetClientDetails (ICD) to obtain just the max date record per client. Then left join this to the IC record; ensuring we keep all InternetClient(IC) records; and only show the related max Detail Record.
The below approach should work in most mySQL versions. It does not use an analytic which we could use to get the max date instead of the derived table provided the MySQL version you use supported it.
FINAL ANSWER:
SELECT IC.id
, IC.full_name
, IC.location
, IC.phone_number
, ICD.icid
, ICD.incdid
, ICD.date_sub
, ICD.date_exp
, ICD.isPaid
, ICD.sub_price
FROM internetClient IC
LEFT JOIN (SELECT ICDi.*
FROM internetclientdetails ICDi
INNER JOIN (SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
ON ICDi.ICID = mICD.ICID
AND ICDi.Date_Sub = mICD.MaxDateSub
) ICD
on IC.id=ICD.icid
ORDER BY ICD.incdid desc
BREAKDOWN / EXPLANATION
The below gives us a subset of max(date_Sub) for each ICID in clientDetails. We need to so we can filter out all the records which are not the max date per clientID.
(SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
Using that set we join to the details on the Client_ID's and the max date to eliminate all but the most recent detail for each client. We do this because we need the other detail attributes. This could be done using a join or exists. I prefer the join approach as it seems more explicit to me.
(SELECT ICDi.*
FROM internetclientdetails ICDi
INNER JOIN (SELECT max(date_sub) MaxDateSub, ICID
FROM internetclientdetails
GROUP BY ICID) mICD
ON ICDi.ICID = mICD.ICID
AND ICDi.Date_Sub = mICD.MaxDateSub
) ICD
Finally the full query joins the client to the detail keeping client even if there is no detail using a left join.
COMPONENTS:
You wanted all records from InternetClient (FROM internetClient IC)
You wanted related records from InternetClientDetail (LEFT Join InternetClientDetail ICD) while retaining teh records from InternetClient.
You ONLY wanted the most current record from InternetClientDetail (INNER JOIN InternetClientDetail mICD as a derived table getting ICID and max(date))
Total record count should = total record count in InternetClient which means all relationships must be a 1:1o on the table joins -- one-to-one Optional.

SQL Sum Calculation Confusion

I am new with mysql and working to change a store application to make it have two stock. I created a table to store stock quantity:
Then I plan to create a view with stock quantity, per store, per SKU. I using the following query:
SELECT
`stockList`.`sku`,
SUM(A.`stockQty`) AS 'store1',
SUM(B.`stockQty`) AS 'store2',
SUM(`stockList`.`stockQty`) AS 'total'
FROM `stockList`
LEFT JOIN (
SELECT * FROM `stockList` WHERE `idStock`=1
) AS A
ON `stockList`.`sku`=A.`sku`
LEFT JOIN (
SELECT * FROM `stockList` WHERE `idStock`=2
) AS B
ON `stockList`.`sku`=B.`sku`
GROUP BY `stockList`.`sku`
Per resulting table, calculation is not proper and I could not identify the logic:
SKU 43 should show for store1 = 9 and for store2 = 10, total = 19. This is what they show if I execute the select queries alone. Please, let me know if I misunderstood how this sum logic works.
You might to use SUM on subquery to calculate Totle price by sku
LEFT JOIN may make some fields not match causing NULL so use IFNULL to preset value 0
You can try this.
SELECT
T.sku,
SUM(T.stockQty) as totle,
IFNULL(A.`store1`,0) AS `store1`,
IFNULL(B.`store2`,0) AS `store2`
FROM `stockList` AS T
LEFT JOIN
(
SELECT sku,SUM(`stockQty`) as `store1`
FROM `stockList`
WHERE `idStock`=1
GROUP BY sku
) as A ON A.sku = T.sku
LEFT JOIN
(
SELECT sku,SUM(`stockQty`) as `store2`
FROM `stockList`
WHERE `idStock`=2
GROUP BY sku
) AS B ON T.sku =B.sku
GROUP BY T.sku
sqlfiddle
Your query is much more complicated than it needs to be. You can just do this:
SELECT
sku,
SUM(stockQty) as total,
SUM(IF(idStock=1,stockQty,0)) AS `store1`,
SUM(IF(idStock=2,stockQty,0)) AS `store2`
FROM `stockList`
GROUP BY sku
Output:
sku total store1 store2
36 10 10 0
37 3 3 0
38 4 4 0
39 3 3 0
40 10 10 0
41 12 12 0
42 12 12 0
43 19 9 10

MySQL 5.5: Efficient way to copy the same user information with different IDs

In MySQL 5.5, assume we have the following MAIN table
Id Name Score
3 a 100
3 b 99
4 c 98
4 d 97
Also a SUB table:
Id New_Id
3 1
3 1
4 2
4 2
The ideal output is OUTPUT table:
Id Name Score
3 a 100
3 b 99
4 c 98
4 d 97
1 a 100
1 b 99
2 c 98
2 d 97
The MySQL fiddle is available here
http://sqlfiddle.com/#!9/91c1cf/6
In the OUTPUT table,
we can see that the Id=1 shares the same information as Id =3. Similarly,
we can see that the Id=2 shares the same information as Id =4.
Is there any simple way such as "INSERT INTO...SELECT", "COPY", and "UPDATE" etc. that we can just copy the data from Id=3 to Id=1, and
copy the data from Id=4 to Id=2?
The following query does generate our ideal output, but the JOIN with the proper indexes is still painfully slow in our few hundreds millions rows. The job even fails due to not sufficient RAM and /tmp folder space in the case of few billion rows. We are upgrading our system from MySQL to somewhere more scab-able. However, we need to make sure the MySQL system will be functional during the few months of the transit period. Any 2 cents will be highly appreciated!
SELECT Id, Name, Score FROM MAIN
UNION
SELECT d.New_Id AS Id, c.Name, c.Score FROM MAIN c
RIGHT JOIN SUB d
ON c.Id = d.Id;
Use INNER JOIN rather than RIGHT JOIN, since you don't need the null rows that result from non-matching rows. You can use INSERT INTO ... SELECT to add these new rows to the table. And rather than using UNION, you can simply do two inserts into the new table:
INSERT INTO OUTPUT (id, name, score)
SELECT id, name, score
FROM MAIN;
INSERT INTO OUTPUT (id, name, score)
SELECT d.new_id, c.name, c.score
FROM MAIN AS c
JOIN SUB AS d ON c.id = d.id;
As long as you have indexes on the id columns in both input tables this should be as efficient as possible.
Finally, when doing large UNION queries, use UNION ALL if you know there are no duplicates that need to be merged. UNION by itself defaults to UNION DISTINCT, so it needs to create a temporary table to hold all the results to scan for duplicates.

how to avoid duplicate data in sql command

I try to use DISTINCT to avoid duplication of the data, but no work.
How to avoid the duplicate data?
Table 1
Employee code Deduction Voucher no Dec_Amount
001 999 50
001 888 20
002 777 100
Table 2
Employee code Payslip Voucher No Pay_Amount
001 111 100
002 222 200
The output should be:
Employee code Deduction Voucher no Dec_Amount Payslip Voucher No Pay_Amount
001 999 50 111 100
001 888 20
002 777 100 222 200
But i got the table like this.
Employee code Deduction Voucher no Dec_Amount Payslip Voucher No Pay_Amount
001 999 50 111 100
001 888 20 111 100
002 777 100 222 200
You cannot get those results with just a SQL query. It seems to me you need it in this format for display in a table/excel spreadsheet. If this is the case you would have to handle the "Hiding" of the particular entries with some other code. The reason is because the entries you want to hide are correctly associated with the 001 employee.
While I do agree this probably makes a lot more sense to do in your front end code, it is possible to do in SQL. Using a variable you get a function similar to SQL Server's ROW_NUMBER function and then only join on the first row per employee code.
See the sqlfiddle - http://sqlfiddle.com/#!2/47302/11
SELECT t1.`Employee code`,`Deduction Voucher no`,`Dec_Amount`,
COALESCE(`Payslip Voucher No`,'') as `Payslip Voucher No`,
COALESCE(CAST(`Pay_Amount` as char(10)),'') as `Pay_Amount`
FROM Table2 t2
RIGHT JOIN
(
SELECT #row_num := IF(#prev_value=`Employee code`,#row_num+1,1) AS RowNumber
,`Employee code`,`Deduction Voucher no`,`Dec_Amount`
,#prev_value := `Employee code`
FROM Table1,
(SELECT #row_num := 1) x,
(SELECT #prev_value := '') y
ORDER BY `Employee code`
) t1
ON t1.`Employee code`=t2.`Employee code` AND t1.RowNumber=1
To expand on #d.lanza38's answer, there is no way given for the DB to tell which row in table1 should get the data from table2. Remember that there is no order to the data in the database, so there is no inherent concept of "the first row with employee code 001".
A standard (inner) join will put them as you have shown - on both. Which is actually correct - your table structures say that for every payslip in table2, there can be many deductions. So if you want the data from both tables, the deductions have to have the matching payslip data attached.
You can't use DISTINCT to magically fix your data - you need to understand the data structures, and relate them correctly.
To get what is in your example (which may be wrong) try this SQL:
select
a.Employee_code,
Deduction_Voucher_no,
Dec_Amount,
Payslip_Voucher_No,
Pay_Amount
from
table1 as a
inner join table2 as b on a.employee_code = b.employee_code
where Deduction_Voucher_no = (
select max(Deduction_Voucher_no)
from table1 as c
where a.Employee_code = c.Employee_code)
UNION
select
a2.Employee_code,
Deduction_Voucher_no,
Dec_Amount,
null as Payslip_Voucher_No,
null as Pay_Amount
from
table1 as a2
inner join table2 as b2 on a2.employee_code = b2.employee_code
where Deduction_Voucher_no <> (
select max(Deduction_Voucher_no)
from table1 as c2
where a2.Employee_code = c2.Employee_code)
order by 1,2 desc
Note: untested, because I don't have your database, and don't even know which database engine you are using. If it complains about selecting nulls, replace with 0 or '' depending upon the data type.
UPDATE improved SQL and provided a fiddle: http://sqlfiddle.com/#!2/e7fc2/2