MYSQL: BASIC SELECT with != statemenet - mysql

SELECT id, company_id, user_id, valid_until, price
FROM `classifieds`
WHERE `user_id` = 1
AND `valid_until` > now()
AND `price` = -2
this query gives me one row, and it works ok...
as soon as i add a statement
SELECT id, company_id, user_id, valid_until, price
FROM `classifieds`
WHERE `user_id` = 1
AND `valid_until` > now()
AND `price` = -2
AND `company_id` != 23
it gives me nothing!!!
the row I am getting in first query has company_id NULL
id company_id user_id valid_until price
35136 NULL 1 2012-02-12 08:06:37 -2
Please advise
EDIT:
I don't want rows that have company_id NULL. I want ALL rows (NULL and not NULL) except for (company_id) value 23.

SELECT id, company_id, user_id, valid_until, price
FROM `classifieds`
WHERE `user_id` = 1
AND `valid_until` > now()
AND `price` = -2
AND (`company_id` IS NULL OR `company_id` != 23)
this seems to do the trick

Related

Alternative to subquery (mysql)

I have tables that hold payroll header & detail info on a separate table. Schema of each below,
CREATE TABLE `Payroll` (
`payId` int,
`groupId` int,
`startDate` date ,
`endDate` date,
`paymentDate` date
);
insert into Payroll values
(20,2,'2022-06-01','2022-06-30','2022-06-30'),
(21,2,'2022-07-01','2022-07-31','2022-07-31'),
(18,1,'2022-05-01','2022-05-31','2022-05-31'),
(19,1,'2022-07-01','2022-07-31','2022-07-31')
;
CREATE TABLE `PayrollItems` (
`payId` int NOT NULL,
`employeeId` int ,
`payCategory` varchar(45) ,
`value` decimal(15,4) NOT NULL
);
insert into PayrollItems values
(20,12,'salary',200),
(20,12,'housing',500),
(20,13,'salary',400),
(20,14,'salary',1300),
(21,12,'salary',200),
(21,12,'housing',500),
(21,13,'salary',400),
(21,14,'salary',1300),
(18,13,'salary',400),
(18,13,'housing',1300),
(19,14,'salary',500),
(19,14,'housing',1200)
;
I am trying to get a query wherein given a payid i should get the previous payid details. Previous payid is identified by a combination of the paymentDate & groupId fields.
Therefore, for the data above, for payid 19 i should get records of payid 18 i.e each pay item value, as they both are of the same groupid, 1 , and paymentDate of payid 18 is prior to paymentDate of payid 19. There could be more records that have a paymentDate dated prior to payid 19, but only first record dated prior is required.
I tried,
SELECT
y.*
FROM
Payroll ppi2
JOIN
PayrollItems prr3 ON (`prr3`.`payId` = `ppi2`.`payId`)
LEFT JOIN
(SELECT
prr3.employeeId,
ppi2.paymentDate,
ppi2.payId,
ppi2.groupId,
'Last months standard salary' AS Particulars,
MAX(CASE
WHEN TRIM(prr3.payCategory) = 'salary' THEN value
ELSE 0
END) salary,
MAX(CASE
WHEN TRIM(prr3.payCategory) = 'housing' THEN value
ELSE 0
END) housing
FROM
Payroll ppi2
JOIN PayrollItems prr3 ON (`prr3`.`payId` = `ppi2`.`payId`)
AND ppi2.payId = 19
GROUP BY ppi2.payId , prr3.employeeId , ppi2.paymentDate,ppi2.groupId
ORDER BY ppi2.paymentDate DESC) AS y ON (y.groupId = ppi2.groupId)
AND y.paymentDate < ppi2.paymentDate
GROUP BY y.payId,y.employeeId,y.paymentDate,y.groupId,y.Particulars;
but i am not getting any results.
Expected result,given payid = 19, would be,
payid employeeid housing salary
18 13 1300 400
Would there be another way of doing this ?
dbfiddle
WITH
cte AS (
SELECT t1.payid prev_payid
FROM Payroll t1
JOIN Payroll t2 USING (groupId)
WHERE t2.payid = #payid
AND t1.startDate < t2.startDate
ORDER BY t1.startDate DESC LIMIT 1
)
SELECT payId,
employeeId,
SUM(value * (payCategory = 'housing')) housing,
SUM(value * (payCategory = 'salary')) salary
FROM PayrollItems
CROSS JOIN cte
WHERE payid = prev_payid
GROUP BY 1, 2
https://dbfiddle.uk/1LAdyksH

