update and 2 select statements in same query causing an error - mysql

I have a table with a field c_id which has the entries with some values of CustomerIds.
I need to set those to null if those customer Ids are not valid as per the table.
I am using the following query. But it seems to throw an error:
update Customers set c_id=NULL where customer_id in (select customer_id from Customers where c_id not in (select customer_id from Customers);
Could someone help me identify the problem here

I avoided IN and used JOINS to fine tune your code:
UPDATE CUST SET C_Id = NULL
FROM Customers CUST
LEFT JOIN Customers LJC ON LJC.Customer_Id = CUST.C_Id
WHERE LJC.Customer_Id IS NULL
SQL Fiddle: http://sqlfiddle.com/#!3/59fb1/5
I'm explain what I have done:
Created dummy table and data for Customers table:
CREATE TABLE Customers (
Customer_Id INT,
C_Id INT
)
INSERT INTO Customers
SELECT 1, 11 UNION
SELECT 2, 22 UNION
SELECT 22, 3 UNION
SELECT 11, 4 UNION
SELECT 5, 6 UNION
SELECT 7, 8 UNION
SELECT 3, 9
Here 11, 22 and 3 are exists in Customer_Id, C_Id, So as per your request the other C_Id 4, 6, 8 and 9 those are not exist in Customer_Id are will UPDATE as NULL.
The below block will return the C_Id those are not exists in the Customer_Id
SELECT C1.C_Id
FROM Customers C1
LEFT JOIN Customers C2 ON C2.Customer_Id = C1.C_Id
WHERE C2.Customer_Id IS NULL
The below block will update C_Id as NULL from the above SELECT block
UPDATE Customers SET C_Id = NULL
WHERE C_Id IN (
SELECT C1.C_Id
FROM Customers C1
LEFT JOIN Customers C2 ON C2.Customer_Id = C1.C_Id
WHERE C2.Customer_Id IS NULL
)
From the above block, if I remove the IN and modify using JOIN, the query what I gave in the top will come.

You have a syntax error.
Add ) at the end of query for closing first select query
update Customers set c_id = NULL
where customer_id in
(select customer_id
from Customers
where c_id not exists
(select customer_id from Customers)
);

Related

Join Two tables but replace overlapping data with second tables

Hello I have an issue I am working on for a theoretical problem. Assume I have these two tables
Order Table
Entry
Order#
DatePlaced
Type
2001
5
2021-05-03
C
Status Table
Entry
Order#
Status
Date
Deleted
2001
5
S
2021-05-04
0
2002
5
D
2021-05-05
0
So I need to be able to get this
Expected Table
Entry
Order#
DatePlaced
Type
Status
Date
Deleted
2002
5
2021-05-03
C
D
2021-05-05
0
This would be fairly easy if I could just left join the data. The is issue is that the sql in the code is already written like this. The tables are joined based on the entry. Every time a new status occurs for an order# the entry in the Order Table is updated EXCEPT when it is delivered. Do to how dependent the code is I cannot simply update the initial query below. I was wondering if there is a join or way without using SET that I can get the last status based on the order? I was thinking we can check the order and then the entry but I am not sure how to join that with the Current Table (data we get from query)
SELECT * FROM orders or
LEFT JOIN status st ON or.entry = st.entry
WHERE st.deleted = 0;
This results in this
Current Table
Entry
Order#
DatePlaced
Type
Status
Date
Deleted
2001
5
2021-05-03
C
S
2021-05-04
0
Is there a way to JOIN the status table with the Current Table so that the status columns become what I expect?
This will work just fine:
SELECT s.entry, s.order_no, o.date_placed, o.type, s.status, s.date, s.deleted
FROM `orders` o
INNER JOIN `status` s ON (
s.order_no=o.order_no AND s.entry=(SELECT MAX(entry) FROM status WHERE order_no = o.order_no)
)
Live Demo
https://www.db-fiddle.com/f/twz1TT9VH7YNTY1KrpRAjx/3
Does the last status have a higher entry number or higher date created?
Perhaps include MAX(st.Entry) as last_entry in your SELECT clause,
Maybe select your fields explicitly
vs SELECT *
and include a
GROUP BY
after your WHERE clause
and a
HAVING
after your GROUP BY
create table orders (
entry INT,
order_number INT,
date_placed date,
order_type VARCHAR(1) )
create table order_status (
entry INT,
order_number INT,
order_status VARCHAR(1),
date_created date,
deleted INT
);
INSERT INTO orders (entry, order_number, date_placed, order_type) VALUES (2001, 5, '2021-05-03', 'C');
INSERT INTO order_status (entry, order_number, order_status, date_created, deleted)
VALUES
(2001, 5, 'S', '2001-05-04', 0),
(2002, 5, 'D', '2001-05-05', 0);
SELECT os.entry, o.order_number, o.date_placed, o.order_type,
os.order_status, os.date_created, os.deleted,
MAX(os.entry) as last_entry
FROM orders o
LEFT JOIN order_status os
ON o.order_number = os.order_number
GROUP BY o.order_number
HAVING os.entry = last_entry

