I have a list of item descriptions in a c# application. What I want is when I select
1 or 2 or more item descriptions of that list (checkbox list) to predict via an sql query to a many to many table what my item is (minimizing each time the possible predictions);
For example
item 1: white,green,blue
item 2: white,red,cyan
item 3: red,blue,purple
user should select from a check list
white->query will return item 1,2
white&green->query will return only item 1
From your humble description of the problem, I suppose you want something like this:
CREATE TABLE items (
item_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(100) NOT NULL
)
CREATE TABLE colors (
color_id INT NOT NULL PRIMARY KEY IDENTITY(1,1),
name VARCHAR(100) NOT NULL
)
CREATE TABLE items_colors (
item_id INT NOT NULL FOREIGN KEY REFERENCES items(item_id),
color_id INT NOT NULL FOREIGN KEY REFERENCES colors(color_id),
PRIMARY KEY(item_id, color_id),
)
INSERT INTO items(name) VALUES ('item 1')
INSERT INTO items(name) VALUES ('item 2')
INSERT INTO items(name) VALUES ('item 3')
INSERT INTO colors(name) VALUES ('white')
INSERT INTO colors(name) VALUES ('green')
INSERT INTO colors(name) VALUES ('blue')
INSERT INTO colors(name) VALUES ('red')
INSERT INTO colors(name) VALUES ('cyan')
INSERT INTO colors(name) VALUES ('purple')
INSERT INTO items_colors(item_id, color_id) VALUES (1, 1)
INSERT INTO items_colors(item_id, color_id) VALUES (1, 2)
INSERT INTO items_colors(item_id, color_id) VALUES (1, 3)
INSERT INTO items_colors(item_id, color_id) VALUES (2, 1)
INSERT INTO items_colors(item_id, color_id) VALUES (2, 4)
INSERT INTO items_colors(item_id, color_id) VALUES (2, 5)
INSERT INTO items_colors(item_id, color_id) VALUES (3, 3)
INSERT INTO items_colors(item_id, color_id) VALUES (3, 4)
INSERT INTO items_colors(item_id, color_id) VALUES (3, 6)
SELECT i.*
FROM items i
WHERE 2 = (
SELECT COUNT(*)
FROM items_colors ic
JOIN colors c
ON ic.color_id = c.color_id
WHERE i.item_id = ic.item_id
AND c.name IN ('white', 'green')
)
Within "IN" clause you should provide list of values that user has selected in the UI (you have to build list of parameters dynamically). You also have to provide number of elements that user has selected ("2" in my example solution).
So the query in application will look like this:
SELECT i.*
FROM items i
WHERE #count = (
SELECT COUNT(*)
FROM items_colors ic
JOIN colors c
ON ic.color_id = c.color_id
WHERE i.item_id = ic.item_id
AND c.name IN (#color1, #color2, ..., #colorN)
)
(Where #count is the number of #colorX parameters.)
Related
I have to convert an int column to a text column and replace the integer values within that column.
For example I have a column status that can contain values like 0, 1, 2, 3, 4, 5, 6, 7.
For '0' I have to replace the value with "New", for '1' with "Identified" and so on.
For this scenario how to write a SQL query?
Personally, I'd go with a mapping table, but another option is CHOOSE() or even the traditional CASE
Note the +1 in the CHOOSE option ... 0 is not a valid option and would return NULL
Example
Declare #YourTable Table ([Status] int)
Insert Into #YourTable Values
(0),(1),(2),(3)
Select *
,ViaCHOOSE = choose([Status]+1,'New','Identified','Some Other','...')
,ViaCASE = case [Status]
when 0 then 'New'
when 1 then 'Identified'
when 2 then 'Some Other'
else null -- or 'Undefined'
end
From #YourTable
Results
Status ViaCHOOSE ViaCASE
0 New New
1 Identified Identified
2 Some Other Some Other
3 ... ...
You could create a (temporary) table with that mapping.
create table XYMapping (number int, text varchar(max));
INSERT INTO XYMapping (number, text) VALUES (1, 'New'), (2, 'Identified'); -- ...
Insert all values and then join them.
Steps to follow to convert int column to text and replace the existing values.
Alter the table. Since INT converts to text implicitly NOT NULL is able to be maintained
Exec UPDATE statement using conversion table specified using VALUES table value constructor
Something like this
drop table if exists #samples;
go
create table #samples (
id int not null,
stat varchar(10) not null);
insert #samples(id, stat) values
(10, 'Start'),
(1, 'Start'),
(1, 'Failed'),
(2, 'Start'),
(3, 'Start'),
(3, 'Failed'),
(4, 'Start'),
(4, 'Failed'),
(4, 'Start'),
(4, 'Failed');
/* step 1: alter the table (and implicitly convert) */
alter table #samples
alter column
id varchar(20) not null;
/* step 2: update based on conversion table */
update s
set id=v.new_str
from #samples s
join
(values ('1', 'New'),
('2', 'Identified'),
('3', 'Identified'),
('4', 'Identified'),
('10', 'Other')) v(old_int, new_str) on s.id=v.old_int;
select * from #samples;
id stat
Other Start
New Start
New Failed
Identified Start
Identified Start
Identified Failed
Identified Start
Identified Failed
Identified Start
Identified Failed
I need to find a relation between multiple person in single table, for example I have the below table:
Guests Table
so I need by sql script to say Guest 123 and 456 they checked in together to the same hotel in the same time 80% and so on...
Kindly support.
It's a little complicated so I've broken it down into multiple subqueries for you using a CTE with a matched key.
This will produce a series of matched pairs - for the primary guest and secondary guest with ratios of how often they stay together rather than just check in.
Setup:
create table temp(
hotelID integer,
checkInDate date,
guestID integer
)
insert into temp values (101, '2020/06/01', 123)
insert into temp values (101, '2020/06/01', 456)
insert into temp values (102, '2020/06/15', 123)
insert into temp values (102, '2020/06/15', 456)
insert into temp values (103, '2020/06/30', 123)
insert into temp values (103, '2020/06/30', 456)
insert into temp values (104, '2020/07/15', 123)
insert into temp values (104, '2020/07/15', 789)
insert into temp values (105, '2020/07/01', 456)
insert into temp values (105, '2020/07/01', 789)
Query:
with keyCte as (
select
distinct cast(hotelID as varchar(3)) + cast(checkInDate as varchar(10)) as myKey,
guestID
from temp
)
select
guestPrime
, guestTwo
, instances as guestPrimeStays
, matches as guestTwoMatches
, cast(matches as float) / cast(instances as float) as hitRate
from (
select
guestID
, count(*) as instances
from keyCte
group by guestID
) sq3
join (
select
guestPrime
, guestTwo
, count(*) as matches
from (
select
keyCte.guestID as guestPrime
, kcte.guestID as guestTwo
from keyCte
join keyCte kcte on kcte.myKey = keyCte.myKey and kcte.guestID != keyCte.guestID
) sq
group by guestPrime, guestTwo
) sq2 on sq2.guestPrime = guestID
I need to use a function in an update of several records.
How to make a T-SQL function read values which have been updated during that update?
Below I created a simple example demonstrating what I get.
My example: table has Category column and I build code based on a category. Function finds the last one withing the category + 1. Or 10000 if it is the first record.
It would work just fine if update is a single record, but updates all records with the same value otherwise.
Below is working code:
CREATE TABLE Tbl
(
Id INT NOT NULL PRIMARY KEY,
Category CHAR(1) NOT NULL,
Code INT NULL
)
CREATE FUNCTION dbo.GetNextAvailableCode
(#Category CHAR(1))
RETURNS INT
AS
BEGIN
DECLARE #MaxCode INT
SELECT #MaxCode = MAX(Code)
FROM dbo.Tbl
WHERE Category = #Category
SET #MaxCode = ISNULL(#MaxCode + 1, 10000);
RETURN #MaxCode
END;
GO
INSERT INTO Tbl (Id, Category)
VALUES (1, 'A'), (2, 'A'), (3, 'A'),
(4, 'B'), (5, 'B'),
(6, 'C'), (7, 'C'), (8, 'C'), (9, 'C')
SELECT * FROM Tbl
UPDATE dbo.Tbl
SET Code = dbo.GetNextAvailableCode(Category)
WHERE Code IS NULL
SELECT * FROM Tbl
GO
DROP TABLE Tbl;
DROP FUNCTION dbo.GetNextAvailableCode;
Here is the result I'm getting:
What I'd like to get is the following:
But it's possible only if next function call can see already changed values...
Any idea how to implement such thing?
Since it is not possible to use function to achieve the desired effect, I re-wrote the update to be used without function and it worked!
Here is the update which produces the result I need:
;WITH data AS (
SELECT Id, Category, Code, ROW_NUMBER() OVER (PARTITION BY Category ORDER BY Id) AS RowNo
FROM dbo.Tbl
WHERE Code IS NULL
)
, maxData AS (
SELECT Category, MAX(Code) AS MaxCode
FROM dbo.Tbl
GROUP BY Category
)
UPDATE S
SET Code = ISNULL(T.MaxCode, 10000) + S.RowNo
FROM data S JOIN maxData T ON T.Category = S.Category
WHERE S.Code IS NULL
Result:
I want to perform a JOIN on two tables to get the LEADTIME per ITEMNUM.
Both tables have common value ITEMNUM that I use for the JOIN operation.
The problem is that in the second table the ITEMNUM is not unique and can contain multiple. different LEADTIME values.
For example see ITEMNUM 2 in Table 2.
In case there are multiple LEADTIME values, I just want to get one of the LEADTIME values.
I don't care which one.
This is what I have so far, but it keeps returning multiple lines for ITEMNUM 2
SELECT ITEMNUM, LEADTIME
FROM TABLE1
LEFT JOIN TABLE2 on TABLE2.ITEMNUM = TABLE1.ITEMNUM
So what can I do to get just one LEADTIME for ITEMNUM 2? ( as mentioned, I don't care which value )
This approach assigns a row number to each row in #table2 resetting it for each ItemNum value. You need to have an order by clause (if you don't SQL Server raises an error) so I am ordering by NEWID() which should result in a randomized order. You will likely want to tweak what columns you are returning. Here is the dbfiddle to see it in action.
IF OBJECT_ID('tempdb.dbo.#table1', 'U') IS NOT NULL DROP TABLE #table1;
IF OBJECT_ID('tempdb.dbo.#table2', 'U') IS NOT NULL DROP TABLE #table2;
CREATE TABLE #table1
(
ID INT
, ItemNum INT
);
CREATE TABLE #table2
(
ID INT
, ItemNum INT
, LeadTime INT
);
INSERT INTO #table1 VALUES (1, 1)
INSERT INTO #table1 VALUES (2, 2)
INSERT INTO #table1 VALUES (3, 3)
INSERT INTO #table1 VALUES (4, 4)
INSERT INTO #table1 VALUES (5, 5)
INSERT INTO #table2 VALUES (1, 1, 6)
INSERT INTO #table2 VALUES (2, 2, 7)
INSERT INTO #table2 VALUES (3, 2, 2)
INSERT INTO #table2 VALUES (4, 3, 6)
INSERT INTO #table2 VALUES (5, 4, 3)
SELECT *
FROM #table1 AS t1
LEFT JOIN (
SELECT *
, ROW_NUMBER() OVER (PARTITION BY ItemNum ORDER BY NEWID()) AS rn
FROM #table2
) AS t2 ON t1.ItemNum = t2.ItemNum
AND t2.rn = 1;
There are several ways to get this done.I would use OUTER APPLY with TOP.
DROP TABLE IF EXISTS #Table1
CREATE TABLE #Table1
(
Id INT
, ItemNum INT
)
DROP TABLE IF EXISTS #Table2
CREATE TABLE #Table2
(
Id INT
, ItemNum INT
, LeadTime INT
)
INSERT INTO #Table1 VALUES
(1, 1)
, (2, 2)
, (3, 3)
, (4, 4)
, (5, 5)
INSERT INTO #Table2 VALUES
(1, 1, 6)
, (2, 2, 7)
, (3, 2, 2)
, (4, 3, 6)
, (5, 4, 3)
SELECT
*
FROM
#Table1 AS T1
OUTER APPLY
(
SELECT TOP 1 T2.LeadTime FROM #Table2 AS T2 WHERE T2.ItemNum = T1.ItemNum
) AS LT
For a time scheduling project, I have two tables: tbl_timeslots which holds available times with slotid as the primary key and totalmembers which counts the number of appointments made for this slot, and tbl_appointments with primary key apptid which holds the actual appointments, with slotid as a foreign key linking to the slot information.
I need to automatically update the totalmembers column any time an appointment is created/deleted/changed. The trigger I wrote (shown below) does not update the correct number of appointments in the tbl_timeslots column totalmembers.
CREATE TABLE tbl_timeslots
(
slotid int ,
fromdate datetime ,
todate datetime ,
totalmembers int
)
INSERT tbl_timeslots (slotid, fromdate, todate, totalmembers)
VALUES (1, '2016-01-01 10:00:00', '2016-01-01 11:00:00', 0)
INSERT tbl_timeslots (slotid, fromdate, todate, totalmembers)
VALUES (2, '2016-01-01 11:00:00', '2016-01-01 12:00:00', 0)
CREATE TABLE tbl_appointments
(
apptid int ,
slotid int ,
firstname varchar(10) ,
lastname varchar(10)
)
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (1, 1, 'Mark', 'Twain')
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (2, 1, 'Thomas', 'Jefferson')
INSERT tbl_appointments (apptid, slotid, firstname, lastname)
VALUES (3, 2, 'Donald', 'Duck')
CREATE TRIGGER [dbo].[tr_totalmembers]
ON [dbo].[TBL_appointments]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
UPDATE tbl_timeslots
SET totalmembers = (SELECT COUNT(1)
FROM tbl_appointments a
WHERE tbl_timeslots.slotid = a.slotid)
FROM inserted i
INNER JOIN deleted d ON i.apptid = d.apptid
WHERE
d.slotid <> i.slotid
AND (tbl_timeslots.slotid = i.slotid OR tbl_timeslots.slotid = d.slotid)
END
I just modified the trigger a bit and it worked for me :
CREATE TRIGGER [dbo].[tr_totalmembers]
ON [dbo].[TBL_appointments]
AFTER UPDATE, INSERT, DELETE
AS
BEGIN
UPDATE tbl_timeslots
SET totalmembers = a.cnt
from
(
SELECT slotid,COUNT(1) as cnt
FROM tbl_appointments
group by slotid
) a
WHERE
tbl_timeslots.slotid=a.slotid
END
Or in case if there is a specific problem please mention so that we could look into it .
A better way to compute totalmembers is to create and use an indexed view:
CREATE VIEW dbo.vw_timeslots_with_totalmembers
WITH SCHEMA_BINDING
AS
SELECT a.slotid, COUNT_BIG(*) AS totalmembers
FROM dbo.tbl_appointments a
GROUP BY a.slotid
GO
CREATE UNIQUE CLUSTERED INDEX IUC_vw_timeslots_with_totalmembers_slotid
ON dbo.vw_timeslots_with_totalmembers (slotid)
GO
DECLARE #slotid INT = 123
SELECT totalmembers
FROM dbo.vw_timeslots_with_totalmembers WITH(NOEXPAND) -- This table hint is needed in order to force usage of indexed view
WHERE slotid = #slotid
GO
Note: please read following notes regarding the proper configuration of SETtings (see section Required SET Options for Indexed Views): https://msdn.microsoft.com/en-us/library/ms191432.aspx .
Note #2: if last SELECT statement returns 0 rows this means that current slot doesn't have appointments (I assume that current slot is valid).