Query Detail Table
Main Table
1 .PK
Detail Table
1. PK
2. Detail description.
If mysql i can group the main table PK for duplicate issue but how it implement in sql server ?Since sql server required something like aggregate
E.g
(vehicle) Main Table
Car
bikes
van
(vehicleItem) Detail Table
Car->item a,item b
bikes-> item a,item c..
van->item b ,item c
I want to filter in a query such as i want to filter item a only?Seem quite inefficient to call in sub loop.Even put all detail item it in a column consider as incorrect but when search item id(number) might be same number unless the item id are guid and distinct different.
** the main purpose is to output list not to sum crosstab :) ya.. grouping cool if you want to sum up.
var sql = "select * from vehicle ";
var command = new SqlCommand(connection,sql);
try {
var reader = command.ExecuteReader();
if (reader.HasRows) {
while (reader.Read())
if (!string.IsNullOrEmpty(Request.QueryString["ItemIdValue[]"])){
var d = Request.QueryString.GetValues("ItemIdValue[]");
if (d != null){
if (d[0].Contains("all")){
if (GetItemExist(" AND vehicleItem.itemId in (SELECT itemId from item ) ")){ }
}else{
if (GetItemExist(" AND vehicleItem.itemId IN ( " + itemFilter.Remove(itemFilter.Length - 1) + " ) ")){ }
}
}
}
}
}
}
At last thinking back, why not select main table and in subquery..
var sql = "select * from vehicle ";
if (!string.IsNullOrEmpty(Request.QueryString["itemIdValue[]"])){
var d = Request.QueryString.GetValues("itemIdValue[]");
if (d != null){
if (d[0].Contains("all")){
sql = sql + #"
AND vehicleId IN (
SELECT vehicleId
FROM vehicle
JOIN vehicleItem
ON vehicle.vehicleId = vehicleItem.vehicleId
WHERE vehicleItem.itemId in
(SELECT itemId from item)
) ";
} else{
for (var e = 0; e < d.Length; e++){
itemFilter += d[e] + ",";
_fieldVariable.Add("itemIdValue[]");
_valueVariable.Add(d[e]);
}
sql = sql + #"
AND vehicleId IN (
SELECT vehicleId
FROM vehicle
JOIN vehicleItem
ON vehicle.vehicleId = vehicleItem.vehicleId
WHERE vehicleItem.itemId in ( " + itemFilter.Remove(itemFilter.Length - 1) + " ) ) ";
}
}
}
When joining two tables t1 and t2, a result set containing all matching rows from table t1 and table t2 is created. When you only want one resulting row per t1's row, it is up to you to filter the join accordingly.
There are several possible ways to do that:
adapt the join's ON clause
group accordingly
specify a WHERE clause
As you wanted to use grouping in your request, you could use a query like this:
SELECT m.PK, min(d.Detail)
FROM Main m
LEFT JOIN Detail d on d.PK=m.pk
WHERE d.Detail = 'item a'
GROUP by m.PK
That way you will get one row per PK with the minimum value in detail. You can use other aggregate functions too. See https://msdn.microsoft.com/en-us/library/ms173454.aspx.
Edit: Added WHERE clause to filter by "item a".
Related
I am new to C# (has experience in other languages before, C++, SQL AutoIT). I have a datatable with 10 columns
Name, MemberNoA, MemberNoB, DriverLicense, MobileNo, Address1, Address2, Address3, ProgramJoinned, Remark
The datatable has around 17,000 rows, what I want to do is, if the same person's records appear more than 2 times in the datatable, put a description in a remark field.
4 criteria to define "same person", any one criteria match will treat as "same person"
i Name + MemberNoA
ii Name + MemberNoB
iii Name + DriverLicense
iv Name + MobileNo
i.e. if there are 3 records with same Name and same MemberNoA, need to put description into remark field of these 3 records.
I work out result set from the above 4 criteria like this:
var resultCriteria1 = from gpr in dt.AsEnumerable()
group gpr by new {
Fld1 = gpr.Field < string > ("Name"),
Fld2 = gpr.Field < string > ("MemberNoA")
}
into grpp
where grpp.Count() > 2
select new {
Name = grpp.Key.Fld1,
MemA = grpp.Key.Fld2,
Cnt = grpp.Count()
};
after that, I loop thru all rows in dt and for each row, loop thru all result set in 4 criteria:
for (int i = 1; i < dt.Rows.Count; i++) {
foreach(var item in resultCriteria1) {
if ((item.Nam == s trName) && (item.MemA == M emberNoA)) {
dt.Rows[i].ItemArray[9] = d t.Rows[i].ItemArray[9] + "Criteria 1 match\r\n";
}
}
}
The program work but run very slow! Is there any method like simple sql statement:
update table1 where
table1.name = table2.name and
table1.MemberNoA = table2.MemberNo2
set table1.Remark = "Criteria 1 match\r\n"
Is there any way to do this in C# or any way to optimize it ? Thanks.
Regds
LAM Chi-fung
The problem is that you are making a cartesian join between the grouped results and the original datatable, without using any performant data structures such as a dictionary or hashset.
But you don't actually need to join it at all, the grouped results can actually hold the relevant data rows directly.
The following code should be performant enough
var grouped =
from gpr in dt.Rows.Cast<DataRow>()
group gpr by (
Fld1: (string)gpr["Name"],
Fld2: (string)gpr["MemberNoA"]
)
into grpp
where grpp.Count() > 2
select grpp;
foreach (var grp in grouped)
foreach (var row in grp)
row["Remark"] += "Criteria 1 match\r\n";
What you can do is to use hashtable, order your data, and then iterate comparing current row data with previous using a cursor. This should give you Log(n) time complexity.
I have two different tables from a DataBase named "empleados" and "fichajes". empleados has the employees data and fichajes has the date and time from when they started working.
I want to get the total work time done by a specific employee between two dates, lets say from 20th to 29th.
I have this query which I use with Dapper on C#:
SELECT CONCAT(e.nombre, " " ,e.apellido) as fullName, tfichajes.total
FROM empleados e
INNER JOIN (
SELECT f1.nif,
SEC_TO_TIME(SUM(TIME_TO_SEC(f1.fechasalida) - TIME_TO_SEC(f1.fechaentrada))) AS total
FROM fichajes f1
WHERE f1.fechasalida <= '2019-04-29'
and f1.fechaentrada >= '2019-04-20'
GROUP BY f1.nif
) AS tfichajes
ON e.nif = tfichajes.nif
WHERE e.nif = '33333333P'
This works just fine, but I was wondering if it was possible to make it simpler.
This is the code I have in my program:
public static List<string> CalculaTotalHoras(string nif, DateTime fechaEntrada, DateTime fechaSalida)
{
var dbCon = DBConnection.Instancia();
if (dbCon.Conectado())
{
string format = "yyyy-MM-dd";
List<string> result = new List<string>();
using (IDbConnection conexion = dbCon.Conexion)
{
var output = conexion.Query($"SELECT CONCAT(e.nombre, \" \" ,e.apellido) as fullName, tfichajes.total " +
$"FROM empleados e INNER JOIN (SELECT f1.nif, SEC_TO_TIME(SUM(TIME_TO_SEC(f1.fechasalida) - TIME_TO_SEC(f1.fechaentrada))) AS total " +
$"FROM fichajes f1 where f1.fechasalida <= '{fechaSalida.ToString(format)}' and f1.fechaentrada >= '{fechaEntrada.ToString(format)}' GROUP BY f1.nif) " +
$"as tfichajes ON e.nif = tfichajes.nif where e.nif = '{ nif }';").ToList();
var i = 0;
foreach (IDictionary<string, object> row in output)
{
foreach (var pair in row)
{
if (i == 0)
{
result.Add(pair.Value.ToString());
i++;
}
else
{
result.Add(pair.Value.ToString());
}
}
}
return result;
}
}
else return null;
}
If you have problems with the readability of the code here you have a gyazo.
Workingtime fichajes table and employees empleados table.
With that exact query, the expected results are Alvaro Arguelles 00:05:00, and in the code, I want to get Alvaro Arguelles and 00:05:00 separated in the result List.
Did I made it much harder than it actually is?
Your query can be significantly simplified. Your inner query is selecting ALL employees working in the date in question, I would start with the INNER query as the main FROM table getting the data for the one employee. THEN join to the employee to grab the name.
select
CONCAT(e.nombre, " " ,e.apellido) as fullName,
OneEmp.Total
from
( select
f1.nif,
SEC_TO_TIME( SUM(TIME_TO_SEC(f1.fechasalida)
- TIME_TO_SEC(f1.fechaentrada))) AS total
from
fichajes f1
where
f1.nif = '33333333P'
AND f1.fechasalida >= '2019-04-20'
AND f1.fechasalida <= '2019-04-29' ) OneEmp
JOIN empleados e
on OneEmp.nif = e.nif
Since your query was summarizing per the one employee, it would only return a single row anyhow. So the above query will return 1 row, 2 columns... name and total. Should be able to read those two values back directly.
But as others commented, PARAMETERIZE your query calls.
I have a list of product records that I am displaying on a dataTable, that are grouped by department name / id. From the image below my returned data does not include the highlighted rows. I am trying to achieve whats shown in the image in that - for each set of products already grouped by department (deserts, stationery, household) get the totals, and name of department (and other columns which I removed to show less clutter) to append to the list
I am trying to achieve the outcome above
This is my current code/query which returns this data without the highlighted rows
List<SaleDetailViewModel> list = new List<SaleDetailViewModel>();
NpgsqlCommand query = new NpgsqlCommand
("SELECT q_department.q_name, q_saledetail.q_code, q_saledetail.q_description, " +
"SUM(q_quantity) AS qtysum, " + //running total of q_quantity for the product in that department
"FROM q_saledetail " +
"LEFT JOIN q_department ON (q_saledetail.q_departmentid = q_department.q_code ) " +
"GROUP BY " +
"q_saledetail.q_departmentid, q_department.q_name, q_saledetail.q_description, q_saledetail.q_code " +
"ORDER BY q_saledetail.q_departmentid "
, connectToDB);
NpgsqlDataReader read = query.ExecuteReader();
while(read.Read())
{
var reportData = new SaleDetailViewModel();
reportData.departmentName = read["q_name"].ToString(); //department name
reportData.q_description = read["q_description"].ToString(); //product DESCRIPTION
reportData.q_code = read["q_code"].ToString(); //product BAR CODE
reportData.sumOfQuantity = Convert.ToDecimal(read["qtysum"]); //sum of quantity sold for that product
list.Add(reportData);
}
connectToDB.Close();
return list;
My challenge now is adding the grouped department row data for each set of products, and append to the list that will be returned i.e
//--
foreach(grouped dataset by department)
{
//get the totals and heading for each set of data
reportData.q_code = //insert department id
reportData.q_description = //insert department name
reportData.sumOfQuantity = // insert total for that department
list.Add(reportData) //add that to the list that will be shown on view
}
//--
I can get department id and code for the grouped data using read.GetString().
string deptName = read.GetString(0);
How do I continue to loop through the dataReader and add totals for each overall set like the quantity columns and have that as a row of its own to be added to the list?... to get the result from the image shown.
Attempt to explain a bit better:
Within my while loop how do I aggregate the set for each department group of products and create a row to add to the list. Or there's a better way of achieving this..
Option 1: Sql and union.
SELECT
q_department.q_name,
q_saledetail.q_code,
q_saledetail.q_description,
SUM(q_quantity) AS qtysum, --running total of q_quantity for the product in that department
FROM q_saledetail
LEFT JOIN q_department ON (q_saledetail.q_departmentid = q_department.q_code )
GROUP BY
q_saledetail.q_departmentid, q_department.q_name, q_saledetail.q_description, q_saledetail.q_code
UNION
SELECT
q_department.q_name,
'',
'',
sum(q_quantity)
FROM q_saledetail
LEFT JOIN q_department ON (q_saledetail.q_departmentid = q_department.q_code )
GROUP BY
q_saledetail.q_departmentid, q_department.q_name
ORDER BY q_saledetail.q_departmentid
By Removing some of the fields that identify different q_saledetail from the group by list, the sum function will sum for all departments instead of breaking the sums up.
Sorry but I can't remember exactly how ordering works with union statements off hand, but I know you can only order
in one place, either the last statement, or the first, or with an outer query that selects from the union.
What you'll have in your data now, is rows that are the 'total' rows for each department, that you know are
'total' rows because the q_code or q_description fields are blank. You can then order the data set by department and then by code (asc or desc) so that the empty code field is at the end of the group of that department and display it like that.
Option 2: Linq and a little manual code work
List<SaleDetailViewModel> list = new List<SaleDetailViewModel>();
while(read.Read())
{
... //Your mapping code
list.Add(reportData);
}
var groupedList = list.GroupBy(x => x.departmentName);
Which will give you a IEnumerable<IGrouping<string, SaleDetailViewModel>.
What that is a your data now grouped by the name of each department which acts as a key
You'll now loop through groupedList and add the values of that IGrouping up.
foreach(var group in groupedList)
{
var total = 0;
foreach(var saledetail in group)
{
total += saledetail.sumOfQuantity;
}
}
Then you just need to add the logic to add it to a list/your datatable in the order of those groups.
Hope this helps.
From this database, I want to select two users, 321 and 102 where BOOK_TYPE is 1 or 2 and compare their BOOK_NR, if they coincide then save BOOK_COUNT only from USER_ID 102.
SqlCommand command = new SqlCommand("SELECT BOOK_NR, BOOK_COUNT, USER_ID, BOOK_TYPE " +
"FROM BOOKS " +
"WHERE (BOOK_TYPE = '1' OR" + "BOOK_TYPE = '2')" +
"AND MONTH(DATE) = '" + DateTime.Today.Month + "'" +
"GROUP BY BOOK_NR, BOOK_COUNT, USER_ID, BOOK_TYPE ", BookConn);
SqlDataReader reader = command.ExecuteReader();
while (reader.Read())
{
string BookNr = (string)reader[0];
int Count = (int)reader[1];
int User = (int)reader[2];
int Type = (int)reader[3];
if( " HERE I NEED HELP " )
{
" AND HERE :) "
}
}
reader.Close();BookConn.Close();
My only solution is as follows
List<string> User321List = new List<string>();
List<string> User102List = new List<string>();
if(User == 321 && Type == 1){ User321List.Add(BookNr.Trim());}
if(User == 102 && Type == 2){ User102List.Add(BookNr.Trim()+"\t"+Count);}
and then...
int count = 0;
foreach (string x in User321List)
{
foreach (string y in User102List)
{
List<string> part = y.Split('\t').Select(p => p.Trim()).ToList();
var z = string.Compare(part[0], x);
if (z == 0)
{
count += int.Parse(part[1]);
}
}
}
but it takes a lot of time to get result..
if someone figured something out of my nightmare please help and sorry for my bad english...
I'm going to focus on answering just the first part:
I want to select two users, 321 and 102 where BOOK_TYPE is 1 or 2 and compare their BOOK_NR, if they coincide then save BOOK_COUNT only from USER_ID 102.
You want to join the table to itself:
SELECT b1.BOOK_NR, b1.BOOK_COUNT, b1.USER_ID, b1.BOOK_TYPE
FROM BOOKS b1
INNER JOIN BOOKS b2 ON b2.BOOK_TYPE IN ('1','2')
AND b2.User_ID = 321
AND b1.BOOK_NR = b2.BOOK_NR
WHERE b1.User_ID = 102 AND b1.BOOK_TYPE IN ('1','2')
AND MONTH(b1.DATE) = MONTH(current_timestamp)
AND MONTH(b2.DATE) = MONTH(current_timestamp)
Since one of your concerns was performance -- that the query was too slow -- I also need to ask whether the BOOK_TYPE column is a string or a number. If it's a number column, you should omit the single quotes on the values: for example, just use IN (1,2) instead of IN ('1','2'). This will help performance by avoiding potential per-row conversions and by potentially better matching to indexes.
If your query is slow, consider splitting the operation in two. Create a SQL Table function so you don't repeat the SQL, passing the users, type and month in as parameters.
Do not do an IN (as suggested in comment). Keep in mind that an OR in the WHERE clause will result in no indexes being used.
Join the two instances of the table function and let sql do the matching for you.
e.g.
Select * from dbo.UserList('321',1,12) as u321
inner join dbo.UserList('102',2,12) as u102
on u321.field = u102.field
BTW... I covered quite a few advanced topics, but the idea is to let SQL do what it does best: JOINS
As a matter of interest, if your sql table function returns many rows, and you ever use them in a sp, remember to use the option (recompile) clause ...
So I have a many to many relationship between something known as Specialism and SpecialismCombo. What I'm trying to do is take an int[] of ids and check if there is a combo already that contains the specialisms with those ids.
I was close but not quite right.
Say I have specialisms with Ids 1 and 3 and I create a combo with those specialisms.
If I pass in 3 & 1 then it returns the expected combo id.
If I pass in 1 then it returns the combo id that has both 1 and 3.
I can't just rely on total number of specialisms associated with the combo. Because if a combo has two items, 1 and 4 and the items being matched on are 1 and 3 I don't want this coming back as a matched combo.
So it's like I do need the count of this result, and match the count of total specialisms associated to the combo. I don't quite get whether I'm after a subquery or detatchedcriteria or how to get the result I want using nhibernate criteria. Thanks for your help!
int[] SpecialismIds = ArrayExtensions.ConvertArray<int>(idCollection.Split(new char[] { '|' }));
ICriteria query = m_SpecialismComboRepository.QueryAlias("sc");
query.CreateAlias("sc.Specialisms", "s", NHibernate.SqlCommand.JoinType.InnerJoin);
ICriterion lastCriteria = null;
foreach(int i in SpecialismIds)
{
ICriterion currentCriteria = Restrictions.Eq("s.SpecialismId", i);
if (lastCriteria != null)
lastCriteria = Restrictions.Or(lastCriteria, currentCriteria);
else
lastCriteria = currentCriteria;
}
if (lastCriteria != null)
query.Add(lastCriteria);
IProjection IdCount = Projections.Count("s.SpecialismId").As("IdCount");
query.SetProjection(
Projections.GroupProperty("sc.SpecialismComboId"),
IdCount
);
query.Add(Restrictions.Eq(IdCount, SpecialismIds.Count()));
var comboId = query.List();
The sql being generated is:
SELECT this_.SpecialismComboId as y0_, count(s1_.SpecialismId) as y1_
FROM dbo.SpecialismCombo this_
inner join SpecialismComboSpecialism specialism3_ on this_.SpecialismComboId=specialism3_.SpecialismId
inner join dbo.Specialism s1_ on specialism3_.SpecialismComboId=s1_.SpecialismId WHERE s1_.SpecialismId = #p0
GROUP BY this_.SpecialismComboId HAVING count(s1_.SpecialismId) = #p1',N'#p0 int,#p1 int',#p0=3,#p1=1
EDIT - It seems like I either need the having to be something like...
HAVING count(s1_.SpecialismId) = (select count(SpecialismId)
from specialismComboSpecialism
where SpecialismComboId = y0
group by SpecialismComboId) == #p2
Or maybe it's simpler than that and I need to exclude SpecalismCombos where the combo.specialisms are not in the collection of ids.
Ie. if the combo has specialisms 1 and 3 but the collection only has 1.. then we could exclude this combo based on 3 not being in the collection…
Edit 8/8/2011
Went back to focusing on how to get the result I needed in SQL - and I believe this query works.
WITH CustomQuery AS
(
SELECT sc.SpecialismComboId,
count(s.SpecialismId) AS ItemCount
FROM SpecialismCombo sc
inner join SpecialismComboSpecialism scs on sc.SpecialismComboId = scs.SpecialismComboId
inner join Specialism s on s.SpecialismId = scs.SpecialismId
GROUP BY sc.SpecialismComboId
HAVING count(s.SpecialismId) = 2
)
SELECT CustomQuery.SpecialismComboId FROM CustomQuery
INNER JOIN SpecialismComboSpecialism scs on CustomQuery.SpecialismComboId = scs.SpecialismComboId
WHERE scs.SpecialismId in (1,4)
GROUP BY CustomQuery.SpecialismComboId
HAVING count(scs.SpecialismId) = 2
So now I just need to figure out how to call this procedure from my nhibernate code passing in the appropriate values :)
I also discovered in the process that my mapping class was wrong - as it was putting the wrong values in the mapping table (ie. the specialismid was ending up in the specialismcomboid field !)
Your solution should actually work well. The specialisms are filtered by id and there shouldn't be anything left that is not searched for, so count should work. Unless you have the same specialism joined more the once. This currentCriteria lastCriteria stuff looks a bit strange, may be there is an error. Just use Expression.In or Conjunction.
IProjection IdCount = Projections.Count("s.SpecialismId").As("IdCount");
IQuery query = session
.CreateCriteria<SpecialismCombo>("sc")
.CreateCriteria("Specialism", "s");
.Add(Expression.In("s.SpecialismId", SpecialismIds));
.SetProjection(
Projections.GroupProperty("sc.SpecialismComboId"),
IdCount);
.Add(Restrictions.Eq(IdCount, SpecialismIds.Count()));
Should result in a query like this:
select ...
from
SpecialismCombo sc
inner join -- linktable ...
inner join Specialism s on ...
where
s.SpecialismId in (1, 4)
Group By sc.SpecialismComboId
having count(*) = 2
The same in HQL
from SpecialismCombo sc
join sc.Specialism s
where s.id in (:ids)
group by sc
having count(*) = :numberOfIds
You could also join the specialism as many times as you have ids to find:
IQuery query = session.CreateCriteria<SpecialismCombo>("sc")
int counter = 0;
foreach(int id in ids)
{
string alias = "s" + counter++;
query
.CreateCriteria("Specialism", alias);
.Add(Expression.Eq(alias + ".SpecialismId", id));
}
should create a query like this:
select ...
from
SpecialismCombo sc
inner join -- linktable ...
inner join Specialism s0 on ...
inner join -- linktable ...
inner join Specialism s1 on ...
where
s0.SpecialismId = 1
and s1.SpecialismId = 4
So I ended up creating a stored proc and using SQL CTE in order to get only the specialism combos with the correct count of specialisms. Posting this in case someone else comes across a similar issue.
Rediscovered after 8 months of using nhibernate that I'd forgotten a lot of SQL stuff :)
DECLARE #IdCollectionCount INT
, #IdCollection VARCHAR(250)
, #CollectionDelimiter NVARCHAR
SET #IdCollectionCount = 2;
SET #IdCollection = '1,4';
SET #CollectionDelimiter= ',';
WITH CustomQuery AS
(
SELECT sc.SpecialismComboId,
count(s.SpecialismId) AS ItemCount
FROM SpecialismCombo sc
inner join SpecialismComboSpecialism scs on sc.SpecialismComboId = scs.SpecialismComboId
inner join Specialism s on s.SpecialismId = scs.SpecialismId
GROUP BY sc.SpecialismComboId
HAVING count(s.SpecialismId) = #IdCollectionCount
)
SELECT Top 1 CustomQuery.SpecialismComboId FROM CustomQuery
INNER JOIN SpecialismComboSpecialism scs on CustomQuery.SpecialismComboId = scs.SpecialismComboId
INNER JOIN dbo.fn_SplitDelimited(#IdCollection,#CollectionDelimiter) AS ids
ON scs.SpecialismId = CAST(ids.ListValue AS INT)
GROUP BY CustomQuery.SpecialismComboId
HAVING count(scs.SpecialismId) = #IdCollectionCount