sql only return rows that have certain column values

With my tables above how can I return the user_id(s) which belong to companies that are only in type_id = 34 and 35 and not belonging in type_id= 8. So since comp_id = 3 isnt in type_id= 8 and is in type_id= 34 and 35 therefore results should be user_id=104 and 105
Im looking for users who are in companies both type_id=34 and 35 not either or. If it is not in 34 but in 35 then users from that company should not be returned.
You can use conditional aggregation to get the comp_ids that meet your conditions and with the operator IN get the user_ids:
select user_id
from Table_2
where comp_id in (
select comp_id
from Table_1
group by comp_id
having sum(type_id not in (34, 35)) = 0
and sum(type_id in (34, 35)) = 2
)
If there are other type_ids than 34, 35 and 8 and they are also allowed as long as 34 and 35 exist but not 8 then:
select user_id
from Table_2
where comp_id in (
select comp_id
from Table_1
group by comp_id
having sum(type_id = 8) = 0
and sum(type_id in (34, 35)) = 2
)
This is a simple and readable way to do it.
SELECT DISTINCT user_id
FROM Table_2
WHERE comp_id IN (
SELECT DISTINCT comp_id
FROM Table_1
WHERE type_id IN (34, 35)
MINUS
SELECT DISTINCT comp_id
FROM Table_1
WHERE type_id IN (8)
)
If your database doesn't support SELECT DISTINCT then just use SELECT and add a GROUP BY comp_id to the bottom of each SELECT statement. Capitalisation of keywords is optional. If you already have a big WHERE statement for Table_2 you can use an inner join to the sub-select instead of an IN.
-- EDIT: to avoid the use of MINUS for MySQL --
Confession: I'm not able to test this SQL in MySQL at the moment
SELECT DISTINCT Table_2.user_id
FROM Table_2
INNER JOIN
(SELECT DISTINCT comp_id
FROM Table_1
WHERE type_id IN (34, 35)
) type_include ON Table_2.type_id = type_include.type_id
LEFT JOIN
(SELECT DISTINCT comp_id
FROM Table_1
WHERE type_id IN (8)
) type_exclude ON Table_2.type_id = type_exclude.type_id
AND type_exclude.type_id IS NULL
You can do such a thing with a subquery, in which you specify the condition that should not be met
SELECT DISTINCT t2.user_id
FROM Table_2 t2
JOIN Table_1 t1
ON ((t1.comp_id = t2.comp_id)
AND ((type_id = 34) OR (type_id = 35))
AND t1.comp_id NOT IN (SELECT comp_id FROM Table_1 WHERE type_id = 8)
)
;
PS: For the data in your example, you would not even have to check for the ID being equal to 35 or 34 as there are no other values in your data anyway.
SELECT DISTINCT t2.user_id
FROM Table_2 t2
WHERE t2.comp_id NOT IN (SELECT comp_id FROM Table_1 WHERE type_id = 8)
;
EDIT: As was correctly pointed out, the first query will
also include user_ids for which Table_1 only has rows with either type_id = 34 or type_id = 35 and not only with both
also include user_ids for which Table_1 has rows that additionally to 34 or 35 may have another type_id other than 8 (which is not present in the example, but may happen in other data)
This is by design, as I understood the question requiring this. If this is not the intended result, please look at the answer from #forpas (https://stackoverflow.com/a/63382574/14015737), which yields a result that does neither of the above.

SQL Find date range gaps in Table