Change db schema or query to return balance for a given period of time

I have came up with the following schema:
CREATE TABLE products
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
quantity INT(10) UNSIGNED NOT NULL,
purchase_price DECIMAL(8,2) NOT NULL,
sell_price DECIMAL(8,2) NOT NULL,
provider VARCHAR(255) NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
# payment methods = {
# "0": "CASH",
# "1": "CREDIT CARD",
# ...
# }
CREATE TABLE orders
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
product_id INT(10) UNSIGNED NOT NULL,
quantity INT(10) UNSIGNED NOT NULL,
payment_method INT(11) NOT NULL DEFAULT 0,
created_at TIMESTAMP NULL,
PRIMARY KEY (id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
# status = {
# "0": "PENDING"
# "1": "PAID"
# }
CREATE TABLE invoices
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
price INT(10) UNSIGNED NOT NULL,
status INT(10) UNSIGNED NOT NULL DEFAULT 0,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
# payment methods = {
# "0": 'CASH',
# "1": 'CREDIT CARD',
# ...
# }
CREATE TABLE bills
(
id INT(10) UNSIGNED AUTO_INCREMENT NOT NULL,
name VARCHAR(255) NOT NULL,
payment_method INT(10) UNSIGNED NOT NULL DEFAULT 0,
price DECIMAL(8,2) NOT NULL,
created_at TIMESTAMP NULL,
PRIMARY KEY (id)
);
And the following query to select a balance:
SELECT ((orders + invoices) - bills) as balance
FROM
(
SELECT SUM(p.sell_price * o.quantity) as orders
FROM orders o
JOIN products p
ON o.product_id = p.id
) orders,
(
SELECT SUM(price) as invoices
FROM invoices
WHERE status = 1
) invoices,
(
SELECT SUM(price) as bills
FROM bills
) bills;
Its working and returning the right balance, but I want to create a chart using Morris.js and I need to change it to return a daily or monthly balance at a given period of time and in this format:
Daily (2017-02-27 to 2017-03-01)
balance | created_at
--------------------------
600.00 | 2017-03-01
50.00 | 2017-02-28
450.00 | 2017-02-27
And monthly (2017-01 to 2017-03)
balance | created_at
--------------------------
200.00 | 2017-03
250.00 | 2017-02
350.00 | 2017-01
What I need to change in my schema or query to return results in this way?
http://sqlfiddle.com/#!9/2289a9/2
Any hints are welcomed. Thanks in advance
Include the created_at date in the SELECT list and a GROUP BY clause in each query.
Ditch the old school comma operator for the join operation, and replace it with a LEFT JOIN.
To return dates for which there are no orders (or no payments, or no invoices) we need a separate row source that is guaranteed to return the date values. As an example, we could use an inline view:
SELECT d.created_dt
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
ORDER BY d.created_dt
The inline view is just an option. If we had a calendar table that contains rows for the three dates we're interested in, we could make use of that instead. What's important is that we have a query that is guaranteed to return to us exactly three rows with the distinct created_at date values we want to return.
Once we have that, we can add a LEFT JOIN to get the value of "bills" for that date.
SELECT d.created_dt
, b.bills
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
LEFT
JOIN ( SELECT DATE(bills.created_at) AS created_dt
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-27'
AND bills.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(bills.created_at)
) b
ON b.created_dt = d.created_dt
ORDER BY d.created_dt
Extending that to add another LEFT JOIN, to get invoices
SELECT d.created_dt
, i.invoices
, b.bills
FROM ( SELECT '2017-02-27' + INTERVAL 0 DAY AS created_dt
UNION ALL SELECT '2017-02-28'
UNION ALL SELECT '2017-03-01'
) d
LEFT
JOIN ( SELECT DATE(bills.created_at) AS created_dt
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-27'
AND bills.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(bills.created_at)
) b
ON b.created_dt = d.created_dt
LEFT
JOIN ( SELECT DATE(invoices.created_at) AS created_dt
, SUM(invoices.price) AS invoices
FROM invoices
WHERE invoices.status = 1
AND invoices.created_at >= '2017-02-27'
AND invoices.created_at < '2017-03-01' + INTERVAL 1 DAY
GROUP BY DATE(invoices.created_at)
) i
ON i.created_dt = d.created_dt
ORDER BY d.created_dt
Similarly, we can a LEFT JOIN to another inline view that returns total orders grouped by DATE(created_at).
It's important that the inline views return distinct value of created_dt, a single row for each date value.
Note that for dev, test and debugging, we can independently execute just the inline view queries.
When a matching row is not returned from a LEFT JOIN, for example no matching row returned from i because there were no invoices on that date, the query is going to return a NULL for the expression i.invoices. To replace the NULL with a zero, we can use the IFNULL function, or the more ANSI standard COALESCE function. For example:
SELECT d.created_dt
, IFNULL(i.invoices,0) AS invoices
, COALESCE(b.bills,0) AS bills
FROM ...
To get the results monthly, we'd need a calendar query that returns one row per month. Let's assume we're going to return a DATE value which as the first day of the month. For example:
SELECT d.created_month
FROM ( SELECT '2017-02-01' + INTERVAL 0 DAY AS created_month
UNION ALL SELECT '2017-03-01'
) d
ORDER BY d.created_month
The inline view queries will need to GROUP BY created_month, so they return a single value for each month value. My preference would be to use a DATE_FORMAT function to return the first day of the month, derived from created_at. But there are other ways to do it. The goal is return a single row for '2017-02-01' and a single row for '2017-03-01'. Note that the date ranges on created_at extend from '2017-02-01' up to (but not including) '2017-04-01', so we get the total for the whole month.
( SELECT DATE_FORMAT(bills.created_at,'%Y-%m-01') AS created_month
, SUM(bills.price) AS bills
FROM bills
WHERE bills.created_at >= '2017-02-01'
AND bills.created_at < '2017-03-01' + INTERVAL 1 MONTH
GROUP BY DATE_FORMAT(bills.created_at,'%Y-%m-01')
) b

Get 100 rows with maximum of 10 rows per group

I have the following query, and I would like to get 100 items from the database, but host_id is in the urls table many times, and I would like to get a maximum of 10 unique rows from that table per host_id.
select *
from urls
join hosts using(host_id)
where
(
last_run_date is null
or last_run_date <= date_sub(curdate(), interval 30 day)
)
and ignore_url != 1
limit 100
So, I would like:
Maximum Results = 100
Max Rows Per Host = 10
I am not sure what I would need to do to accomplish this task. Is there a way to do this without a subquery?
Hosts Table
CREATE TABLE `hosts` (
`host_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`host` VARCHAR(50) NOT NULL,
`last_fetched` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`ignore_host` TINYINT(1) UNSIGNED NOT NULL,
PRIMARY KEY (`host_id`),
UNIQUE INDEX `host` (`host`)
)
Urls Table
CREATE TABLE `urls` (
`url_id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`parent_url_id` INT(10) UNSIGNED NOT NULL,
`scheme` VARCHAR(5) NOT NULL,
`host_id` INT(10) UNSIGNED NOT NULL,
`path` VARCHAR(500) NOT NULL,
`query` VARCHAR(500) NOT NULL,
`date_found` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`last_run_date` DATETIME NULL DEFAULT NULL,
`ignore_url` TINYINT(1) UNSIGNED NOT NULL,
PRIMARY KEY (`url_id`),
UNIQUE INDEX `host_path_query` (`host_id`, `path`, `query`)
)
Thats it (I hope)
I cant test i real. i have no data. pls test it and give me a little ping.
SELECT *
FROM (
SELECT
#nr:=IF(#lasthost = host_id, #nr+1, 1) AS nr,
u.*,
#lasthost:=IF(#lasthost = host_id, #lasthost, host_id) AS lasthost
FROM
urls u,
( SELECT #nr:=4, #lasthost:=-1 ) AS tmp
WHERE (
last_run_date IS NULL
OR last_run_date <= date_sub(curdate(), INTERVAL 30 DAY)
)
AND ignore_url != 1
ORDER BY host_id, last_run_date
) AS t
LEFT JOIN HOSTS USING(host_id)
WHERE t.nr < 11
LIMIT 100;
ok,
first:
I only select the rows with your query, and order it
by the host_id and time
SELECT
u.*
FROM
urls u
( SELECT #nr:=4, #lasthost:=-1 ) AS tmp
WHERE (
last_run_date IS NULL
OR last_run_date <= date_sub(curdate(), INTERVAL 30 DAY)
)
AND ignore_url != 1
ORDER BY host_id, last_run_date
second
I add to variables nr and lasthost and setup it in the select. Now
i count nr each row and reset it to 1 if the host_id is change. So i get a
list of rows numbert from 1 to n for each host_id
SELECT
#nr:=IF(#lasthost = host_id, #nr+1, 1) AS nr,
u.*,
#lasthost:=IF(#lasthost = host_id, #lasthost, host_id) AS lasthost
FROM
urls u,
( SELECT #nr:=4, #lasthost:=-1 ) AS tmp
WHERE (
last_run_date IS NULL
OR last_run_date <= date_sub(curdate(), INTERVAL 30 DAY)
)
AND ignore_url != 1
ORDER BY host_id, last_run_date
third
i put it this query in a new select so i can join your second table and restrict the result only for rows less 11 and also limit the result to 100
SELECT *
FROM (
SELECT
#nr:=IF(#lasthost = host_id, #nr+1, 1) AS nr,
u.*,
#lasthost:=IF(#lasthost = host_id, #lasthost, host_id) AS lasthost
FROM
urls u,
( SELECT #nr:=4, #lasthost:=-1 ) AS tmp
WHERE (
last_run_date IS NULL
OR last_run_date <= date_sub(curdate(), INTERVAL 30 DAY)
)
AND ignore_url != 1
ORDER BY host_id, last_run_date
) AS t
LEFT JOIN HOSTS USING(host_id)
WHERE t.nr < 11
LIMIT 100;
Thats all
So you need a limited JOIN. Another guess:
SELECT * FROM hosts
LEFT JOIN urls ON
urls.host_id = hosts.host_id
WHERE urls.host_id IN
(SELECT host_id FROM urls
LIMIT 0,10)
LIMIT 0,100

Sum only if all grouped rows are not null, else return null

I have a table like this:
item_id quantity
1 2
1 3
2 NULL
2 4
3 NULL
3 NULL
And now I'm doing a SELECT like this:
SELECT
sum(`quantity`) AS `total_quantity`,
FROM `items`
GROUP BY `item_id`
Now, it return repectively 5, 4, and NULL, but I want 5, NULL and NULL.
I want that if there is a NULL value in the grouped rows, the sum should be NULL, and not the sum of the lines whose columns are not null. How can I achieve that?
Thanks!
You can use only case statement to check if any row of a group contains null as quantity
SELECT item_id,
CASE WHEN SUM(quantity IS NULL) > 0
THEN NULL
ELSE SUM(quantity)
END quantity
FROM items
GROUP BY item_id
using #Abhik Chakraborty's fiddle
DEMO
Thats kind if weird output, in most cases the request is to replace null with 0 or something else, however here is a way to do it
select
x.item_id,
max(x.quantity) as quantity from (
SELECT
t1.item_id,
#sm:= if(#prev_item = item_id, #sm_qty+quantity,quantity) as quantity,
#prev_item :=item_id,
#sm_qty:= quantity
from items t1,(select #prev_item:=null,#sm_qty=0)x
order by item_id
)x
group by x.item_id;
http://www.sqlfiddle.com/#!9/ccb36/13
SELECT * FROM (
( -- Get all not null quantities
SELECT
`i1`.`item_id`,
sum(`i1`.`quantity`) AS `total_quantity`
FROM `items` AS `i1`
WHERE `i1`.`item_id` NOT IN ( SELECT `i2`.`item_id` FROM `items` AS `i2` WHERE `i2`.`quantity` IS NULL )
GROUP BY `item_id`
)
UNION ALL
( -- Get all null quantities
SELECT
`i3`.`item_id`,
null AS `i3`.`total_quantity`
FROM `items` AS `i3`
WHERE `i3`.`item_id` IN ( SELECT `i4`.`item_id` FROM `items` AS `i4` WHERE `i4`.`quantity` IS NULL )
GROUP BY `i3.item_id`
)
) AS items_table
ORDER BY items_table.item_id

Load top 5 records per date

I have a table, in which there are date wise quiz score of different users. I want to load top 5 scorers for every date.
Table sample create statement:
CREATE TABLE `subscriber_score` (
`msisdn` varchar(25) COLLATE utf8_unicode_ci NOT NULL,
`date` date NOT NULL,
`score` int(11) NOT NULL DEFAULT '0',
`total_questions_sent` int(11) NOT NULL DEFAULT '0',
`total_correct_answers` int(11) NOT NULL DEFAULT '0',
`total_wrong_answers` int(11) NOT NULL DEFAULT '0',
PRIMARY KEY (`msisdn`,`date`),
KEY `fk_subscriber_score_subscriber1` (`msisdn`),
CONSTRAINT `fk_subscriber_score_subscriber1` FOREIGN KEY (`msisdn`) REFERENCES `subscriber` (`msisdn`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Query which I have tried:
SELECT subscriber.msisdn AS msisdn,subscriber.name AS name,subscriber.gender AS gender,tmp2.score AS score,tmp2.date AS winning_date
FROM subscriber,
(SELECT msisdn,tmp.date,tmp.score
FROM subscriber_score,
(SELECT date,MAX(score) AS score
FROM subscriber_score
WHERE date > '2014-10-10' AND date < '2014-11-10' GROUP BY date)
tmp
WHERE subscriber_score.date=tmp.date AND subscriber_score.score=tmp.score)
tmp2
WHERE subscriber.msisdn=tmp2.msisdn ORDER BY winning_date
Actual output: Only one top scorer for every date is shown.
Wanted Output Top 5(or say 10) records for every date are required.
I think you can do this using variables to assign each row a row number, then filter the top 5 for each date.
SELECT s.name AS name,
s.gender AS gender,
s.msisdn,
ss.date,
ss.score
FROM ( SELECT ss.msisdn,
ss.score,
#r:= CASE WHEN ss.Date = #d THEN #r + 1 ELSE 1 END AS RowNum,
#d:= ss.date AS winning_date
FROM subscriber_score AS ss
CROSS JOIN (SELECT #d:= '', #r:= 0) AS v
WHERE ss.date > '2014-10-10'
AND ss.date < '2014-11-10'
ORDER BY ss.Date, ss.Score DESC
) AS ss
INNER JOIN Subscriber AS s
ON s.msisdn = ss.msisdn
WHERE ss.RowNum <= 5;
Example on SQL Fiddle
refer this query its not complete but hope it helps
SELECT SCORE
FROM table
WHERE date='somedate'
ORDER BY SCORE DESC LIMIT 5
select bc.msisdn msisdn,bc.name name,bc.gender gender,ab.score score,ab.date winning_date
(
select msisdn,date,score,
dense_rank() over (partition by date order by score desc) rnk
from subscriber_score
) ab,subscriber bc
where bc.msisdn=ab.msisdn and ab.rnk<=5
order by winning_date ;
This is how you can get solution of your problem in oracle sql.
try below
SELECT subscriber.msisdn AS msisdn,subscriber.name AS name,subscriber.gender AS gender,tmp2.score AS score,tmp2.date AS winning_date
FROM subscriber inner join
(select msisdn,date, score, ROW_NUMBER() OVER(PARTITION BY date ORDER BY score DESC) AS Row
FROM subscriber_score
WHERE date > '2014-10-10' AND date < '2014-11-10' GROUP BY date)
tmp
on subscriber.msisdn=tmp.msisdn and tmp.row<=5