Home > Back-end >  SQL: Count elements in a joined table while using group by
SQL: Count elements in a joined table while using group by

Time:12-08

Having written this question, C# LinQ: get the total of elements of a related table, I am convinced I was not clear enough to get help (or at least I was not in the right direction for that), so I'll focus my question in a different way so to see if I get a start point.

My question was mainly about LinQ, and now I'll focus only on SQL and try to use a generalized way of asking, so please, don't mark it as duplicate again, because it is not.

Given the following tables, I need to list elements from TableA adding two columns counting how many elements from TableB and TableC has for each row:

TableA:

Column_name Type
TableA_id int
ForeignKey1 smallint
ForeignKey2 int
ForeignKey3 smallint

TableB:

Column_name Type
TableB_id int
ForeignKey1 smallint
ForeignKey2 smallint
ForeignKey3 int

TableC:

Column_name Type
TableC_id int
ForeignKey4 int

How tables are related:

  1. For each TableA row, ForeignKey1 ForeignKey2 ForeignKey3 equals one row in TableB that needs to be count, in other words, I need to count the occurences of ForeignKey1 ForeignKey2 ForeignKey3 in TableB and display it as an extra "count" column in the TableA list.

  2. Same, for each TableA row, I also need to count how many elements of the TableC belong to that row, taking into account that ForeignKey4 is the foreign key that links with TableB_id.

Saying it differently again, FK1 FK2 FK3 in TableA > one row in TableB, so TableA will have many related TableB rows and FK4 In TableC > one row in TableB, so in the end, one row in TableA will have x TableB and y TableC elements.

I hope I'm being clear now and get a SQL solution. Then I'll use it as a start point and try to translate it to LinQ.

This is what I was able to do so far:

With this next SQL query:

select a.tablea_id, a.foreignkey1, a.foreignkey2, a.foreignkey3, count(*) from TableA a
    left join TableB b 
        on a.foreignkey1 = b.foreignkey1 and
        a.foreignkey2 = b.foreignkey2 and 
        a.foreignkey3 = b.foreignkey3 
    group by a.tablea_id, a.foreignkey1, a.foreignkey2, a.foreignkey3

I am able to count elements from TableB for each TableA row, but I'm getting stuck with TableC part.

Edit 1: For @Svyatoslav Danyliv adding extra information so it's easier to help me:

Finca entity:

public partial class finca
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public finca()
    {
        this.ps = new HashSet<ps>();
        this.orden_trabajo = new HashSet<orden_trabajo>();
    }
    
    public int finca_id { get; set; }
    public string descripcion { get; set; }
    public System.DateTime fecha_creacion { get; set; }
    public int direccion_id { get; set; }
    public bool disponible_contratacion { get; set; }
    public short concesion_id { get; set; }
    public string codigo_gis { get; set; }
    public string numero_alternativo { get; set; }
    public Nullable<System.DateTime> fecha_ultima_inspeccion { get; set; }
    public Nullable<decimal> latitud { get; set; }
    public Nullable<decimal> longitud { get; set; }
    public int acometida_defecto_id { get; set; }
    public short planificacion_defecto_id { get; set; }
    public int oficina_mas_cercana_id { get; set; }
    public int ruta_defecto_id { get; set; }
    
    public virtual concesion concesion { get; set; }
    public virtual direccion direccion { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<ps> ps { get; set; }
    public virtual oficina oficina { get; set; }
    public virtual planificacion planificacion { get; set; }
    public virtual ruta ruta { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<orden_trabajo> orden_trabajo { get; set; }
    public virtual acometida acometida { get; set; }
}

Contrato entity:

public partial class contrato
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public contrato()
    {
        this.contrato_clausula = new HashSet<contrato_clausula>();
        this.contrato_file = new HashSet<contrato_file>();
        this.contrato_tipo_facturacion = new HashSet<contrato_tipo_facturacion>();
        this.incidencia_facturacion = new HashSet<incidencia_facturacion>();
        this.reclamacion = new HashSet<reclamacion>();
        this.deuda_externa = new HashSet<deuda_externa>();
        this.contrato_ciclo = new HashSet<contrato_ciclo>();
        this.contrato_campo_personalizado = new HashSet<contrato_campo_personalizado>();
        this.ciclo_contrato_consumo_minimo = new HashSet<ciclo_contrato_consumo_minimo>();
        this.contrato_historial = new HashSet<contrato_historial>();
        this.convenio_contrato = new HashSet<convenio_contrato>();
        this.convenio = new HashSet<convenio>();
        this.orden_domiciliacion = new HashSet<orden_domiciliacion>();
        this.contrato_coeficientes = new HashSet<contrato_coeficientes>();
        this.ov_usuario_contrato = new HashSet<ov_usuario_contrato>();
        this.sac_gestion = new HashSet<sac_gestion>();
        this.expediente_morosidad = new HashSet<expediente_morosidad>();
        this.contrato_tipo_contrato = new HashSet<contrato_tipo_contrato>();
        this.adeudo_domiciliado = new HashSet<adeudo_domiciliado>();
        this.contrato_tarjeta_bancaria = new HashSet<contrato_tarjeta_bancaria>();
        this.contrato_estado_morosidad = new HashSet<contrato_estado_morosidad>();
        this.contrato_cambio_email = new HashSet<contrato_cambio_email>();
        this.ov_usuario_solicitud_cuenta = new HashSet<ov_usuario_solicitud_cuenta>();
        this.cobro = new HashSet<cobro>();
        this.devolucion_saldo = new HashSet<devolucion_saldo>();
        this.contrato_consumo_historico_facturado = new HashSet<contrato_consumo_historico_facturado>();
        this.notificacion_cobro_ventanilla = new HashSet<notificacion_cobro_ventanilla>();
        this.grupo_gestor_cobro_contrato = new HashSet<grupo_gestor_cobro_contrato>();
        this.tarjeta_bancaria_operacion = new HashSet<tarjeta_bancaria_operacion>();
        this.factura = new HashSet<factura>();
        this.orden_trabajo = new HashSet<orden_trabajo>();
        this.grupo_gestor_cobro_contrato1 = new HashSet<grupo_gestor_cobro_contrato>();
    }
    
    public int contrato_id { get; set; }
    public short tipo_contrato_id { get; set; }
    public int ps_id { get; set; }
    public int cliente_id { get; set; }
    public short estado_contrato_idx { get; set; }
    public Nullable<decimal> importe_fianza { get; set; }
    public short idioma_comunicacion_id { get; set; }
    public Nullable<int> direccion_emision_id { get; set; }
    public string email_emision { get; set; }
    public System.DateTime fecha_alta { get; set; }
    public System.DateTime fecha_alta_solicitud { get; set; }
    public Nullable<System.DateTime> fecha_baja { get; set; }
    public Nullable<System.DateTime> fecha_solicitud_baja { get; set; }
    public Nullable<short> relacion_inmueble_alta_idx { get; set; }
    public bool cortable { get; set; }
    public Nullable<long> contrato_file_id { get; set; }
    public Nullable<int> ultimo_estado_morosidad_id { get; set; }
    public Nullable<decimal> importe_pendiente_afavor { get; set; }
    public Nullable<short> canal_cobro_idx { get; set; }
    public Nullable<int> cliente_apoderado_alta_id { get; set; }
    public Nullable<int> cliente_baja_id { get; set; }
    public string referencia { get; set; }
    public string observaciones { get; set; }
    public bool excluir_facturacion_periodica { get; set; }
    public Nullable<int> tipo_autorizado_baja_id { get; set; }
    public Nullable<long> fichero_baja_id { get; set; }
    public string observaciones_baja { get; set; }
    public Nullable<System.DateTime> fecha_baja_prevista { get; set; }
    public Nullable<bool> acepta_informacion_comercial { get; set; }
    public bool exento_recargos { get; set; }
    public int forma_envio_factura_id { get; set; }
    public string aviso_al_lector { get; set; }
    public Nullable<short> tipo_calibre_contratado_id { get; set; }
    public Nullable<short> tipo_entidad_face_idx { get; set; }
    public Nullable<int> oficina_contable_fe_id { get; set; }
    public Nullable<int> organo_gestor_fe_id { get; set; }
    public Nullable<int> unidad_tramitadora_fe_id { get; set; }
    public Nullable<int> organo_proponente_fe_id { get; set; }
    public short num_descargas_contrato_api { get; set; }
    public bool gran_consumidor { get; set; }
    
    public virtual cliente cliente { get; set; }
    public virtual idioma idioma { get; set; }
    public virtual direccion direccion { get; set; }
    public virtual tipo_contrato tipo_contrato { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_clausula> contrato_clausula { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_file> contrato_file { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_tipo_facturacion> contrato_tipo_facturacion { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<incidencia_facturacion> incidencia_facturacion { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<reclamacion> reclamacion { get; set; }
    public virtual cliente cliente_apoderado_alta { get; set; }
    public virtual cliente cliente_baja { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<deuda_externa> deuda_externa { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_ciclo> contrato_ciclo { get; set; }
    public virtual file file { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_campo_personalizado> contrato_campo_personalizado { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<ciclo_contrato_consumo_minimo> ciclo_contrato_consumo_minimo { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_historial> contrato_historial { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<convenio_contrato> convenio_contrato { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<convenio> convenio { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<orden_domiciliacion> orden_domiciliacion { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_coeficientes> contrato_coeficientes { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<ov_usuario_contrato> ov_usuario_contrato { get; set; }
    public virtual file fichero_baja { get; set; }
    public virtual tipo_autorizado_baja tipo_autorizado_baja { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<sac_gestion> sac_gestion { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<expediente_morosidad> expediente_morosidad { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_tipo_contrato> contrato_tipo_contrato { get; set; }
    public virtual ps ps { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<adeudo_domiciliado> adeudo_domiciliado { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_tarjeta_bancaria> contrato_tarjeta_bancaria { get; set; }
    public virtual contrato_estado_morosidad ultimo_contrato_estado_morosidad { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_estado_morosidad> contrato_estado_morosidad { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_cambio_email> contrato_cambio_email { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<ov_usuario_solicitud_cuenta> ov_usuario_solicitud_cuenta { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<cobro> cobro { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<devolucion_saldo> devolucion_saldo { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<contrato_consumo_historico_facturado> contrato_consumo_historico_facturado { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<notificacion_cobro_ventanilla> notificacion_cobro_ventanilla { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<grupo_gestor_cobro_contrato> grupo_gestor_cobro_contrato { get; set; }
    public virtual forma_envio_factura forma_envio_factura { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<tarjeta_bancaria_operacion> tarjeta_bancaria_operacion { get; set; }
    public virtual tipo_calibre tipo_calibre { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<factura> factura { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<orden_trabajo> orden_trabajo { get; set; }
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<grupo_gestor_cobro_contrato> grupo_gestor_cobro_contrato1 { get; set; }
}

Punto de suministro entity:

public partial class puntosuministroview
{
    public int ps_id { get; set; }
    public Nullable<int> contrato_id { get; set; }
    public string razon_social { get; set; }
    public string cliente_con_identificacion { get; set; }
    public Nullable<System.DateTime> fecha_baja_contrato { get; set; }
    public short concesion_id { get; set; }
    public short planificacion_id { get; set; }
    public Nullable<int> acometida_id { get; set; }
    public string direccion_nombre_extendido { get; set; }
    public Nullable<int> cliente_id { get; set; }
    public string nombre_planificacion { get; set; }
    public Nullable<short> ps_clasificacion_id { get; set; }
    public short empresa_id { get; set; }
    public string nombre_concesion { get; set; }
    public short ps_activo_idx { get; set; }
    public Nullable<int> ruta_id { get; set; }
    public string ruta { get; set; }
    public string identificacion { get; set; }
    public Nullable<short> zona_id { get; set; }
    public string nombre_zona { get; set; }
    public string descripcion { get; set; }
    public string codigo { get; set; }
}

From "puntosuministroview" concesion_id planificacion_id acometida_id identifies one Finca

From "contrato" ps_id identifies one puntodesuministroview.

What I need to a LinQ query where I list Fincas, but with two added columns where I count "puntosdesuministroview" and "contratos".

CodePudding user response:

If I understand what you are trying to do, this may help.

select a.tablea_id, a.foreignkey1, a.foreignkey2, a.foreignkey3, count(b.TableB_id) b_count, count(c.TableC_id) c_count
  from
    TableA a
    left join TableB b  on a.foreignkey1 = b.foreignkey1 and a.foreignkey2 = b.foreignkey2 and  a.foreignkey3 = b.foreignkey3
    left join TableC c on b.TableB_id = c.foreignkey4
  group by a.tablea_id, a.foreignkey1, a.foreignkey2, a.foreignkey3

CodePudding user response:

A row in tablea can have many entries in tableb, and a row in tableb can again have many entries in tablec. You want to show all tablea rows, each with their tableb and tablec counts.

In standard SQL you could simply join, aggregate and count as in the following query. The only thing to be aware of is that each tableb row appears multiple times in the joins to to them being linked to multiple tablec rows. So, count distinct tableb IDs to get the correct count.

select
  a.*,
  count(distinct b.tableb_id) as b_count,
  count(c.tablec_id) as c_count
from tablea a
left outer join tableb b on b.foreignkey1 = a.foreignkey1
                        and b.foreignkey2 = a.foreignkey2
                        and b.foreignkey3 = a.foreignkey3
left outer join tablec c on c.foreignkey4 = b.tableb_id
group by a.tablea_id
order by a.tablea_id;

In SQL Server this is not possible, because this DBMS is not able to detect that grouping by the tablea's ID means it can show the complete row, as the ID uniqly identifies this data. (This is called functional dependency.) In SQL Server you can write above query, but the GROUP BY clause must contain all tablea columns. In the example:

group by a.tablea_id, a.foreignkey1, a.foreignkey2, a.foreignkey3

Another option is to do the aggregation step by step:

with c as
(
  select foreignkey4, count(*) as cnt
  from tablec
  group by foreignkey4
)
, c_and_b as
(
  select
    b.foreignkey1, b.foreignkey2, b.foreignkey3,
    count(*) as b_cnt,
    coalesce(sum(c.cnt), 0) as c_cnt
  from tableb b
  left outer join c on c.foreignkey4 = b.tableb_id
  group by b.foreignkey1, b.foreignkey2, b.foreignkey3
)
select
  a.*,
  coalesce(c_and_b.b_cnt, 0) as b_count,
  coalesce(c_and_b.c_cnt, 0) as c_count
from tablea a
left outer join c_and_b on c_and_b.foreignkey1 = a.foreignkey1
                       and c_and_b.foreignkey2 = a.foreignkey2
                       and c_and_b.foreignkey3 = a.foreignkey3
order by a.tablea_id;

This latter query looks a lot more complicated than the first one. This is mainly because of the chain (a->b->c) where you want both the b and the c count. If b and c were independent from each other (say a = posts, b = upvotes, c = downvotes), then aggregating b and c and then joining those two results to table a would even be my preferred method. The query would still not look as slimlined as the first query, but would avoid other problems (mainly an unnecessarily big intermediate result plus aggregation problems when it's not only about counting, but about sums or averages).

  • Related