Good day.
I seem to be struggling with what seems like a simple problem.
I have a table that has a value connected to a date (Monthly) for a finite number of ID's
ie. Table1
ID | Date ---| Value
01 | 2015-01 | val1
01 | 2015-02 | val2
02 | 2015-01 | val1
02 | 2015-03 | val2
So ID: 02 does not have a value for date 2015-02.
I would like to return all ID's and Dates that do not have a value.
Date range is: select distinct date from Table1
I can't seem to think outside the realms of selecting and joining on the same table.
I need to include the ID in my select to I can somehow select the ID and Date range that exists for that ID and compare to the entire date range, to get all the dates for each ID that isn't in the "entire" date range.
Please advise.
Thank you
Not very clear about your last two sentences. But you can play with the following query with different #max_days and #min_date:
-- DROP TABLE table1;
CREATE TABLE table1(ID int not null, `date` date not null, value varchar(64) not null);
INSERT table1(ID,`date`,value)
VALUES (1,'2015-01-01','v1'),(1,'2015-01-02','v2'),(2,'2015-01-01','v1'),(2,'2015-01-03','v2'),(4,'2015-01-01','v1'),(4,'2015-01-04','v2');
SELECT * FROM table1;
SET #day=0;
SET #max_days=5;
SET #min_date='2015-01-01';
SELECT i.ID,d.`date`
FROM (SELECT DISTINCT ID FROM table1) i
CROSS JOIN (
SELECT TIMESTAMPADD(DAY,#day,#min_date) AS `date`,#day:=#day+1 AS day_num
FROM table1 WHERE #day<#max_days) d
LEFT JOIN table1 t
ON t.ID=i.ID
AND t.`date`=d.`date`
WHERE t.`date` IS NULL
ORDER BY i.ID,d.`date`;
I now understand your requirement of dates being taken from the table; you want to find any gaps in the date ranges for each id.
This does what you need, but can probably be improved. Explanation below and you can view a working example.
DROP TABLE IF EXISTS Table1;
DROP TABLE IF EXISTS Year_Month_Calendar;
CREATE TABLE Table1 (
id INTEGER
,date CHAR(7)
,value CHAR(4)
);
INSERT INTO Table1
VALUES
(1,'2015-01','val1')
,(1,'2015-02','val2')
,(2,'2015-01','val1')
,(2,'2015-03','val1');
CREATE TABLE Year_Month_Calendar (
date CHAR(10)
);
INSERT INTO Year_Month_Calendar
VALUES
('2015-01')
,('2015-02')
,('2015-03');
SELECT ID_Year_Month.id, ID_Year_Month.date, Table1.id, Table1.date
FROM (
SELECT Distinct_ID.id, Year_Month_Calendar.date
FROM Year_Month_Calendar
CROSS JOIN
( SELECT DISTINCT id FROM Table1 ) AS Distinct_ID
WHERE Year_Month_Calendar.date >= (SELECT MIN(date) FROM Table1 WHERE id=Distinct_ID.ID)
AND Year_Month_Calendar.date <= (SELECT MAX(date) FROM Table1 WHERE id=Distinct_ID.ID)
) AS ID_Year_Month
LEFT JOIN Table1
ON ID_Year_Month.id = Table1.id AND ID_Year_Month.date = Table1.date
-- WHERE Table1.id IS NULL
ORDER BY ID_Year_Month.id, ID_Year_Month.date
Explanation
You need a calendar table which contains all dates (year/months) to cover the data you are querying.
CREATE TABLE Year_Month_Calendar (
date CHAR(10)
);
INSERT INTO Year_Month_Calendar
VALUES
('2015-01')
,('2015-02')
,('2015-03');
The inner select creates a table with all dates between the min and max date for each id.
SELECT Distinct_ID.id, Year_Month_Calendar.date
FROM Year_Month_Calendar
CROSS JOIN
( SELECT DISTINCT id FROM Table1 ) AS Distinct_ID
WHERE Year_Month_Calendar.date >= (SELECT MIN(date) FROM Table1 WHERE id=Distinct_ID.ID)
AND Year_Month_Calendar.date <= (SELECT MAX(date) FROM Table1 WHERE id=Distinct_ID.ID)
This is then LEFT JOINED to the original table to find the missing rows.
If you only want to return the missing row (my query displays the whole table to show how it works), add a WHERE clause to restrict the output to those rows where an id and date is not returned from Table1
Original answer before comments
You can do this without a tally table, since you say
Date range is: select distinct date from Table1
I've slightly changed the field names to avoid reserved words in SQL.
SELECT id_table.ID, date_table.`year_month`, table1.val
FROM (SELECT DISTINCT ID FROM table1) AS id_table
CROSS JOIN
(SELECT DISTINCT `year_month` FROM table1) AS date_table
LEFT JOIN table1
ON table1.ID=id_table.ID AND table1.`year_month` = date_table.`year_month`
ORDER BY id_table.ID
I've not filtered the results, in order to show how the query is working. To return the rows where only where a date is missing, add WHERE table1.year_month IS NULL to the outer query.
SQL Fiddle
You will need a tally table(s) or month/year tables. So you can then generate all of the potential combinations you want to test with. As far as exactly how to use it your example could use some expanding on such as last 12 months, last3 months, etc. but here is an example that might help you understand what you are looking for:
http://rextester.com/ZDQS5259
CREATE TABLE IF NOT EXISTS Tbl (
ID INTEGER
,Date VARCHAR(10)
,Value VARCHAR(10)
);
INSERT INTO Tbl VALUES
(1,'2015-01','val1')
,(1,'2015-02','val2')
,(2,'2015-01','val1')
,(2,'2015-03','val1');
SELECT yr.YearNumber, mn.MonthNumber, i.Id
FROM
(
SELECT 2016 as YearNumber
UNION SELECT 2015
) yr
CROSS JOIN (
SELECT 1 MonthNumber
UNION SELECT 2
UNION SELECT 3
UNION SELECT 4
UNION SELECT 5
UNION SELECT 6
UNION SELECT 7
UNION SELECT 8
UNION SELECT 9
UNION SELECT 10
UNION SELECT 11
UNION SELECT 12
) mn
CROSS JOIN (
SELECT DISTINCT ID
FROM
Tbl
) i
LEFT JOIN Tbl t
ON yr.YearNumber = CAST(LEFT(t.Date,4) as UNSIGNED)
AND mn.MonthNumber = CAST(RIGHT(t.Date,2) AS UNSIGNED)
AND i.ID = t.ID
WHERE
t.ID IS NULL
The basic idea to determine what you don't know is to generate all possible combinations of something could be. E.g. Year X Month X DISTINCT Id and then join back to figure out what is missing.
Probably not the prettiest but this should work.
select distinct c.ID, c.Date, d.Value
from (select a.ID, b.Date
from (select distinct ID from Table1) as a, (select distinct Date from Table1) as b) as c
left outer join Table1 d on (c.ID = d.ID and c.Date = d.Date)
where d.Value is NULL

SQL Group By And Display With Different Values

I want to create group query where Table values are like this below:
EMP_ID ProjectID
815 1
985 1
815 3
985 4
815 4
And i want output like this
EMP_ID ProjectID1 ProjectID2 ProjectID3
815 1 3 4
985 1 4 0
can anyone know how can i achieve this thing in SQL query.
Thank in advance.
The short way:
Using http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat
SELECT
tbl.emp_id,
GROUP_CONCAT( DISTINCT project_id ) project_id_list
FROM tbl
GROUP BY tbl.emp_id
In this case, you have to split/process the concatenated project_id_list string (or NULL) in your application
The long way:
We will use a little trick:
http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html
For MyISAM tables you can specify AUTO_INCREMENT on a secondary column
in a multiple-column index. In this case, the generated value for the
AUTO_INCREMENT column is calculated as MAX(auto_increment_column) + 1
WHERE prefix=given-prefix. This is useful when you want to put data
into ordered groups.
CREATE TEMPORARY TABLE temp (
emp_id INT NOT NULL,
-- project_num will count from 1 to N PER emp_id!
project_num INT NOT NULL AUTO_INCREMENT,
project_id INT NOT NULL,
PRIMARY KEY ( emp_id, project_num )
) ENGINE=MyISAM; -- works only with myisam!
Generate the per-group auto increments:
INSERT INTO temp ( emp_id, project_id )
SELECT emp_id, project_id FROM tbl
Calculate how many project_id columns are needed:
$MAX_PROJECTS_PER_EMP =
SELECT MAX( max_projects_per_emp ) FROM
( SELECT COUNT(*) AS max_projects_per_emp project_id FROM tbl GROUP BY emp_id )
Programmatically create the select expression:
SELECT
temp.emp_id,
t1.project_id AS project_id_1,
t2.project_id AS project_id_2,
t98.project_id AS project_id_98,
t99.project_id AS project_id_99,
FROM temp
LEFT JOIN temp AS t1 ON temp.emp_id = t1.id AND t1.project_num = 1
LEFT JOIN temp AS t2 ON temp.emp_id = t2.id AND t1.project_num = 2
// create $MAX_PROJECTS_PER_EMP lines of LEFT JOINs
LEFT JOIN temp AS t98 ON temp.emp_id = t98.id AND t98.project_num = 98
LEFT JOIN temp AS t99 ON temp.emp_id = t99.id AND t99.project_num = 99

SQL - Update multiple fields per row from the same lookup table without multiple joins

I have a temp table similar to this
create table #temp
(
log_id int,
...some other fields,
one_user_id int,
two_user_id int,
three_user_id int,
four_user_id int,
one_username varchar(50),
two_username varchar(50),
three_username varchar(50),
four_username varchar(50)
)
I start out knowing all the user ids, but then I need to look up their names in a user lookup table and update the name fields in the temp table.
create table #user_lookup
(
user_id int,
username varchar(50)
)
I know I could join to the user lookup table once for every id using a different alias to get them all, but I was looking for a slick way to do it just once.
Any ideas ?
EDIT:
Ok, more info on the purpose for multiple users per row. The #temp table row (not all fields displayed) signifies a log entry that represents a collation of multiple actions by potentially multiple users, but all tying to that one log row.
I could have duplicate log rows, one for each user who played a role, but it's easier to consume on the client side as single rows.
This is why there are multiple users per row.
I think this should work:
UPDATE temp
SET one_username = u1.username
, two_username = u2.username
, three_username = u3.username
, four_username = u4.username
FROM #temp as temp
join #user_lookup as u1 on u1.user_id = temp.one_user_id
join #user_lookup as u2 on u2.user_id = temp.two_user_id
join #user_lookup as u3 on u3.user_id = temp.three_user_id
join #user_lookup as u4 on u4.user_id = temp.four_user_id
But I don't know why you have four users in one table... ;)
The only other real alternative solution is to pull in the related records with an IN clause and make use of CASE statements to tie the usernames with the correct user_id's. However this is way more complicated than simply using a JOIN statement and doesn't really offer any advantage except that there aren't multiple JOIN's involved. Here is a complete working sample of how to pull data using this structure:
create table #temp
(
one_user_id int,
two_user_id int,
three_user_id int,
four_user_id int,
one_username varchar(50),
two_username varchar(50),
three_username varchar(50),
four_username varchar(50)
)
insert #temp (one_user_id, two_user_id, three_user_id, four_user_id) values (1, 3, 6, 7)
insert #temp (one_user_id, two_user_id, three_user_id, four_user_id) values (2, 5, 8, 1)
;with User_Lookup as (
select 1 as user_id, 'abc' as username union
select 2, 'def' union
select 3, 'ghi' union
select 4, 'jkl' union
select 5, 'mno' union
select 6, 'pqr' union
select 7, 'stu' union
select 8, 'vwx' union
select 9, 'jon' union
select 10, 'bob'
), Result as (
select
one_user_id,
two_user_id,
three_user_id,
four_user_id,
max(case when U.user_id = one_user_id then U.username end) as one_username,
max(case when U.user_id = two_user_id then U.username end) as two_username,
max(case when U.user_id = three_user_id then U.username end) as three_username,
max(case when U.user_id = four_user_id then U.username end) as four_username
from
#Temp T,
User_Lookup U
where
U.user_id in (T.one_user_id, T.two_user_id, T.three_user_id, T.four_user_id)
group by
T.one_user_id, T.two_user_id, T.three_user_id, T.four_user_id
)
update
#temp
set
one_username = R.one_username,
two_username = R.two_username,
three_username = R.three_username,
four_username = R.four_username
from
Result R
inner join
#temp T on R.one_user_id=T.one_user_id and R.two_user_id=T.two_user_id
and R.three_user_id=T.three_user_id and R.four_user_id=T.four_user_id
select * from #temp
drop table #temp
Output:
one_user_id two_user_id three_user_id four_user_id one_username two_username three_username four_username
1 3 6 7 abc ghi pqr stu
2 5 8 1 def mno vwx abc