I am new to Sql, and need some guidance to create a Trial Balance via Sql query in MySql.
Consider the following scenario:
Two Tables:
Accounts
Transactions
Accounts Table fields details:
AccNo (PK)(varchar) (5)
AccName (varchar)(50)
AccOpBal (double)
Transactions Table fields details:
TransID (int) (Auto Increment) (PK)
AccNo (varchar) (5)
TransDt (DateTime)
TransDebit (Double)
TransCredit (Double)
Now I need a SQL query based on transDt date range(for e.g 01st Jan-14 to 31sth Jan-2014) which will return:
AccNo
AccOpBal
TransDebit (Sum of monthly transaction i.e Jan-2014)
TransCredit (Sum of monthly transaction i.e Jan-2014)
TransDebit (Sum of Yearly transaction i.e from 01st July-2013 to 31st Jan 2014 or YTD)
TransCredit (Sum of Yearly transaction i.e from 01st July-2013 to 31st Jan 2014 or YTD)
It is not necessary that every AccNo has opening balance (AccOpBal), likewise, it is also not necessary that every AccNo has transactions (TransDebit or TransCredit). But if an AccNo has any, it should be in query.
UPDATE Picture of sample trial added
You could achieve that result with a select over a union of two queries, one for the month to date and one for the year to date figures.
select accno, accopbal, sum(mtd_d), sum(mtd_c), sum(ytd_d),sum(ytd_c)
from
( select ao.accno
, ao.accOpBal
, 0 as mtd_d
, 0 as mtd_c
, 0 as ytd_d
, 0 as ytd_c
from accounts ao
left outer join transactions tn on tn.accno = ao.accno
where tn.accno is null
union
select tm.accno
, a.accOpBal
, sum(tm.transdebit) as mtd_d
, sum(tm.transcredit) as mtd_c
, 0 as ytd_d
, 0 as ytd_c
from accounts a
right outer join transactions tm on tm.accno = a.accno
where tm.transdt between '2014-01-01' and '2014-01-31'
group by a.accno, a.accopbal
union
select ty.accno
, a.accOpBal
, 0
, 0
, sum(ty.transdebit)
, sum(ty.transcredit)
from accounts a
right outer join transactions ty on ty.accno = a.accno
and ty.transdt between '2013-07-01' and '2014-01-31'
group by a.accno, a.accopbal
) alltxn
group by accno, accopbal
Here is a sqlfiddle with a small test set
and here is the testset:
-- january
insert into transactions values (1, 'alfki', '2014-01-01', 1,3);
insert into transactions values (1, 'alfki', '2014-01-02', 1,3);
insert into transactions values (1, 'alfki', '2014-01-03', 1,3);
-- last year
insert into transactions values (1, 'alfki', '2013-09-01', 5,2);
-- txn without acc
insert into transactions values (1, 'noexi', '2014-01-03', 4,2);
-- acc with txn
INSERT INTO Accounts values ( 'alfki', 'alfred', 4);
-- acc without txn
INSERT INTO Accounts values ( 'lefto', 'lefto', 6);
with the following query result:
ACCNO | ACCOPBAL |SUM(MTD_D)|SUM(MTD_C)|SUM(YTD_D)|SUM(YTD_C)
------+----------+----------+----------+----------+-----------
alfki | 4 | 3 | 9 | 8 | 11
lefto | 6 | 0 | 0 | 0 | 0
noexi | (null) | 4 | 2 | 4 | 2
Related
I have a table of ports:
drop table if exists ports;
create table ports(id int, name char(20));
insert into ports (id, name ) values
(1, 'Port hedland'),
(2, 'Kwinana');
And a table of tariffs connected to those ports:
drop table if exists tariffs;
create table tariffs(id int, portId int, price decimal(12,2), expiry bigint(11));
insert into tariffs (id, portId, price, expiry ) values
(1, 2, 11.00, 1648408400),
(2, 2, 12.00, 1648508400),
(3, 2, 13.00, 1648594800),
(4, 2, 14.00, 1651273200),
(5, 2, 15.00, 2250000000 );
insert into tariffs (id, portId, price, expiry ) values
(1, 1, 21.00, 1648408400),
(2, 1, 22.00, 1648508400),
(3, 1, 23.00, 1648594800),
(4, 1, 24.00, 1651273200),
(5, 1, 25.00, 2250000000 );
Each tariff has an expiry.
I can easily make a query to figure out the right tariff for as specific date for each port. For example at timestamp 1648594700 the right tariff is:
SELECT * FROM tariffs
WHERE 1648594700 < expiry AND portId = 2
ORDER BY expiry
LIMIT 1
Result:
id portId price expiry
3 2 13.00 1648594800
However, in my application I want to be able to pull in the right tariff starting from the ports record.
For one record, I can do this:
SELECT * FROM ports
LEFT JOIN tariffs on tariffs.portId = ports.id
WHERE 1648594700 < tariffs.expiry AND ports.id = 2
LIMIT 1
Result:
id name id portId price expiry
2 Kwinana 3 2 13.00 1648594800
This feels a little 'dirty', especially because I am doing a lookup on a record, and then forcing only one result using LIMIT. But, OK.
What I cannot do, and can't work out how to do, is a query that will return a list of ports, and each port having a price field that matches the constraint above (that is, the record with the highest expiry compared to 1648594700 for each port).
This obviously won't work:
SELECT * FROM ports
left join tariffs on tariffs.portId = ports.id
where 1648594700 < tariffs.expiry
Since the result of the query, testing with timestamp 1648594700, would be:
id name id portId price expiry
2 Kwinana 3 2 13.00 1648594800
2 Kwinana 4 2 14.00 1651273200
2 Kwinana 5 2 15.00 2250000000
1 Port he 3 1 23.00 1648594800
1 Port he 4 1 24.00 1651273200
1 Port he 5 1 25.00 2250000000
Instead, the result for all ports (before further filtering) should be:
id name id portId price expiry
2 Kwinana 3 2 13.00 1648594800
1 Port he 3 1 23.00 1648594800
Is there a clean, non-hacky way to have such a result?
As an added constraint, is this possible for this to be done in ONE query, without temp tables etc.?
You can select the lowest expiry, do your join and only take the rows having this minimum expiry:
SELECT p.id, p.name, t.id, t.portId, t.price, t.expiry
FROM ports p
LEFT JOIN tariffs t ON p.id = t.portId
WHERE expiry = (SELECT MIN(expiry) FROM tariffs WHERE 1648594700 < expiry)
ORDER BY p.id;
This will get your desired result, please see here: db<>fiddle
On MySQL 8+, ROW_NUMBER should work here:
WITH cte AS (
SELECT p.id, p.name, t.price, t.expiry,
ROW_NUMBER() OVER (PARTITION BY p.id ORDER BY t.expiry) rn
FROM ports p
LEFT JOIN tariffs t ON t.portId = p.id
WHERE t.expiry > 1648594700
)
SELECT id, name, price, expiry
FROM cte
WHERE rn = 1
ORDER BY id;
This logic would return one record for each port having the nearest expiry.
I want to fetch data from two tables post, post_like
I want the data such that if row entry is not present in post_like table null/0 as result to be shown
currently row data is omitted if the data is not present for a particular day.
I have prepared sample data
CREATE TABLE post(post_id INT, user_id INT, post_type INT);
INSERT INTO post VALUES (1,1,1),(2,1,2),(3,1,2),(4,2,1),(5,2,3);
CREATE TABLE post_like(post_id INT, user_id INT, created_on DATE);
INSERT INTO post_like VALUES
(1,4,"2020-02-10"),(2,4,"2020-02-10"),
(3,4,"2020-02-10"),(1,4,"2020-02-11"),
(2,4,"2020-02-11"),(3,4,"2020-02-11"),
(1,4,"2020-02-12"),(2,4,"2020-02-13"),
(3,4,"2020-02-13"),(1,4,"2020-02-14"),
(2,4,"2020-02-14"),(3,4,"2020-02-16"),
(1,4,"2020-02-16"),(2,4,"2020-02-16"),
(3,4,"2020-02-17"),(4,4,"2020-02-10"),
(5,4,"2020-02-16"),(4,4,"2020-02-10"),
(4,4,"2020-02-15"),(4,4,"2020-02-13"),
(5,4,"2020-02-11");
SQL fiddle
Query I am using
SELECT COUNT(a.post_id) AS likeCnt
, DAYNAME(DATE(a.created_on)) as day
FROM post_like a
JOIN post b
ON b.post_id = a.post_id
WHERE a.created_on BETWEEN subdate(curdate(),dayofweek(curdate())+5) AND
subdate(curdate(),dayofweek(curdate())-1)
AND b.user_id = 1
AND b.post_type = 2
GROUP
BY DATE(a.created_on)
above query returns me day wise data and total count of rows present in post_like table for that particular day but is omitting result for a day if no entry is found
I want to get the count for that day to be zero instead of skipping it.
Desired Output Example
likeCnt | day
------------------------
4 | Monday
2 | Tueday
1 | Wednesday
0 | Thursday
1 | Friday
1 | Saturday
0 | Sunday
SELECT COALESCE(data.likeCnt, 0) likeCnt,
day_names.day
FROM ( SELECT DAYNAME('2000-01-01') AS day UNION ALL -- generate daynames list
SELECT DAYNAME('2000-01-02') UNION ALL
SELECT DAYNAME('2000-01-03') UNION ALL
SELECT DAYNAME('2000-01-04') UNION ALL
SELECT DAYNAME('2000-01-05') UNION ALL
SELECT DAYNAME('2000-01-06') UNION ALL
SELECT DAYNAME('2000-01-07') ) day_names
LEFT JOIN ( SELECT COUNT(a.post_id) AS likeCnt -- and join the data
, ANY_VALUE(DAYNAME(DATE(a.created_on))) as day
FROM post_like a
JOIN post b ON b.post_id = a.post_id
WHERE a.created_on BETWEEN subdate(curdate(),dayofweek(curdate())+5)
AND subdate(curdate(),dayofweek(curdate())-1)
AND b.user_id = 1
AND b.post_type = 2
GROUP BY DATE(a.created_on) ) data USING (day)
PS. ANY_VALUE() added for possible ONLY_FULL_GROUP_BY SQL mode.
I'm very new to MYSQL, have looked at many answers on this site but can't get the following to work...
Table is "member"
3 fields are "id" (Integer); and 2 date fields "dob" and "expiry"
I need to count the number of records where all are current members, ie
expiry<curdate()
then I need to know the count of records with the following conditions:
year(curdate())-year(dob) <25 as young
year(curdate())-year(dob) >25 and <=50 as Medium
year(curdate())-year(dob) >50 as Older
So I expect to get a single row with many columns and the count of each of these conditions.
Effectively I'm filtering current members for their age grouping.
I've tried a subquery but failed to get that to work.
Thanks
If you really want the end result as you have mentioned, you could use views. It takes a long way to achieve the result. However, here is the way. I created the following table member and inserted data as follows.
CREATE TABLE member (
id int(11) AUTO_INCREMENT PRIMARY KEY,
dob date DEFAULT NULL,
expiry date DEFAULT NULL
);
INSERT INTO member (id, dob, expiry) VALUES
(1, '1980-01-01', '2020-05-05'),
(2, '1982-05-05', '2020-01-01'),
(3, '1983-05-05', '2020-01-01'),
(4, '1981-05-05', '2020-01-01'),
(5, '1994-05-05', '2020-01-01'),
(6, '1992-05-05', '2020-01-01'),
(7, '1960-05-05', '2020-01-01'),
(8, '1958-05-05', '2020-01-01'),
(9, '1958-07-07', '2020-05-05');
Following is the member table with data.
id | dob | expiry
--------------------------------
1 | 1980-01-01 | 2020-05-05
2 | 1982-05-05 | 2020-01-01
3 | 1983-05-05 | 2020-01-01
4 | 1981-05-05 | 2020-01-01
5 | 1994-05-05 | 2020-01-01
6 | 1992-05-05 | 2020-01-01
7 | 1960-05-05 | 2020-01-01
8 | 1958-05-05 | 2020-01-01
9 | 1958-07-07 | 2020-05-05
Then I created a separate view for all the current employees named as current_members as follows.
CREATE VIEW current_members AS (SELECT * FROM member WHERE TIMESTAMPDIFF(YEAR, CAST(CURRENT_TIMESTAMP AS DATE), member.expiry) >= 0);
Then querying from that view, I created 3 separate views containing counts for each age ranges of young, middle and old as follows.
CREATE VIEW young AS (SELECT COUNT(*) as Young FROM (SELECT TIMESTAMPDIFF(YEAR, current_members.dob, CAST(CURRENT_TIMESTAMP AS DATE)) AS age FROM current_members HAVING age <= 25) yng);
CREATE VIEW middle AS (SELECT COUNT(*) as Middle FROM (SELECT TIMESTAMPDIFF(YEAR, current_members.dob, CAST(CURRENT_TIMESTAMP AS DATE)) AS age FROM current_members HAVING age BETWEEN 25 AND 50) mid);
CREATE VIEW old AS (SELECT COUNT(*) as Old FROM (SELECT TIMESTAMPDIFF(YEAR, current_members.dob, CAST(CURRENT_TIMESTAMP AS DATE)) AS age FROM current_members HAVING age >= 50) old);
Finally, the three views were cross joined in order to get the counts of each age range into a single row of one final table as follows.
SELECT * FROM young, middle, old;
This will give you the following result.
Young | Middle | Old
----------------------
2 | 4 | 3
SUGGESTION : FOR THE ABOVE TEDIOUS TIME DIFFERENCE CALCULATIONS, YOU COULD WRITE YOUR OWN STORED PROCEDURE TO SIMPLIFY THE CODE
I have a table that looks like this:
CustomerID | ContactTime | AttemptResult
-----------+-----------------+-----------------
1 | 1/1/2016 5:00 | Record Started
1 | 1/1/2016 6:00 | Appointment
2 | 1/2/2016 5:00 | Record Started
1 | 1/3/2016 6:00 | Sold
2 | 1/2/2016 5:00 | Sold
3 | 1/4/2016 5:00 | Record Started
3 | 1/4/2016 6:00 | Sold
From
create table #temp1
(
CustomerID int,
ContactTime datetime,
Result nvarchar(50)
)
insert into #temp1 values (1, '1/1/2016 5:00', 'Record Started')
insert into #temp1 values (1, '1/1/2016 6:00', 'Appointment')
insert into #temp1 values (2, '1/2/2016 5:00', 'Record Started')
insert into #temp1 values (1, '1/3/2016 6:00', 'Sold')
insert into #temp1 values (2, '1/2/2016 5:00', 'Sold')
insert into #temp1 values (3, '1/4/2016 5:00', 'Record Started')
insert into #temp1 values (3, '1/4/2016 6:00 ', 'Sold')
How can I query this in a way that gets all combinations in order of AttemptResults ? So something like:
CustID | Sequence
-------+--------------------------------------
1 | Record Started -> Appointment -> Sold
2 | Record Started -> Sold
3 | Record Started -> Sold
I'm not even sure where to start...
If this is your complete dataset, I can help you. Otherwise I would need to see more. Use something called a Window Function. Below esentially indexes or keeps track of how many entries there are for each CustomerID.
Select *, row_number() over (partition CustomerID group by ContactTime) as Combo
into #temp
from table
Then count how many combos of 2 happen (Record Started->Sold), combos of 3 (Record Started -> Appointment -> Sold )
Select CustomerID, max(Combo) as MaxCombo
into #temp1
from #temp
group by CustomerId
Select MaxCombo, count(*)
from #temp1
group by MaxCombo
You could also use Common Table Expressions instead of these temp tables but I didnt want to add too much confusion.
I have two tables: orders and oldorders. Both are structured the same way. I want to union these two tables and then join them to another table: users. Previously I only had orders and users, I am trying to shoehorn oldorders into my current code.
SELECT u.username, COUNT(user) AS cnt
FROM orders o
LEFT JOIN users u
ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user
This finds the number of nonzero total orders all users have made in table orders, but I want to this in the union of orders and oldorders. How can I accomplish this?
create table orders (
user int,
shipped int,
total decimal(4,2)
);
insert into orders values
(5, 1, 28.21),
(5, 1, 24.12),
(5, 1, 19.99),
(5, 1, 59.22);
create table users (
username varchar(100),
userident int
);
insert into users values
("Bob", 5);
Output for this is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 4 |
+----------+-----+
After creating the oldorders table:
create table oldorders (
user int,
shipped int,
total decimal(4,2)
);
insert into oldorders values
(5, 1, 62.94),
(5, 1, 53.21);
The expected output when run on the union of the two tables is:
+----------+-----+
| username | cnt |
+----------+-----+
| Bob | 6 |
+----------+-----+
Just not sure where or how to shoehorn a union into there. Instead of running the query on orders, it needs to be on orders union oldorders. It can be assumed there is no intersect between the two tables.
You just need to union this way:
SELECT u.username, COUNT(user) AS cnt
FROM
(
SELECT * FROM orders
UNION
SELECT * FROM oldorders
) o
LEFT JOIN users u ON u.userident = o.user
WHERE shipped = 1
AND total != 0
GROUP BY user;
First get the combined orders using UNION between orders and oldorders table.
The rest of the work is exactly same what you did.
SEE DEMO
Note:
Left join doesn't make sense in this case. Orders for which the users don't exist then you will get NULL 0 as output. This doesn't hold any value.
If you want <user,total orders> for all users including users who might not have ordered yet then you need to change the order of the LEFT JOIN